Ziya Suzen About

logz: printf debugging across processes

2 November 2026

Some bugs only show up when several processes talk to each other: a Go service, a .NET client, a TypeScript UI, maybe a container or two. Each one logs somewhere different, in its own format, with its own clock. By the time I have lined up three terminal windows and a log file, I have forgotten what I was looking for.

What I want in that moment is printf debugging that works across processes: one call that puts a line in one place, from any language, with no setup. logz is that.

Being an old Perl programmer, I learned to be pragmatic. Sometimes it is easier to just watch what a program does, without any infrastructure, using something that is easy to understand and easy to build. There are sophisticated debuggers today, Visual Studio’s among them, but for this kind of problem the simple approach wins. Brian Kernighan said it in UNIX for Beginners, which shipped with Seventh Edition Unix in 1979:

The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.

Why not OpenTelemetry

For production I use proper observability, and OpenTelemetry is the right tool for traces and metrics. It is not what I reach for when the question is “does this line even run, and in what order relative to that other process”. Adding an SDK, an exporter and a collector to three projects in three languages to answer that is more work than the bug.

logz is deliberately the opposite: no authentication, no TLS, no delivery guarantee, and no packages. It is for a development machine and nothing else.

The protocol

A message is a header line, the message lines, and a terminator, over TCP:

<timestamp> <hostname> <source>
<message line 1>
<message line 2>
__END__

That is the whole thing. A line that is exactly __END__ is sent doubled. The server reads messages, prints them with a colour per source, and appends them to logz.log, so tail -f and grep work straight away.

Because the protocol is that small, a client is one file. There is one each for .NET, Go, Node and Deno, and the idea is that you copy the file into your project instead of adding a dependency:

Logz.Log("API", $"order {id} accepted");
logz.Log("WORKER", fmt.Sprintf("picked up %s", id))

Logging is fire and forget. If the server is not running the messages are dropped, and nothing in the application blocks or throws because of logz.

A few extras

The .NET client grew a few helpers, each because I needed it once:

Then there is traffic that is not my code’s logging at all. Two extras cover that: a mitmproxy addon that records HTTP traffic as a JSONL index plus one file per body, and a small Node script that drives a browser over the Chrome DevTools Protocol and records network, console and page events. Both write to files that are easy to grep and jq.

The limits

It is a debugging aid, not a logging system. The server holds a connection for a short time and expects clients to reconnect, messages can be lost, and anything on the network can write to it. The mitmproxy recordings contain whatever went over the wire, cookies and tokens included, so they stay out of version control.

The code is on GitHub under the MIT license. The clients are meant to be copied and changed, so treat them as a starting point rather than a library.