@neutrino-io/logger
Structured JSON logger for Neutrino Cloudflare Workers services.
Package: @neutrino-io/logger
Lightweight structured logging for Cloudflare Workers services. Emits newline-delimited JSON via console.log() (info/warn/debug) and console.error() (error), compatible with Cloudflare's log aggregation pipeline (surfaces in CF Workers logs).
import {
Logger,
createLogger,
initTrace,
withTraceHeaders,
requestLogger,
} from "@neutrino-io/logger";Log Levels
| Level | Method | Output |
|---|---|---|
"debug" | logger.debug() | console.log() |
"info" | logger.info() | console.log() |
"warn" | logger.warn() | console.log() |
"error" | logger.error() | console.error() |
Logger
Structured JSON logger class. Each instance is bound to a service name and optionally to a traceId and requestId for request-scoped logging.
class Logger {
constructor(service: string, traceId?: string, requestId?: string);
info(message: string, data?: Record<string, unknown>): void;
warn(message: string, data?: Record<string, unknown>): void;
error(message: string, data?: Record<string, unknown>): void;
debug(message: string, data?: Record<string, unknown>): void;
}Every call emits a LogEntry JSON object:
interface LogEntry {
timestamp: string; // ISO 8601
level: LogLevel;
service: string;
traceId?: string;
requestId?: string;
message: string;
[key: string]: unknown; // spread from the data argument
}Example output:
{
"timestamp": "2026-03-31T12:00:00.000Z",
"level": "info",
"service": "iam",
"traceId": "abc-123",
"requestId": "req-456",
"message": "→ request",
"method": "POST",
"path": "/api/nno/session"
}createLogger(service)
Factory function — creates a Logger instance bound to the given service name. Use this for module-level loggers that are not yet bound to a request context.
function createLogger(service: string): Logger;import { createLogger } from "@neutrino-io/logger";
const log = createLogger("my-service");
log.info("Service started");
log.error("Unhandled error", { error: err.message });For request-scoped logging with trace IDs, prefer constructing a Logger directly or using the requestLogger Hono middleware.
initTrace(request)
Extracts traceId and requestId from incoming request headers (x-trace-id, x-request-id). Generates new UUIDs for any missing values.
function initTrace(request: Request): { traceId: string; requestId: string };import { initTrace, Logger } from "@neutrino-io/logger";
export default {
async fetch(request: Request, env: Env) {
const { traceId, requestId } = initTrace(request);
const log = new Logger("my-worker", traceId, requestId);
log.info("Handling request");
},
};withTraceHeaders(headers, traceId, requestId)
Adds x-trace-id and x-request-id headers to an existing headers object. Returns a new Headers instance — does not mutate the input.
Use this when making outbound fetch calls to downstream services to propagate the trace context.
function withTraceHeaders(
headers: Headers | [string, string][] | Record<string, string> | undefined,
traceId: string,
requestId: string,
): Headers;import { initTrace, withTraceHeaders } from "@neutrino-io/logger";
const { traceId, requestId } = initTrace(request);
const response = await fetch("https://registry.svc.nno.app/api/platforms", {
headers: withTraceHeaders(
{ Authorization: `Bearer ${token}` },
traceId,
requestId,
),
});requestLogger(service) — Hono Middleware
Hono middleware that handles the full per-request logging lifecycle. On each request it:
- Calls
initTraceto extract or generatetraceIdandrequestId - Stores both on the Hono context (
c.get('traceId'),c.get('requestId')) - Creates a request-scoped
Loggerand stores it on the context (c.get('logger')) - Logs an incoming request line (
→ request) - Awaits
next(), then logs the outgoing response line (← response) with status and duration
import type { MiddlewareHandler } from "hono";
function requestLogger(service: string): MiddlewareHandler;import { Hono } from "hono";
import { requestLogger } from "@neutrino-io/logger";
const app = new Hono();
app.use("*", requestLogger("iam"));
app.get("/health", (c) => {
const log = c.get("logger") as Logger;
log.info("Health check");
return c.json({ ok: true });
});Context values set by this middleware:
| Key | Type | Description |
|---|---|---|
traceId | string | Distributed trace ID (from header or generated) |
requestId | string | Per-request ID (from header or generated) |
logger | Logger | Request-scoped logger bound to traceId and requestId |
Log output example:
{"timestamp":"2026-03-31T12:00:00.000Z","level":"info","service":"iam","traceId":"abc","requestId":"req-1","message":"→ request","method":"GET","path":"/health"}
{"timestamp":"2026-03-31T12:00:00.050Z","level":"info","service":"iam","traceId":"abc","requestId":"req-1","message":"← response","method":"GET","path":"/health","status":200,"duration":50}