Docs/Runtime & Infrastructure/Logging

Runtime & Infrastructure

Logging

Logging — Runtime & Infrastructure documentation for BCP Framework.

Last updated: 2026-08-29T17:13:55

Logging and observability

BCP Framework 0.1.23 added the structured server logger and development request timing output. BCP Framework 0.2.7 complements that logging layer with process-local metrics and health/readiness primitives through bcp/observability.

Use these layers together:

text
bcp/server
  -> structured event/request logs

bcp/observability
  -> counters, gauges, histograms
  -> Prometheus exposition
  -> request metrics middleware
  -> health/readiness checks

See Observability Platform v2 for the 0.2.7 metrics and health APIs.

Server logger

Import logging APIs from the server-only bcp/server entry:

TypeScript
import {
    logger,
} from "bcp/server";

logger.info(
    "Application started",
    {
        service: "catalog",
    }
);

Available levels are:

text
debug
info
warn
error
silent

The default level is info.

Structured fields can contain strings, numbers, booleans, objects, arrays, Error values and bigint values. Circular references are replaced with [Circular] rather than crashing the logger.

Request-scoped logging

Use requestLogger() while handling an API route, loader, guard or form action:

TypeScript
import {
    requestLogger,
} from "bcp/server";

export async function loader() {
    const log =
        await requestLogger({
            feature: "categories",
        });

    log.info(
        "Loading categories"
    );

    return {
        items: [],
    };
}

The returned child logger automatically binds the current request identity:

text
requestId
method
path

requestId uses the same request-scoped value exposed by requestId(): a valid incoming X-Request-Id is reused, otherwise BCP creates a UUID for the request.

Response request ID

When an application wants to return the request identity to a caller, wrap a response with attachRequestId():

TypeScript
import {
    attachRequestId,
    json,
} from "bcp/server";

export async function GET() {
    return attachRequestId(
        json({
            ok: true,
        })
    );
}

The returned response includes:

text
X-Request-Id: <current request id>

This helper preserves the original response status, status text, body and headers.

Child loggers

Bind shared fields once with child():

TypeScript
import {
    logger,
} from "bcp/server";

const dbLog =
    logger.child({
        component: "database",
    });

dbLog.debug(
    "Executing query",
    {
        operation: "users.list",
    }
);

Child bindings are inherited by further child loggers.

Log level

Set the minimum log level with:

Bash
BCP_LOG_LEVEL=debug

Supported values:

text
debug
info
warn
error
silent

Invalid or missing values fall back to info.

Use debug during framework/application diagnostics to see development SSR and module-import timing events.

Log format

Human-readable output is the default:

Bash
BCP_LOG_FORMAT=pretty

Example:

text
[2026-08-27T10:00:00.000Z] [INFO] [bcp] User loaded userId=42

For containers, log collectors and CI systems use JSON lines:

Bash
BCP_LOG_FORMAT=json

Example:

JSON
{"timestamp":"2026-08-27T10:00:00.000Z","level":"info","logger":"bcp","message":"User loaded","userId":42}

Each log call emits exactly one line.

Development HTTP logging

Development request output remains focused on application API traffic. BCP suppresses request-style logs for framework and browser bootstrap traffic such as:

text
/_bcp/*
/api/bcp-styles
page/document requests
public static assets

An application API request is converted into the structured logger format. With pretty output it resembles:

text
[2026-08-27T10:00:00.000Z] [INFO] [bcp-dev] HTTP request event=http.request method=GET path=/api/users status=200 durationMs=1.24

With BCP_LOG_FORMAT=json the same event is emitted as one JSON object containing:

text
event=http.request
method
path
status
durationMs

Existing development Import: ...ms and SSR: ...ms measurements are converted into debug events:

text
module.import
ssr.render

They are therefore visible when BCP_LOG_LEVEL=debug and stay out of normal info output.

Metrics vs logs

Logs and metrics serve different purposes.

Use structured logs when you need per-event context:

text
request ID
operation
error stack
resource identity
diagnostic fields

Use metrics when you need aggregate trends:

text
request count
status distribution
latency buckets
queue depth
worker count

Avoid copying high-cardinality log fields such as request IDs, user IDs or raw paths into metric labels.

The 0.2.7 request metrics middleware follows this rule by using method and status labels by default.

Errors

Pass an Error as a structured field:

TypeScript
try {
    // ...
} catch (error) {
    logger.error(
        "Operation failed",
        {
            error,
        }
    );
}

Error values are serialized with their name, message, stack and cause when available.

Environment example

A development .env.local can use:

text
BCP_LOG_LEVEL=debug
BCP_LOG_FORMAT=pretty

A production/container environment can use:

text
BCP_LOG_LEVEL=info
BCP_LOG_FORMAT=json