Skip to content

Getting started

This guide walks through adding a named log stream to a Layeron app.

You will create a stream, register it with the app, emit records from a route, and configure sampling and redaction for production-friendly output.

Before you begin, make sure you have:

  • A Layeron backend app.
  • @layeron/core and @layeron/modules installed.
  • At least one route where you want to emit logs.

Import log from @layeron/modules.

Terminal window
import { log } from "@layeron/modules"

log creates a Layeron module. You register that module with app.use(...), the same way you register database and other backend capabilities.

Create a stream with a stable platform namespace/name identity.

Terminal window
const appLog = log({
name: "app",
namespace: "api",
})

Choose a name that describes the stream. Use Namespaces for namespace defaults and naming rules.

For example:

NamespaceNameUse
apiappGeneral API route logs.
apidatabaseQuery and persistence logs.
webhooksstripeStripe webhook handling logs.
adminauthAdmin authentication logs.

This identity model makes future filtering and retention policies predictable.

Add the stream to your backend app.

Terminal window
app.use(appLog)

Layeron records the stream in the app specification. During compilation, the stream contributes a Log Product Worker and observability intent to the Worker target. During runtime, route code sends records to that Product Worker through WorkerEntrypoint RPC.

Use the stream inside route handlers.

Terminal window
app.get("/api/health", (request) => {
appLog.info("health checked", {
path: new URL(request.url).pathname,
})
return { ok: true }
})

Use log levels to express severity:

Terminal window
appLog.debug("cache lookup", { key })
appLog.info("post created", { postId })
appLog.warn("retry scheduled", { attempt })
appLog.error("delivery failed", { eventId })
appLog.fatal("startup failed")

Use capture(...) when you catch an error and want it recorded as an error record.

Terminal window
app.post("/api/posts", async (request) => {
try {
const body = await request.json()
return Response.json({ body })
} catch (error) {
appLog.capture(error, {
route: "/api/posts",
})
return Response.json({ error: "invalid_json" }, { status: 400 })
}
})

Captured errors are emitted through the same stream identity.

Sampling controls how many records are emitted.

Terminal window
const appLog = log({
name: "app",
namespace: "api",
sampling: {
success: 0.25,
error: 1,
},
})

Use success for normal log records. Use error for error and fatal records. Both values are numbers from 0 to 1.

Use workerLogs.headSamplingRate when you want Cloudflare Workers Logs to sample whole Worker invocations before log records are collected.

Terminal window
const appLog = log({
name: "app",
namespace: "api",
workerLogs: {
headSamplingRate: 0.5,
},
})

Layeron sampling applies to records in the named stream. Workers Logs head sampling applies to the whole Worker invocation.

Redaction removes sensitive values before a record is emitted.

Terminal window
const appLog = log({
name: "app",
namespace: "api",
redaction: {
fields: ["authorization", "cookie", "token"],
headers: ["authorization", "cookie"],
},
})

Use redaction for values that should never appear in logs, such as tokens, cookies, secrets, and authorization headers.

You can also keep shared redaction rules in Policy and pass them to the log stream:

Terminal window
import { log, policy } from "@layeron/modules"
const appPolicy = policy({
name: "app",
redaction: [{
id: "sensitive-log-fields",
target: "log",
fields: ["authorization", "token"],
headers: ["authorization", "cookie"],
}],
})
const appLog = log({
name: "app",
namespace: "api",
redaction: appPolicy.redactionPolicy(["sensitive-log-fields"]),
})

The log stream receives inline fields and headers for runtime redaction. The compiled app also keeps the Policy reference for plans, deployment state, and dashboard views.

Workers Logs are useful for short-term viewing. Add archive retention when you need records to remain queryable through Layeron:

Terminal window
const appLog = log({
name: "app",
namespace: "api",
retention: {
mode: "archive",
days: 30,
},
})

After deployment, read retained records with the CLI:

Terminal window
layer logs list . --env production --namespace api --name app --since 1h

In the Cloudflare runtime target, the Log Product Worker emits structured records through Workers Logs and writes retained records through Layeron Storage when archive retention is enabled. Storage owns the R2 bucket behind that archive.