Skip to content

Overview ​

A log is one line of text with a level and, optionally, structured attributes — the key/value data you'd otherwise interpolate into the message. Logs show up on the project's Logs page, searchable by message, level and attribute.

Two ways to send them:

  • Explicitly, with log.info() / warn() / error() / ... — recommended, since you choose what's worth shipping and can attach attributes.
  • Automatically, with ConsoleIntegration, which forwards every console.log/info/warn/error/debug call.

Logs aren't errors: log.error() records a line at the error level but doesn't create an issue. To report an exception with a stack trace, use captureException() (see Capturing Errors).

Sending logs ​

log has one method per level — log, debug, info, warn, error — each taking a message and an optional attributes object:

ts
import { log } from '@buglapse/node'

log.info('Server started', { port: 3000 })
log.warn('Payment retry', { orderId: 1042, attempt: 2 })
log.error('Webhook rejected', { provider: 'stripe', reason: 'bad_signature' })

Calls made before init() are queued and sent once the client exists.

Every log is batched by the transport (see Transports) and stamped with the current tags and context — global ones and, inside a request or withScope(), that scope's (see Tags & Context).

Attributes ​

Keep the message a fixed string and put the variable parts in attributes:

ts
// Good — one message, searchable fields
log.info('Order placed', { orderId: order.id, total: order.total, currency: 'EUR' })

// Avoid — every call is a different message, nothing to filter on
log.info(`Order ${order.id} placed for ${order.total} EUR`)

Each top-level attribute becomes a search filter on the dashboard (orderId:1042). Prefer flat objects with string, number or boolean values — nested objects are kept and shown, but aren't offered as filters.

Attributes describe one log line; tags and context describe everything captured in their scope. Use attributes for "what happened in this call", tags/context for "who/where".

Forwarding console output ​

If your code (or a library) already logs through console, turn on ConsoleIntegration:

ts
import { init } from '@buglapse/node'
import { consoleIntegration } from '@buglapse/node/integrations'

init({
    dsn: process.env.BUGLAPSE_DSN,
    integrations: [consoleIntegration()],
})

The original call still prints to stdout/stderr, then it's sent as a log of the same level. A trailing plain object becomes the attributes; everything before it is joined into the message:

ts
console.warn('Slow query', { table: 'orders', ms: 1840 })
// → warn log "Slow query", attributes { table: "orders", ms: 1840 }

Things to watch:

  • Every library in the process writes to the same console — enabling this can send a lot. Watch the log quota after turning it on.
  • console.error(error) becomes the message {}: an Error has no enumerable fields for JSON.stringify. Report errors with captureException().
  • Only log, info, warn, error and debug are forwarded.
  • Loggers that write to process.stdout directly (pino, winston's stream transports) bypass console and aren't captured — call log.*() from a custom transport of your logger instead.

Built-in logs from integrations ​

Two integrations send logs of their own:

  • HttpErrorsIntegration — an error log per failed outgoing request (5xx or a network failure), with method, url and status attributes.
  • EventLoopBlockIntegration — a warn log when synchronous work blocks the event loop, with a duration attribute.

Linking logs to traces ​

A log captured while a span is active — anywhere inside a request handled by HttpServerIntegration, or inside your own asyncSpan() — is stamped with trace.id and span.id, and the dashboard links it to that trace. A log from outside any span (at startup, in a top-level timer) isn't linked to a trace.

Dropping or scrubbing logs ​

Logs go through the same preprocessors as every other event, so you can drop or rewrite them before they leave the process:

ts
import { addPreprocessor } from '@buglapse/node'

addPreprocessor((event) => {
    if (event.type !== 'log') return event

    if (event.payload.level === 'debug' && process.env.NODE_ENV === 'production') return null

    delete event.payload.attributes?.email

    return event
})

Filtering, sampling and quota ​

On the server side, logs go through the project's Settings → Filters like every other event: disabled ingestion, IP / environment / release discard lists. The Logs sample rate there keeps a random share of logs.

Each stored log counts against the organization's monthly log quota and the overall monthly event quota. Once either is reached, further logs are rejected until the next month.

Viewing on the dashboard ​

Logs in the project sidebar lists the newest logs first, with level, message and time; open one to see its attributes, tags and context, and links to the user and trace. The search bar matches free text against the message and supports filters (full syntax: Search):

  • level: — log, debug, info, warn or error;
  • environment: — the environment passed to init();
  • user: — the identified user;
  • any attribute, tag or context key — orderId:1042, service:billing-api.

For example: webhook level:error environment:production.