Skip to content

aquaticcalf/afterlog

Repository files navigation

afterlog

Structured logging for TypeScript. One JSON object per request.

import { afterlog } from "afterlog"

const builder = afterlog.createBuilder({
  http_method: "GET",
  path: "/users/123",
})

builder.set("user_id", "123")

const user = await builder.timing("database", () => db.getUser("123"))

await afterlog.finalize(builder)
// {"request_id":"550e8400-...","trace_id":"a12b34cd-5678-40ef-abcd-...","http_method":"GET",...}

Install

bun
bun add afterlog
npm
npm install afterlog
yarn
yarn add afterlog
pnpm
pnpm add afterlog

Quick Start

Configure once:

import { afterlog, createConsoleAdapter } from "afterlog"

afterlog.configure({
  adapter: createConsoleAdapter(),
})

Use in your routes:

app.get("/users/:id", async (req, res) => {
  const builder = afterlog.createBuilder({
    http_method: req.method,
    path: req.path,
  })

  const user = await builder.timing("db", () => db.getUser(req.params.id))
  builder.set("user_id", user.id)

  await afterlog.finalize(builder)
  res.json(user)
})

What You Get

One JSON object per request with:

  • request_id - UUID unique per request
  • trace_id - UUID shared across services (auto-generated if not provided)
  • timings - how long each operation took
  • error - normalized error info
  • Your custom fields
{
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "trace_id": "a12b34cd-5678-40ef-abcd-1234567890ab",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "http_method": "GET",
  "path": "/users/123",
  "user_id": "123",
  "timings": {
    "db": 45,
    "cache": 5
  }
}

Why Wide Events?

Traditional logging:

[10:30:00] GET /users/123
[10:30:00] Database query: SELECT * FROM users WHERE id=123
[10:30:01] Cache miss
[10:30:02] Response: 200

Wide event logging:

{ "http_method": "GET", "path": "/users/123", "timings": { "db": 1000, "cache": 50 } }
  • Query by any field
  • One write per request
  • Works with tracing

Documentation

Adapters

afterlog doesn't send logs anywhere by default. You write the adapter:

const datadogAdapter = {
  emit: async (event) => {
    await fetch("https://http-intake.logs.datadoghq.com/v1/input", {
      method: "POST",
      headers: { "DD-API-KEY": process.env.DD_API_KEY },
      body: JSON.stringify(event),
    })
  },
}

afterlog.configure({ adapter: datadogAdapter })

Or use the built-in console adapter for development.

Sampling

Don't log everything. Sample by error, latency, or random:

import { errorRule, createLatencyRule } from "afterlog"

afterlog.configure({
  adapter: myAdapter,
  sampling: {
    rules: [
      errorRule, // Always log errors
      createLatencyRule({ threshold_ms: 1000, sample_rate: 1.0 }), // Always log slow requests
    ],
    default_rate: 0.05, // 5% of the rest
  },
})

License

MIT

About

afterlog

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages