Cheap, simple logging
ready in seconds
LogAbyss is a lightweight structured log ingest service built for developers who want fast, affordable observability without agent bloat. One HTTP endpoint, Basic Auth — works from any language, runtime, or serverless function.
API Credentials
Every request is authenticated with Basic Auth. You need an App ID (username) and an App Secret (password) for each app you want to log to.
Go to logabyss.com and sign in to your account.
In the left panel click Apps.
Select an existing app or create a new one, then copy the App ID and App Secret.
Tip: Store credentials as environment variables — LOGABYSS_APP_ID and LOGABYSS_APP_SECRET — and never commit them to source control.
cURL
The fastest way to verify your credentials and confirm the API is reachable. Replace YOUR_APP_ID and YOUR_APP_SECRET with the values from your panel.
curl --location 'https://api.logabyss.com/logs/winston' \
--header 'Content-Type: application/json' \
-u YOUR_APP_ID:YOUR_APP_SECRET \
--data '[
{
"level": "info",
"label": "my-worker",
"message": "Server started",
"timestamp": "2025-01-15T10:30:00.000Z"
},
{
"level": "error",
"label": "my-worker",
"message": { "error": "Something went wrong", "code": 500 },
"timestamp": "2025-01-15T10:30:05.123Z"
}
]'
A 200 response with an empty body means your logs were accepted. See the API Reference for all status codes.
JS / TS Library
A zero-dependency client that batches logs automatically and sends them every few seconds — no npm install needed. Copy the file into your project and import it.
Save as logabyss.ts anywhere in your project, then import { LogAbyss } from './logabyss.js'
Save as logabyss.js anywhere in your project, then import { LogAbyss } from './logabyss.js'
Works in Node.js 18+, Bun, and Cloudflare Workers without any build step.
Basic usage
import { LogAbyss } from './logabyss.js';
const logger = new LogAbyss({
credentials: {
appId: process.env.LOGABYSS_APP_ID!,
appSecret: process.env.LOGABYSS_APP_SECRET!,
},
label: 'web-worker', // identifies this process in the dashboard
batchTimeout: 3000, // auto-send every 3 s (default)
maxBatchSize: 100, // flush early if 100 entries pile up (default)
});
logger.info('Server started');
logger.warn('High memory usage', { usedMb: 512 });
logger.error('Unhandled exception', { stack: err.stack });
logger.debug('Cache miss', { key: 'user:42' });
// Attach a requestId to correlate all logs for a single request
logger.info('Payment processed', { amount: 99.99 }, { requestId: reqId });
// Before shutdown — ensure no buffered logs are dropped
await logger.flush();
Node.js
Requires Node.js 18+ (built-in fetch). Logs are batched and sent every 3 seconds in the background — no changes to your request handling needed.
// 1. Copy logabyss.js into your project
// 2. Import and create a logger instance (once, at module level)
import { LogAbyss } from './logabyss.js';
import http from 'http';
const logger = new LogAbyss({
credentials: {
appId: process.env.LOGABYSS_APP_ID,
appSecret: process.env.LOGABYSS_APP_SECRET,
},
label: 'web-worker',
});
const server = http.createServer((req, res) => {
logger.info('Request', { method: req.method, url: req.url });
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('OK');
});
server.listen(3000, () => {
logger.info('Listening on port 3000');
});
// Flush remaining buffered logs on shutdown
process.on('SIGTERM', async () => {
logger.info('Shutting down…');
await logger.flush();
process.exit(0);
});
Bun
Bun ships with native fetch and full ESM support — the library works without modification.
// 1. Copy logabyss.js into your project
import { LogAbyss } from './logabyss.js';
const logger = new LogAbyss({
credentials: {
appId: process.env.LOGABYSS_APP_ID,
appSecret: process.env.LOGABYSS_APP_SECRET,
},
label: 'bun-server',
});
const server = Bun.serve({
port: 3000,
async fetch(req) {
logger.info('Request', { url: req.url, method: req.method });
return new Response('OK');
},
});
logger.info(`Listening on port ${server.port}`);
process.on('SIGTERM', async () => {
await logger.flush();
process.exit(0);
});
Python
Pure standard-library implementation — no pip install required. A background thread flushes the batch every 3 seconds.
import json
import threading
import time
import urllib.request
from base64 import b64encode
from datetime import datetime, timezone
APP_ID = "YOUR_APP_ID" # or os.environ["LOGABYSS_APP_ID"]
APP_SECRET = "YOUR_APP_SECRET" # or os.environ["LOGABYSS_APP_SECRET"]
LABEL = "python-worker"
ENDPOINT = "https://api.logabyss.com/logs/winston"
_batch = []
_lock = threading.Lock()
def _auth_header():
token = b64encode(f"{APP_ID}:{APP_SECRET}".encode()).decode()
return {"Content-Type": "application/json", "Authorization": f"Basic {token}"}
def flush():
"""Send all buffered logs immediately."""
with _lock:
if not _batch:
return
payload, _batch[:] = _batch[:], []
body = json.dumps(payload).encode()
req = urllib.request.Request(ENDPOINT, data=body, headers=_auth_header(), method="POST")
urllib.request.urlopen(req, timeout=10)
def log(message, level="info", request_id=None):
entry = {
"level": level,
"label": LABEL,
"message": message,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
if request_id:
entry["requestId"] = request_id
with _lock:
_batch.append(entry)
def _auto_flush():
while True:
time.sleep(3)
try:
flush()
except Exception as exc:
print(f"[logabyss] flush error: {exc}")
threading.Thread(target=_auto_flush, daemon=True).start()
# ── Usage ──────────────────────────────────────────────────────────────────
log("Server started")
log({"event": "user_login", "user_id": 42})
log("Payment failed", level="error")
log("Request handled", level="info", request_id="abc-123")
# On shutdown
flush()
AWS Lambda
Lambda may freeze the execution environment immediately after the handler returns. Always call await logger.flush() — a finally block is the safest place.
// handler.mjs — copy logabyss.js into the same directory
import { LogAbyss } from './logabyss.js';
// Declare outside the handler to reuse across warm invocations
const logger = new LogAbyss({
credentials: {
appId: process.env.LOGABYSS_APP_ID,
appSecret: process.env.LOGABYSS_APP_SECRET,
},
label: 'lambda-fn',
});
export const handler = async (event, context) => {
logger.info('Invoked', { requestId: context.awsRequestId, event });
try {
// ... your logic ...
logger.info('Success');
return { statusCode: 200, body: 'OK' };
} catch (err) {
logger.error('Handler failed', { error: String(err) });
throw err;
} finally {
// ⚠️ Always flush — Lambda freezes the container after the handler returns.
await logger.flush();
}
};
Google Cloud Functions
Flush before calling res.send() — Cloud Functions may terminate the process immediately after the response is sent.
// index.mjs — copy logabyss.js into the same directory
import { LogAbyss } from './logabyss.js';
const logger = new LogAbyss({
credentials: {
appId: process.env.LOGABYSS_APP_ID,
appSecret: process.env.LOGABYSS_APP_SECRET,
},
label: 'gcf-fn',
});
export const handler = async (req, res) => {
logger.info('Request received', { method: req.method, path: req.path });
try {
// ... your logic ...
logger.info('Done');
await logger.flush(); // ⚠️ flush before res.send()
res.send('OK');
} catch (err) {
logger.error('Function error', { error: String(err) });
await logger.flush();
res.status(500).send('Error');
}
};
Azure Functions
Same pattern — flush before returning the response object so Azure does not terminate the process before the logs are sent.
// src/functions/myFunction.mjs — copy logabyss.js into src/
import { LogAbyss } from '../logabyss.js';
import { app } from '@azure/functions';
const logger = new LogAbyss({
credentials: {
appId: process.env.LOGABYSS_APP_ID,
appSecret: process.env.LOGABYSS_APP_SECRET,
},
label: 'azure-fn',
});
app.http('myFunction', {
methods: ['GET', 'POST'],
authLevel: 'anonymous',
handler: async (request, context) => {
logger.info('Triggered', { invocationId: context.invocationId });
// ... your logic ...
logger.info('Returning response');
await logger.flush(); // ⚠️ flush before returning
return { body: 'OK' };
},
});
Cloudflare Workers
Workers must return a Response synchronously — do not block with
await logger.flush() before returning. Instead, use
ctx.waitUntil() to keep the Worker alive for the flush after the response is sent.
Note: Cloudflare Workers support setTimeout, but the Worker lifetime is very short. Rely on ctx.waitUntil(logger.flush()) at the end of every request rather than the auto-batch timer.
// worker.js — copy logabyss.js into the same directory (wrangler bundles it)
import { LogAbyss } from './logabyss.js';
export default {
async fetch(request, env, ctx) {
const logger = new LogAbyss({
credentials: {
appId: env.LOGABYSS_APP_ID,
appSecret: env.LOGABYSS_APP_SECRET,
},
label: 'cf-worker',
batchTimeout: 30_000, // effectively disabled — rely on flush() instead
});
logger.info('Request received', { url: request.url, method: request.method });
// ... your logic ...
const response = new Response('OK', { status: 200 });
// ctx.waitUntil — returns the response immediately while logs finish sending
ctx.waitUntil(logger.flush());
return response;
},
};
Add secrets via Wrangler: wrangler secret put LOGABYSS_APP_ID and wrangler secret put LOGABYSS_APP_SECRET.
API Reference
Endpoint
POST https://api.logabyss.com/logs/winston
Content-Type: application/json
Authorization: Basic base64(appId:appSecret)
Request body
A JSON array of one or more log entry objects.
[
{
"level": "info",
"label": "web-worker",
"message": "Server started",
"timestamp": "2025-01-15T10:30:00.000Z",
"requestId": "25656582-d074-4a2a-b8aa-b607b92d306d"
},
{
"label": "web-worker",
"message": { "event": "cache_miss", "key": "user:42" }
}
]
Log entry fields
| Field | Type | Required | Description |
|---|---|---|---|
| level | string | No | One of debug, info, warn, error. Defaults to info. |
| label | string | Yes | Worker or process name within the app — e.g. web-worker, sidekiq, hostname. Used for filtering in the dashboard. |
| message | string | object | Yes | The log payload. Can be a plain string or any JSON-serialisable object. |
| timestamp | ISO 8601 string | No | When the event occurred. Falls back to server receive time when omitted. Setting it is strongly recommended for accurate ordering. |
| requestId | UUID string | No | Correlation ID for grouping all log lines that belong to the same request or job. |
Response codes
| Code | Meaning |
|---|---|
| 200 | Logs accepted. Response body is empty. |
| 400 | Validation failed — check your JSON structure (array of objects with a label field). |
| 401 | Invalid credentials — verify your App ID and App Secret in the LogAbyss panel. |