---
title: "Structured Logs"
description: "Batch related log events into one emitted payload with typed fields and request-aware metadata."
canonical_url: "https://www.blyp.dev/docs/structured-logs"
markdown_url: "https://www.blyp.dev/docs/structured-logs.md"
last_updated: "2018-10-20"
x_farming_labs_generated_preamble: true
---

# Structured Logs
URL: /docs/structured-logs
LLM index: /llms.txt
Description: Batch related log events into one emitted payload with typed fields and request-aware metadata.

# 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:

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

## Standalone usage

```ts
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:

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

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

```text
[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()`:**

```text
[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:

```ts
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.

```text
(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.

- request metadata such as method, path, status, and duration is merged when you emit
- framework `customProps` are merged into the final payload
- a structured emit suppresses the normal auto `http_request` or `http_error` record for that request
- if you start a request-scoped structured log and then call the root `logger` in the same request, Blyp warns once and ignores the root logger write
- the active request `traceId` is preserved even if default structured fields contain a conflicting `traceId`

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

Convex is the exception: call `logger.createStructuredLog(...)` from `@blyp/core/convex` inside Convex functions. The root `createStructuredLog` import would pull the Node logger graph into the isolate.

See [Request Tracing](/docs/working-with-blyp/request-tracing) for the full correlation model across response headers, client logs, connectors, and database rows.

## Emit options

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

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

Use:

- `response` or `status` to set the final HTTP status
- `error` to attach normalized error details
- `level` and `message` to override the final emitted event summary

## Emitted payload shape

The returned payload contains your typed fields plus Blyp metadata:

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

`StructuredLogPayload<TFields>` includes:

- your initial and `set()` fields
- `groupId`
- `timestamp`
- `level`
- optional `method`, `path`, `status`, and `duration`
- optional `events` collected before emit
- optional normalized `error`

## Production output (NDJSON)

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

```json
{"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.

## Related docs

- [Basic Usage](/docs/basic-usage)
- [Errors](/docs/errors)
- [Server-Side Types](/docs/types/server-side)
- [Utilities & Runtime](/docs/reference)

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
