Core - v1.3.1
    Preparing search index...

    Class Logger

    Structured, level-classified logging — and nothing else.

    Each line is one JSON object emitted through the matching console.* method, so the runtime's log collector records the native level and keeps the fields searchable. Nothing here aggregates — that is TelemetrySink, a separate capability, because a sink samples at volume and is the wrong place for lines that have to be read back verbatim.

    The first argument is an event name — a stable, lowercase, dotted identifier such as notification.sent or external_api.error — emitted under the event key. A collector that indexes JSON fields turns that key into a column, so event = notification.failed becomes a filter rather than a substring search, and the count of one event over time becomes a series. A prose sentence can be neither: 'Failed to send notification for user 7' is a different string on every line it is written.

    That is why the third argument exists rather than replacing the first. A message is written for whoever is reading the stream, and it is the only thing most log viewers show by default. Omit it and the event name stands in, so the column is never empty; supply it and the line says something in English without anyone opening it:

    log.warn('external_api.error', { provider, status }, `Telegram rejected the send (${status})`)
    

    Anything that varies belongs in the payload, where it stays structured and searchable on its own — never interpolated into the event name.

    Context and payload are flattened onto the line, not nested under a wrapper, so a field is indexed as status rather than data.status.

    Context passes through an allow-list first; see LogFields. Payload never does — it was named at the call site for this one call, and dropping it there would discard the answer somebody logged the line to get.

    service names the deployment, from SERVICE_NAME in the environment or, failing that, logging.service on the configuration surface. It is context, so it reaches the line only while the allow-list admits it — which the kernel default does.

    Some runtimes already record it: Cloudflare stamps the script name on every log record before it is stored, so a Worker that never leaves that console gains nothing from a second copy and can drop the field. A deployment whose lines travel — exported to a file, shipped through Logpush, read on a runtime with no such column — should keep it, because a record that only makes sense inside one vendor's console is not a record.

    That is the trade the allow-list exists to let a deployment make, rather than this class making it for every consumer. The value comes from the environment rather than a constant compiled into the bundle, so whichever way it is decided the name cannot drift from the deployment it describes.

    service answers which service; a line that needs to say which part of it ran names a component in the context.

    The threshold comes from the surface registered by configure, and LOG_LEVEL in the environment overrides it per deployment, so staging can run at debug without a code change.

    Retention is the runtime's business, not this class's — it writes lines and stops there. Whether they are kept, and for how long, is a deployment setting wherever the process runs.

    const log = Logger.fromEnv(env, { component: 'ArticleService' })

    log.info('article.created', { id })
    log.info('article.created', { id }, `Article ${id} created`)

    Bayu Dwiyan Satria

    1.2.1

    1.0.0

    Index
    context: LogContext

    Fields repeated on every line.

    fields: LogFields

    Which of those fields the deployment actually wants ingested.

    threshold: number

    Minimum weight a line must carry to be emitted.

    • Assembles the object that becomes the line.

      Parameters

      • level: LogLevel

        Severity of the line.

      • event: string

        What happened, as a dotted event name.

      • Optionaldata: Record<string, unknown>

        Structured detail.

      • Optionalmessage: string

        A sentence for someone reading the stream.

      Returns Record<string, unknown>

      The line, ready to serialise.

      The three reserved keys are written first so a raw line reads level-then-event-then-message, and re-asserted on every merge so nothing can displace them. time sits behind the allow-list like any other field: a runtime that stamps its own ingestion timestamp does not need a second one, and it is computed here rather than carried in the context so it records when the line was written and not when the logger was built.

    • Emits a debug line.

      Parameters

      • event: string

        What happened, as a dotted event name.

      • Optionaldata: Record<string, unknown>

        Structured detail.

      • Optionalmessage: string

        A sentence for someone reading the stream. Defaults to the event name.

      Returns void

    • Writes one line through the console method matching its level.

      Parameters

      • level: LogLevel

        Severity of the line.

      • event: string

        What happened, as a dotted event name.

      • Optionaldata: Record<string, unknown>

        Structured detail.

      • Optionalmessage: string

        A sentence for someone reading the stream.

      Returns void

      Context and payload are both flattened onto the line rather than nested, so a collector indexes status rather than data.status and a dashboard column reads as a field name instead of a path.

      They are not treated alike. Context is ambient — stamped by middleware for the span of a request — and passes through the allow-list, because a deployment should be able to decide it does not want the point of presence on every line. Payload was typed out at the call site for this one call, so it is emitted whole: filtering it would silently discard the answer someone went to the trouble of logging.

    • Emits an error line.

      Parameters

      • event: string

        What happened, as a dotted event name.

      • Optionaldata: Record<string, unknown>

        Structured detail.

      • Optionalmessage: string

        A sentence for someone reading the stream. Defaults to the event name.

      Returns void

    • Builds a logger for the current environment.

      Parameters

      • env: { LOG_FIELDS?: string; LOG_LEVEL?: string; SERVICE_NAME?: string }

        Anything carrying an optional LOG_LEVEL, SERVICE_NAME and LOG_FIELDS. Tolerates null.

      • context: LogContext = {}

        Fields to repeat on every line.

      Returns Logger

      A logger honouring the configured (or LOG_LEVEL) threshold, and emitting the context fields the deployment declared.

      The parameter is structural rather than a named environment type. All this needs is a possible LOG_LEVEL, SERVICE_NAME and LOG_FIELDS, so asking for exactly that keeps the kernel free of any platform's environment shape — a runtime's Env object, a process.env, or a bare object literal all satisfy it.

      The service name is read from the environment first and the configuration surface second. The order is the point: a name in the environment sits beside the deployment's own name — in wrangler.json, two lines below "name" — where a rename that misses it shows up in the same diff. A name compiled into the bundle from config/ sits in a different file that a rename does not touch, which is how a Worker came to spend production calling itself cloudflare-boilerplate. The configuration value remains the fallback for runtimes with no environment to read.

      With neither set the line still carries a service, reading unknown. A missing name is a deployment defect, and an omitted key would make it an invisible one — service = unknown is a query that finds every misconfigured deployment.

    • Emits an info line.

      Parameters

      • event: string

        What happened, as a dotted event name.

      • Optionaldata: Record<string, unknown>

        Structured detail.

      • Optionalmessage: string

        A sentence for someone reading the stream. Defaults to the event name.

      Returns void

    • Parses a level name, ignoring anything unrecognised.

      Parameters

      • Optionalvalue: string

        The candidate level name.

      Returns LogLevel

      The level, or null when the value is not one.

    • Makes a data payload serialisable, and safe to ship.

      Parameters

      • data: Record<string, unknown>

        The payload to convert.

      Returns Record<string, unknown>

      The payload scrubbed of secrets, with errors expanded to message and stack.

      Two passes for two different problems. redact runs first, because a credential must not survive into the line whatever shape it arrived in; then Error expansion, which JSON.stringify would otherwise flatten to {}, losing the only useful part of an error line.

      The order is load-bearing in one direction only: redaction returns Error values untouched precisely so this pass can still recognise them.

    • Emits a warn line.

      Parameters

      • event: string

        What happened, as a dotted event name.

      • Optionaldata: Record<string, unknown>

        Structured detail.

      • Optionalmessage: string

        A sentence for someone reading the stream. Defaults to the event name.

      Returns void