@steve31415/log-logger-ts
v1.1.4
Published
Lightweight logging library for Cloudflare Workers and browsers
Maintainers
Readme
@steve31415/log-logger-ts
Lightweight logging library for Cloudflare Workers and browsers with automatic batching and dual logging (console + backend).
Installation
npm install @steve31415/log-logger-tsFeatures
- Dual logging: Logs to console immediately and batches for backend delivery
- Automatic batching: Flushes on 10 events, 1 second timeout, or any ERROR (whichever first)
- Timeout-based flush:
flushWithTimeout(ms)for Workers withctx.waitUntil - Best-effort delivery:
flushSync()usessendBeaconfor browser page unload - Workers utilities:
waitUntilFlush,createRequestLogger,withRequestLoggerfor easy setup - Hono middleware: Built-in middleware for Hono framework
- Service binding support: Pass service bindings instead of using global fetch
- Error serialization:
serializeError()utility for logging errors with full context
Usage
Cloudflare Workers (recommended - with service binding)
For Cloudflare Workers, use a service binding for LOG_INGEST to route requests internally:
import { createRequestLogger, waitUntilFlush } from '@steve31415/log-logger-ts';
// wrangler.toml: [[services]] binding = "LOG_INGEST" service = "log-ingest"
export default {
async fetch(request, env, ctx) {
const logger = createRequestLogger({
app: 'my-app',
logIngest: env.LOG_INGEST,
});
try {
logger.info('Request received', { url: request.url });
// ... handle request
return new Response('OK');
} finally {
waitUntilFlush(ctx, logger);
}
}
}Cloudflare Workers (with handler wrapper)
The withRequestLogger wrapper automatically sets up logging, generates run IDs, and handles flushing:
import { withRequestLogger } from '@steve31415/log-logger-ts';
type Env = { LOG_INGEST: ServiceBindingLike };
const handler = withRequestLogger<Env>(
async (request, env, ctx, logger) => {
logger.info('Processing request', { url: request.url });
return new Response('OK');
},
{
app: 'my-app',
getLogIngest: (env) => env.LOG_INGEST,
}
);
export default { fetch: handler };Cloudflare Workers (with Hono)
Use the built-in Hono middleware for seamless integration:
import { Hono } from 'hono';
import { honoLoggerMiddleware, getLogger } from '@steve31415/log-logger-ts';
type Env = { Bindings: { LOG_INGEST: ServiceBindingLike } };
const app = new Hono<Env>();
app.use('*', honoLoggerMiddleware({
app: 'my-app',
getLogIngest: (env) => env.LOG_INGEST,
}));
app.get('/', (c) => {
const logger = getLogger(c);
logger.info('Hello from route handler');
return c.text('Hello!');
});
export default app;Cloudflare Workers (manual setup)
For full control, create the logger manually:
import { Logger, FetchDeliveryAdapter } from '@steve31415/log-logger-ts';
const INGEST_ENDPOINT = 'https://log-ingest.example.com/v1/logs:ingest';
export default {
async fetch(request, env, ctx) {
// Pass service binding as second argument
const logger = new Logger(
{ app: 'my-app', context: 'worker_http' },
new FetchDeliveryAdapter(INGEST_ENDPOINT, env.LOG_INGEST)
);
try {
logger.info('Request received', { url: request.url });
// ... handle request
return new Response('OK');
} finally {
ctx.waitUntil(logger.flushWithTimeout(3000));
}
}
}Cloudflare Workers (cron/queue)
import { createRequestLogger, waitUntilFlush } from '@steve31415/log-logger-ts';
export default {
async scheduled(event, env, ctx) {
const logger = createRequestLogger({
app: 'my-app',
logIngest: env.LOG_INGEST,
context: 'worker_cron',
runId: crypto.randomUUID(),
});
try {
logger.info('Cron started');
// ... do work
logger.info('Cron completed');
} finally {
waitUntilFlush(ctx, logger);
}
}
}Browser
import { Logger, BrowserDeliveryAdapter } from '@steve31415/log-logger-ts';
const logger = new Logger(
{ app: 'my-app', context: 'web_frontend' },
new BrowserDeliveryAdapter('/api/logs')
);
// Flush on page visibility change (uses sendBeacon for reliability)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
logger.flushSync();
}
});API
Logger Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| app | string | required | Application name (see reserved names below) |
| context | string | required | Execution context (e.g., worker_http, web_frontend) |
| runId | string | optional | Correlation ID for invocation/session |
| logger | string | optional | Module/subsystem name |
| batchSize | number | 10 | Max events before flush |
| batchTimeoutMs | number | 1000 | Max time before flush (ms) |
| consoleLog | boolean | true | Enable console logging |
| minLevel | LogLevel | 'DEBUG' | Minimum level to log |
Reserved App Names
The app name log is reserved. When app: 'log' is used, the logger writes only to the native console and skips backend delivery entirely. This prevents recursive logging when the logging infrastructure itself needs to log.
Log Levels
logger.debug(message, meta?)- DEBUG levellogger.info(message, meta?)- INFO levellogger.warn(message, meta?)- WARN levellogger.error(message, meta?)- ERROR level (triggers immediate flush)
Child Loggers
const logger = new Logger({ app: 'my-app', context: 'worker_http' }, adapter);
const dbLogger = logger.child('database');
const authLogger = logger.child('auth');
dbLogger.info('Query executed'); // Logs as [my-app:database]
authLogger.warn('Token expired'); // Logs as [my-app:auth]Flush Methods
flush()- Async flush of buffered eventsflushWithTimeout(ms)- Flush with timeout, falls back tosendBeaconif availableflushSync()- Synchronous best-effort flush usingsendBeacon(for page unload)close()- Flush and prevent further logging
Workers Utilities
waitUntilFlush(ctx, logger, timeoutMs?)
Safely schedule a logger flush using ctx.waitUntil. This is the recommended way to ensure logs are delivered before the worker terminates.
waitUntilFlush(ctx, logger); // Default 3000ms timeout
waitUntilFlush(ctx, logger, 5000); // Custom timeoutcreateRequestLogger(options)
Create a logger configured for request handling. Returns a Logger instance.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| app | string | required | Application name |
| logIngest | ServiceBindingLike | required | Service binding for log ingest |
| context | string | 'worker_http' | Execution context |
| runId | string | optional | Correlation ID |
| endpoint | string | default endpoint | Custom ingest URL |
| config | object | {} | Additional Logger config |
withRequestLogger(handler, options)
Wrap a Workers fetch handler with automatic logger setup. The logger is injected as the fourth parameter.
const handler = withRequestLogger<Env>(
async (request, env, ctx, logger) => {
// logger is automatically created and flushed
return new Response('OK');
},
{
app: 'my-app',
getLogIngest: (env) => env.LOG_INGEST,
generateRunId: () => crypto.randomUUID(), // optional
flushTimeoutMs: 3000, // optional
}
);Hono Middleware
honoLoggerMiddleware(options)
Create Hono middleware that sets up logging for each request.
app.use('*', honoLoggerMiddleware({
app: 'my-app',
getLogIngest: (env) => env.LOG_INGEST,
logRequestStart: true, // Log "Request started" (default: true)
logRequestComplete: true, // Log "Request completed" (default: true)
}));getLogger(c) / getLoggerOrUndefined(c)
Retrieve the logger from Hono context.
app.get('/api', (c) => {
const logger = getLogger(c); // Throws if middleware not configured
// or
const logger = getLoggerOrUndefined(c); // Returns undefined if not available
});Error Serialization
serializeError(error)
Serialize an error for logging. Extracts useful properties from Error objects and handles non-Error values.
import { serializeError } from '@steve31415/log-logger-ts';
try {
await riskyOperation();
} catch (error) {
logger.error('Operation failed', { error: serializeError(error) });
}Output for Error instances:
{
"name": "TypeError",
"message": "Cannot read property 'x' of undefined",
"stack": "TypeError: Cannot read property...",
"cause": { ... }, // If present (ES2022)
"customProp": "value" // Any additional enumerable properties
}Service Bindings
For Cloudflare Workers, always use a service binding instead of global fetch:
# wrangler.toml
[[services]]
binding = "LOG_INGEST"
service = "log-ingest"// Pass to FetchDeliveryAdapter
new FetchDeliveryAdapter(endpoint, env.LOG_INGEST)
// Or use convenience functions
createRequestLogger({ app: 'my-app', logIngest: env.LOG_INGEST })License
MIT
