Blyp Docs

Structured Logs

Use createStructuredLog() when multiple log lines belong to one logical unit of work and you want one final structured payload.

Structured logs emit nothing until .emit() is called. Everything you .set() is held silently and flushed as a single rich event.

Root import

createStructuredLog is a root export:

import { createStructuredLog } from "@blyp/core";

Standalone usage

import { createStructuredLog } from "@blyp/core";

const structuredLog = createStructuredLog("checkout", {
  service: "web-api",
  level: "info",
  timestamp: new Date().toISOString(),
});

structuredLog.set({
  user: { id: 1, plan: "pro" },
  cart: { items: 3, total: 9999 },
  payment: { method: "card", status: "success" },
});

structuredLog.info("payment authorized");
structuredLog.emit({
  status: 200,
  message: "checkout",
});

Nothing is written until you call .emit(). The intermediate info(), warn(), error(), and table() calls accumulate events on the in-memory batch.

Without structured logs - scattered output, hard to correlate:

[INFO] user authenticated
[INFO] item added to cart
[INFO] payment processing
[INFO] order created

With structured logs - one rich event on .emit():

[INFO] checkout
  service=web-api
  user.id=1 user.plan=pro
  cart.items=3 cart.total=9999
  payment.method=card payment.status=success
  status=200

Terminal output on .emit():

[INFO] checkout
  service=web-api
  user.id=1 user.plan=pro
  cart.items=3 cart.total=9999
  payment.method=card payment.status=success
  status=200

Typed fields

If you want the final payload to be strongly typed, pass a generic:

import { createStructuredLog } from "@blyp/core";

const structuredLog = createStructuredLog<{
  message: string;
  level: string;
  timestamp: string;
  hostname?: string;
  port?: number;
}>("test", {
  message: "Hello Elysia",
  level: "info",
  timestamp: new Date().toISOString(),
  hostname: "127.0.0.1",
  port: 3000,
});

set() extends the field type, so later additions are reflected in the returned StructuredLog<TFields> instance.

What happens if you forget .emit()

If a request or workflow completes without calling .emit(), Blyp does not flush the structured payload.

(no structured log output - .emit() was never called)

This is the main failure mode to watch for during development: every set(), info(), warn(), error(), or table() call stays in memory until the final emit happens.

Framework handler behavior

Inside framework handlers, the imported createStructuredLog(...) automatically binds to the active request-scoped logger.

That means you can keep the same root import in standalone code and in framework routes without switching APIs.

Emit options

emit() accepts the fields Blyp uses to finalize the payload:

structuredLog.emit({
  response,
  status: 200,
  level: "success",
  message: "checkout completed",
  error,
});

Use:

Emitted payload shape

The returned payload contains your typed fields plus Blyp metadata:

import type {
  StructuredLog,
  StructuredLogEmitOptions,
  StructuredLogEvent,
  StructuredLogPayload,
} from "@blyp/core";

StructuredLogPayload<TFields> includes:

Production output (NDJSON)

In production, the same structured log emits as a single NDJSON line:

{"level":"info","time":1710000000000,"msg":"checkout","service":"web-api","user":{"id":1,"plan":"pro"},"cart":{"items":3,"total":9999},"payment":{"method":"card","status":"success"},"status":200}

This is what gets written to your log file or forwarded to a connector such as PostHog, Sentry, Better Stack, or OTLP.