Skip to content

Overview ​

"Metrics" covers two separate things in Buglapse:

  • Span metrics — a named number attached to a span (order.total, rows.imported, payload.bytes), charted on the trace page. This is what you'll use most on a server.
  • Custom hits — counted occurrences ("signup", "payment-confirmed") on the project's Metrics page, sent with hit(name).

Span metrics ​

The callback of span() / asyncSpan() receives a handle with metric(name, value):

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

await asyncSpan('import-csv', async (span) => {
    const rows = await parseCsv(file)
    span.metric('import.rows', rows.length)
    span.metric('import.bytes', file.size)

    const skipped = await insertRows(rows)
    span.metric('import.skipped', skipped)
})

startInactiveSpan()'s return value has the same method — record metrics any time before end():

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

const span = startInactiveSpan('render-pdf', { op: 'pdf' })
const pdf = await renderPdf(invoice)
span.metric('pdf.pages', pdf.pageCount)
span.end()

Rules:

  • Values are numbers. For strings, booleans or objects use span.setData(key, value).
  • Last value wins. A second metric() with the same name on the same span overwrites the first. Accumulate in a variable and record once if you need a total.
  • Only while the span is open. Anything recorded after the span has finished is not sent.
  • Per span. A child span doesn't inherit its parent's metrics. Reuse the same name on different spans to compare them across a trace.
  • Name them area.measurement, with the unit when it isn't obvious: queue.wait_ms, payload.kb.

Every span already records its own duration, so wrap a step in its own span to time it; use a metric for what the duration doesn't tell you — sizes, counts, or a timing inside a longer span:

ts
await asyncSpan('handle-webhook', async (span) => {
    span.metric('webhook.delay_ms', Date.now() - Date.parse(payload.sentAt))
    await processWebhook(payload)
})

Before init() has run, spans still run your code with a no-op handle, so metric() calls are ignored rather than throwing.

On the trace page, the side panel charts every metric name used in the trace — one small chart per name across the trace's spans — and shows a selected span's metrics as name/value pairs. See Tracing.

Span metrics have their own monthly quota, counted per metric entry: a span with three metric names counts as three; spans without metrics don't use it. Once it's exhausted, spans carrying metrics are rejected at ingestion.

Custom hits from the server ​

hit(name) counts an occurrence of something on the Metrics page — useful for events that are only known for sure on the server, like a confirmed payment:

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

hit('payment-confirmed')

A server has no visit of its own. To have the hit counted in the visitor's browser session (so it shows up under the same Visits and Visitors as their page views), pass the browser SDK's session id along with the request and hand it to hit():

ts
// browser
import { getSessionId } from '@buglapse/browser'

await fetch('/api/checkout', { method: 'POST', headers: { 'X-Buglapse-Session': getSessionId() } })
ts
// server
hit('payment-confirmed', request.get('X-Buglapse-Session') || undefined)

Without a session id the hit is still counted under its name, just not tied to any visit.

Keep the set of names small and fixed — each distinct name becomes its own row, so don't build names from ids (hit('purchase'), not hit('purchase-' + orderId)). Hits carry no value; for amounts, use a log with attributes or a span metric. Each stored hit counts as one event toward the monthly event quota. See Events → Hit for the payload.