A lightweight logging system with an incredibly simple configuration. Stream your application logs instantly with an automatic 24-hour retention buffer.
Send logs easily from any application. It's simple and blazing fast.
// --- .env ---
// QUICK_LOGGER_TOKEN=demo_token
// ------------
package logger
import (
"bytes"
"encoding/json"
"net/http"
"os"
"sync"
"time"
"github.com/joho/godotenv"
)
var (
apiUrl string
logBuffer []map[string]string
mutex sync.Mutex
flushInterval = 5 * time.Second
)
func init() {
_ = godotenv.Load()
apiUrl = "https://universallogger.fly.dev/api/v1/logs/batch?token=" + os.Getenv("QUICK_LOGGER_TOKEN")
// Start a background worker to periodically send logs
go startBackgroundFlusher()
}
// startBackgroundFlusher runs infinitely and flushes logs at regular intervals.
func startBackgroundFlusher() {
for {
time.Sleep(flushInterval)
flushLogs()
}
}
// flushLogs securely reads the current buffer, clears it, and sends the batch via HTTP.
func flushLogs() {
mutex.Lock()
if len(logBuffer) == 0 {
mutex.Unlock()
return
}
// Copy the buffer and clear the original
batch := logBuffer
logBuffer = nil
mutex.Unlock()
// Send the batch to the QuickLogger API asynchronously
body, _ := json.Marshal(batch)
go http.Post(apiUrl, "application/json", bytes.NewBuffer(body))
}
// sendLog is a thread-safe helper to append a single log to the buffer.
func sendLog(level, message string) {
mutex.Lock()
defer mutex.Unlock()
logBuffer = append(logBuffer, map[string]string{
"level": level,
"message": message,
})
}
// Public API methods for logging
func LogInfo(message string) { sendLog("INFO", message) }
func LogWarning(message string) { sendLog("WARN", message) }
func LogError(message string) { sendLog("ERROR", message) }
# --- .env ---
# QUICK_LOGGER_TOKEN=demo_token
# ------------
import requests
import threading
import time
import os
from dotenv import load_dotenv
load_dotenv()
API_URL = f"https://universallogger.fly.dev/api/v1/logs/batch?token={os.environ.get('QUICK_LOGGER_TOKEN', '')}"
FLUSH_INTERVAL = 5 # seconds
log_buffer = []
lock = threading.Lock()
def start_background_worker():
"""Starts a daemon thread that periodically flushes logs to the server."""
thread = threading.Thread(target=_flush_logs_periodically, daemon=True)
thread.start()
def _flush_logs_periodically():
"""Infinite loop that flushes logs at defined intervals."""
while True:
time.sleep(FLUSH_INTERVAL)
flush_now()
def flush_now():
"""Reads the current buffer safely, clears it, and sends an HTTP POST request."""
global log_buffer
with lock:
if not log_buffer:
return
# Copy the buffer and clear the original
batch = log_buffer[:]
log_buffer.clear()
try:
# Send the batch to the API
requests.post(API_URL, json=batch, timeout=3)
except Exception:
pass # Ignore network errors to prevent app crashing
# Start the worker immediately
start_background_worker()
def _send_log(level: str, message: str):
"""Thread-safe function to append a log to the buffer."""
with lock:
log_buffer.append({"level": level, "message": message})
# Public logging functions
def log_info(message: str): _send_log("INFO", message)
def log_warning(message: str): _send_log("WARN", message)
def log_error(message: str): _send_log("ERROR", message)
// --- .env ---
// VITE_QUICK_LOGGER_TOKEN=demo_token
// ------------
// Example for Vite, adapt to your framework's env variables
const API_URL = `https://universallogger.fly.dev/api/v1/logs/batch?token=${import.meta.env.VITE_QUICK_LOGGER_TOKEN}`;
const FLUSH_INTERVAL_MS = 5000;
let logBuffer = [];
// Start the periodic flushing mechanism
setInterval(flushLogs, FLUSH_INTERVAL_MS);
/**
* Sends the current buffered logs to the server.
* Uses keepalive to ensure requests finish even if the user closes the tab.
*/
function flushLogs() {
if (logBuffer.length === 0) return;
// Copy the buffer and clear it
const batch = [...logBuffer];
logBuffer = [];
try {
fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(batch),
// keepalive ensures the browser finishes the request on page unload
keepalive: true
}).catch(() => { /* silent fail */ });
} catch (e) {
// Handle sync errors gracefully
}
}
/**
* Helper to append a log to the internal buffer.
*/
function appendLog(level, message) {
logBuffer.push({ level, message });
}
// Exported public API
export const logInfo = (msg) => appendLog("INFO", msg);
export const logWarning = (msg) => appendLog("WARN", msg);
export const logError = (msg) => appendLog("ERROR", msg);
// --- .env ---
// QUICK_LOGGER_TOKEN=demo_token
// ------------
require('dotenv').config();
const express = require('express');
const app = express();
const API_URL = `https://universallogger.fly.dev/api/v1/logs/batch?token=${process.env.QUICK_LOGGER_TOKEN}`;
const FLUSH_INTERVAL_MS = 5000;
let logBuffer = [];
// Start the periodic flushing mechanism
setInterval(flushLogs, FLUSH_INTERVAL_MS);
/**
* Sends the current buffered logs to the server.
* Requires Node 18+ for native fetch API.
*/
function flushLogs() {
if (logBuffer.length === 0) return;
// Copy the buffer and clear it
const batch = [...logBuffer];
logBuffer = [];
fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(batch)
}).catch(() => { /* silent fail */ });
}
/**
* Appends a log to the internal buffer.
*/
function sendLog(level, message) {
logBuffer.push({ level, message });
}
// Example usage in an Express route
app.get('/', (req, res) => {
sendLog("INFO", "User visited homepage");
res.send('Hello from Express');
});
// Express Global Error Handling Middleware
// Automatically catches and logs any unhandled exceptions
app.use((err, req, res, next) => {
sendLog("ERROR", `[${req.method}] ${req.path} - ${err.message}`);
res.status(500).send('Internal Server Error');
});
app.listen(3000, () => console.log('Server running on port 3000'));
// --- .env ---
// QUICK_LOGGER_TOKEN=demo_token
// ------------
use reqwest::Client;
use serde_json::json;
use std::env;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::{sleep, Duration};
use dotenvy::dotenv;
use lazy_static::lazy_static;
// Global thread-safe log buffer
lazy_static! {
static ref LOG_BUFFER: Arc<Mutex<Vec<serde_json::Value>>> = Arc::new(Mutex::new(Vec::new()));
}
/// Initializes the background worker that periodically sends logs to the server.
/// Call this once when your application starts.
pub async fn start_logger() {
dotenv().ok();
let token = env::var("QUICK_LOGGER_TOKEN").unwrap_or_default();
let api_url = format!("https://universallogger.fly.dev/api/v1/logs/batch?token={}", token);
let client = Client::new();
let buffer = LOG_BUFFER.clone();
// Spawn a background task
tokio::spawn(async move {
flush_loop(client, api_url, buffer).await;
});
}
/// The infinite loop that manages the flushing interval.
async fn flush_loop(client: Client, api_url: String, buffer: Arc<Mutex<Vec<serde_json::Value>>>) {
loop {
sleep(Duration::from_secs(5)).await;
let mut batch = Vec::new();
{
// Safely acquire the lock, copy data, and clear the original buffer
let mut locked_buffer = buffer.lock().await;
if locked_buffer.is_empty() {
continue;
}
batch.append(&mut *locked_buffer);
}
// Send the batch asynchronously
if !batch.is_empty() {
let _ = client.post(&api_url).json(&batch).send().await;
}
}
}
/// Helper function to append a log to the global buffer.
pub async fn append_log(level: &str, message: &str) {
let payload = json!({
"level": level,
"message": message
});
let mut locked_buffer = LOG_BUFFER.lock().await;
locked_buffer.push(payload);
}
// Public logging API
pub async fn log_info(msg: &str) { append_log("INFO", msg).await; }
pub async fn log_warning(msg: &str) { append_log("WARN", msg).await; }
pub async fn log_error(msg: &str) { append_log("ERROR", msg).await; }
Live Demo
Live Stream
Watch the logs stream in real-time below, powered by WebSockets.
demo-terminal
[SYSTEM] Establishing handshake over secure proxy...
[SYSTEM] Connected to demo project.
[SYSTEM] Listening for incoming events...