Released Open Source · Developer Tool

Traceloom

Structured application traces without a heavy framework.

A small library that records what actually happened during one logical process: every event tied to one trace_id, written as JSONL you can read with grep. No agent, no collector, no platform.

PHP v0.4.0 · JavaScript v0.1.0 · Go v0.1.0 · one JSONL event format

logs/2026-07-10.jsonl
# one request, one trace_id, three events {"timestamp":"2026-07-10T10:41:20.112345Z", "trace_id":"9f1a8e7c2d4b4a9f93e2b2b1454f0c0a", "event":"request_start","sequence":1, "elapsed_ms":0.121, "data":{"method":"POST","path":"/orders"}} {"timestamp":"2026-07-10T10:41:20.117381Z", "trace_id":"9f1a8e7c2d4b4a9f93e2b2b1454f0c0a", "event":"auth_success","sequence":2, "elapsed_ms":5.157, "data":{"user_id":42}} {"timestamp":"2026-07-10T10:41:20.289842Z", "trace_id":"9f1a8e7c2d4b4a9f93e2b2b1454f0c0a", "event":"request_end","sequence":3, "elapsed_ms":177.618, "data":{"status":201}}

Why it exists

Logs show isolated messages. A trace shows a story.

Traditional application logs give you lines. Each one is true, and none of them tells you what the request actually did. Traceloom connects related events into a readable execution timeline for one logical process: a request, a job, a webhook, a script.

The niche is narrow on purpose. A small PHP API, a Node worker or a Go job does not need a distributed tracing platform: agents, collectors and dashboards solve problems it does not have. What it does need is the answer to one question: what happened inside the request that broke at 10:41? Traceloom does exactly that, and nothing else.

Reconstructing that answer from ordinary logs means grepping for a user ID, guessing which lines belong together and hoping nothing interleaved. Traceloom gives those lines a shared identity instead.

Boundaries

What Traceloom is not

It is not a replacement for Monolog or Pino: use a general-purpose logger when you need levels, transports and integrations.

It is not a replacement for OpenTelemetry: use that when you need industry-standard distributed tracing.

It is not an observability platform: use one when you need aggregation, dashboards, alerting or cross-service querying.

Core concept

One trace_id, one timeline

The whole library is built on a single idea, and everything else is a consequence of it.

You start a trace at the beginning of a logical process. Every event recorded through it carries the same trace_id, so the events of one request are findable as a group instead of scattered through a file shared with everything else running at that moment.

Each event also carries a sequence number and elapsed_ms measured from the start of the trace on a monotonic clock. Order survives even when timestamps collide; duration survives an NTP correction. A gap in sequence is deliberate too: it is how a reader learns the timeline lost an event.

The output is JSONL: one JSON object per line, appended to a file. No daemon to run, no port to open, no schema registry. You can grep it, pipe it through jq, or use the bundled eventtrace CLI to print a trace as a timeline.

Incoming request
tracer.start(): a fresh trace_id; an inbound ID is stored as parent_trace_id
trace.event('request_start'): sequence 1 · elapsed_ms 0.121
trace.event('auth_success'): sequence 2 · elapsed_ms 5.157
trace.event(...): sequence N · payload sanitized, secrets masked
JSONL file: logs/2026-07-10.jsonl, rotated into shards
grep by trace_id: the whole request, in order, in one place

Language versions

Three implementations. All released.

PHP came first and is the most mature. JavaScript and Go have both reached v0.1.0 and write the same event format; they are published, installable and usable today.

Install · Composer
composer require golovanov/traceloom

What's specific to PHP

The original implementation and the most mature at v0.4.0. Its sanitizer is the reference for the others: string, record, array, node, depth and key size limits, with a digest appended to truncated keys so two long keys can never collapse into one. Writes are synchronous; concurrent processes coordinate with flock. Ships the eventtrace CLI via vendor/bin.

golovanov-dev/traceloom

PHP · quick start
use Golovanov\Traceloom\Tracer;

$tracer = Tracer::fromDirectory(__DIR__ . '/logs');

$trace = $tracer->start();

$trace->event('request_start', [
    'method' => 'POST',
    'path' => '/orders',
]);

$trace->event('auth_success', [
    'user_id' => 42,
]);

$trace->event('request_end', [
    'status' => 201,
]);

One format, three languages

A polyglot system, one readable timeline

The three implementations are not three products that happen to share a name. They write the same JSONL schema and apply the same sanitizer rules, so a PHP API, a Node worker and a Go job produce timelines a human reads the same way.

The shared event schema
{
  "timestamp":  "2026-07-10T10:41:20.112345Z",
  "trace_id":   "9f1a8e7c2d4b4a9f93e2b2b1454f0c0a",
  "event":      "request_start",
  "sequence":   1,
  "elapsed_ms": 0.121,
  "data":       { "method": "POST", "path": "/orders" }
}

An incoming trace ID is not taken at face value by default: all three implementations record it as parent_trace_id and start a trace with its own ID, so a request from outside cannot write into your timeline. Behind a gateway that sets the header itself, you enable trust with one setting. This is data hygiene, nothing more.

One eventtrace show <trace-id> command works on the files of any implementation and prints the same timeline, whichever language wrote them.

Write coordination is simple: PHP and Go can append to one directory, and a Node writer keeps a directory of its own. All three read each other's files.

Design principles

The rules the library is held to

Every one of these has said no to a feature at some point. That is what makes them principles rather than adjectives.

01

Low dependency footprint

The Go and JavaScript packages have zero runtime dependencies; PHP pulls in nothing beyond the language. Adding tracing should not reshape your dependency tree.

02

Predictable API

Start a trace, record events, close. Three concepts. The same three in every language, adjusted only where the language demands it.

03

Structured context

Events carry typed payloads, not interpolated strings. A machine can filter them; a human can still read them.

04

Framework independence

No PSR-7, Laravel, Symfony or Express adapter in the core. The library does not care what routed the request.

05

Practical diagnostics

The output is a file you can grep on a server at 2am, not a format that requires a running platform to mean anything.

06

Safe production behaviour

Fail-safe by default: a tracing failure never breaks the host application. Secrets are masked, payloads bounded, and losses counted rather than swallowed. One limit to know: masking matches key names, not values, so a secret stored under an innocuous key is not detected.

07

Easy integration

One line to start, one line per event. Files are created with restrictive modes, rotated by size and expired by retention without a cron job.

My role

What I own on this project

Product definition, architecture, cross-implementation contract decisions, AI-agent coordination, technical review and release decisions.

I defined the product, set the shared JSONL contract across the PHP, Node.js and Go implementations, and directed AI-assisted implementation, while retaining responsibility for architecture, reviews and releases.

Keeping one event format aligned across three languages is the actual engineering work here. The JavaScript and Go implementations found security gaps in the sanitizer that the PHP version then had to close, which is why PHP is at v0.4.0 rather than v0.1.0.

Current status

Current state of the project

All three packages are published and installable. PHP is the furthest along; JavaScript and Go have just reached their first release. Each implementation ships unit, integration, CLI and multi-process concurrency tests, with CI on GitHub Actions.

PHP · Released JavaScript · Released Go · Released

Roadmap

What shipped, and what I'm not promising

Released means published and installable. Everything below that line is an intention.

  1. Shipped · PHP v0.1.0 → v0.2.0

    The PHP core

    Tracer and Trace, trace-ID generation and continuation, JSONL writer with date-based files and size-based shard rotation, flock coordination, sensitive-key masking, fail-safe error handling and the eventtrace show CLI.

  2. Shipped · JavaScript v0.1.0 · Go v0.1.0

    Two more languages, one format

    The Node.js package (ESM + CJS + types, batched async writes, bounded queue with an overflow policy) and the Go module (kernel file locks on Unix and Windows, context-aware events, reflection-based payload normalization), both writing the PHP wire format.

  3. Shipped · PHP v0.3.0 → v0.4.0

    The contract closes the gaps

    Building the JS and Go implementations exposed real holes in the PHP sanitizer. v0.3.0 aligned masking, marker escaping and defaults across all three; v0.4.0 added maxKeyBytes: an unbounded key was the last way a single hostile field could degrade an entire event.

  4. In progress

    Toward a stable v1.0

    Stabilizing the API across the three implementations so the shared contract can be frozen at v1.0. No new features and no dates promised: the work is letting the interfaces settle and hold.

  5. Considered · not committed

    Beyond the MVP core

    A richer CLI (tail, event search) and framework adapters for PSR-7, Laravel, Symfony or Express are deliberately outside the core today. If they land, they land as separate packages: the core stays small.

Install it, trace one endpoint, grep the file.

That is the whole evaluation. If the timeline it prints does not tell you something your logs weren't telling you, Traceloom is not the right tool for your system, and I would rather you found that out in ten minutes.