Structured Logging
Slingshot emits one structured JSON log entry per HTTP request. The shape is designed for ingestion by Datadog, CloudWatch, Loki, Pino-compatible viewers — anything that parses JSON. Every entry carries the request ID, actor, tenant, response time, and active trace, so a single grep across the log stream tells you everything about a request.
How it works
Section titled “How it works”The requestLogger middleware is mounted by the framework when logging.enabled is left at
its default (true). It runs after your handler resolves, derives the log level from the
status code (info for 2xx/3xx, warn for 4xx, error for 5xx and unhandled exceptions),
builds a RequestLogEntry, and hands it to onLog. The default onLog is
console.log(JSON.stringify(entry)) — replace it to ship to your own sink.
/health, /docs, /openapi.json, and /metrics are excluded by default; they are too noisy
to be useful and would drown out the requests you actually care about.
Configure globally
Section titled “Configure globally”import { defineApp } from '@lastshotlabs/slingshot';
export default defineApp({ logging: { enabled: true, level: 'info', excludePaths: ['/health', '/internal', /^\/admin\/_static/], excludeMethods: ['OPTIONS'], onLog: entry => { // entry is a fully-typed RequestLogEntry myLogger.write(entry); }, },});
declare const myLogger: { write(entry: unknown): void };level filters out entries below the threshold — set it to warn in dev to silence chatter,
or to info in prod for full visibility. excludePaths accepts strings (prefix match) or
RegExp.
What you get per entry
Section titled “What you get per entry”Every RequestLogEntry includes:
requestId— the server-generated UUID set by the request ID middlewareuserId,sessionId— resolved from the actor,nullif unauthenticatedrequestTenantId— request-scoped tenant ID (distinct fromactor.tenantId)traceId,spanId— populated when OTel tracing is activeresponseTime— round-trip in millisecondsip,userAgent,method,path,statusCodeerr: { message, stack }— only when the handler threw
That gives you everything you need to reconstruct a request without needing a separate APM.
Diagnostic logging
Section titled “Diagnostic logging”For non-request logs from framework helpers and plugins, use log():
import { log } from '@lastshotlabs/slingshot';
log('billing: refund issued', { invoiceId: '123' });log() writes to console.log only when logging.verbose is true — by default that
follows LOGGING_VERBOSE=true or NODE_ENV !== 'production'. Production stays quiet unless
you opt in.
What’s next
Section titled “What’s next”- Request IDs — the ID that ties every log entry together
- Distributed Tracing —
traceIdandspanIdcorrelation - Audit Logging — durable compliance log on top of structured logs