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/debugcall.
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{}: anErrorhas no enumerable fields forJSON.stringify. Report errors withcaptureException().- Only
log,info,warn,erroranddebugare forwarded. - Loggers that write to
process.stdoutdirectly (pino, winston's stream transports) bypassconsoleand aren't captured — calllog.*()from a custom transport of your logger instead.
Built-in logs from integrations
Two integrations send logs of their own:
- HttpErrorsIntegration — an
errorlog per failed outgoing request (5xxor a network failure), withmethod,urlandstatusattributes. - EventLoopBlockIntegration — a
warnlog when synchronous work blocks the event loop, with adurationattribute.
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,warnorerror;environment:— theenvironmentpassed toinit();user:— the identified user;- any attribute, tag or context key —
orderId:1042,service:billing-api.
For example: webhook level:error environment:production.