No description
  • Zig 84.8%
  • Nix 15.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jeffrey C. Ollie e73dd4920d
All checks were successful
test / docs (push) Successful in 6m49s
test / test (push) Successful in 6m43s
test / vm (push) Successful in 8m55s
Push what the workflow builds to the niks3 cache
Every job builds with Nix, so each gets an OIDC token and ends by pushing
to the niks3 cache from main, as the template does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CTNofVkpYkzrGvXFroKDxf
2026-10-05 20:04:59 -05:00
.forgejo/workflows Push what the workflow builds to the niks3 cache 2026-10-05 20:04:59 -05:00
examples Date the copyright from 2024 and drop the duplicate LICENSE 2026-09-13 21:17:20 -05:00
LICENSES Make the project REUSE compliant 2026-09-13 20:19:31 -05:00
src Build with Zig 0.17.0 2026-10-05 19:58:05 -05:00
tests Date the copyright from 2024 and drop the duplicate LICENSE 2026-09-13 21:17:20 -05:00
tools Date the copyright from 2024 and drop the duplicate LICENSE 2026-09-13 21:17:20 -05:00
.gitignore Date the copyright from 2024 and drop the duplicate LICENSE 2026-09-13 21:17:20 -05:00
build.zig Build with Zig 0.17.0 2026-10-05 19:58:05 -05:00
build.zig.zon Build with Zig 0.17.0 2026-10-05 19:58:05 -05:00
flake.lock Build with Zig 0.17.0 2026-10-05 19:58:05 -05:00
flake.nix Build with Zig 0.17.0 2026-10-05 19:58:05 -05:00
package.nix Build with Zig 0.17.0 2026-10-05 19:58:05 -05:00
README.md Build with Zig 0.17.0 2026-10-05 19:58:05 -05:00
REUSE.toml Date the copyright from 2024 and drop the duplicate LICENSE 2026-09-13 21:17:20 -05:00
typos.toml Date the copyright from 2024 and drop the duplicate LICENSE 2026-09-13 21:17:20 -05:00

journalz

A Zig library for writing structured log messages directly to the systemd journal over its native datagram socket at /run/systemd/journal/socket.

Talking to the socket directly means a message carries its own fields rather than being flattened into a line of text. Every message is built up in a memfd, sealed against further writes, and handed to the journal as an SCM_RIGHTS file descriptor over sendmsg, so there is no size limit to work around and the journal reads the payload the sender can no longer change.

Sending is done through a std.Io, which the caller supplies, so the library names no I/O implementation of its own.

The API documentation is generated from the doc comments, which carry most of the explanation, and is published at https://jeff.jcollie.page/journalz/. zig build docs-serve reads the same thing locally -- it has to be served rather than opened, since the viewer fetches its sources at runtime.

const journalz = @import("journalz");

const logger: journalz.Logger = try .init(io, "myprogram", .DAEMON);
defer logger.deinit();

// A formatted MESSAGE field, with failures swallowed.
logger.info("listening on port {d}", .{port});

// Or build the fields yourself.
const msg = try logger.message(.INFO);
errdefer msg.cancel();

var buf: [1024]u8 = undefined;
var w = msg.writer(&buf);
try w.writeAll("MESSAGE", "connection accepted");
try w.print("REMOTE_ADDR", "{f}", .{addr});
try w.writeSrc(@src());
try w.flush();

try msg.send();

Routing std.log to the journal

examples/std_log.zig replaces std.Options.logFn, so that everything the program and its libraries already log through std.log lands in the journal. The level becomes a real syslog priority and the scope becomes a field, which means journald indexes both:

journalctl --identifier=journalz-example --priority=warning
journalctl --identifier=journalz-example LOG_SCOPE=database

rather than leaving a reader to grep for warning: and (database) in a line of text. The example is built by zig build and run against a real journald by the NixOS check, so it cannot quietly stop working.

Where this lives

The canonical repository is on Forgejo, with mirrors on Tangled and Radicle:

git clone https://git.jcollie.dev/jeff/journalz.git

Radicle

The repository is also on Radicle, where its Repository ID is the only way to find it:

rad:z4LAwD5xmrZ9WB8HmbV65Q2PEmsDX
rad clone rad:z4LAwD5xmrZ9WB8HmbV65Q2PEmsDX

Building

The build needs nothing beyond Zig 0.17.0; the flake's devshell provides it.

nix develop -c zig build test
nix develop -c zig build check

For Zig 0.16.0, use the zig-0.16 branch, which holds the last of journalz to build with it:

git clone -b zig-0.16 https://git.jcollie.dev/jeff/journalz.git

or, as a dependency:

zig fetch --save https://git.jcollie.dev/jeff/journalz/archive/zig-0.16.tar.gz

The v0.1.0 tag also predates the move to 0.17 and builds with 0.16.0.

Testing

The unit tests talk to a fake journald of their own -- a Unix datagram socket that receives the message, pulls the sealed memfd out of the ancillary data and decodes the wire format back into fields. So they assert on what actually crossed the socket, and they run anywhere, including in a Nix build sandbox with no systemd present.

What they cannot show is that systemd-journald agrees, since the decoder they check against was written from the same understanding as the encoder. That is what the NixOS virtual machine check is for: it boots a machine with a real journald, runs tests/e2e.zig against it, and reads the entries back out of journalctl -- the priorities, the caller's own indexed fields, a value with newlines in it, and a 256 KiB field that no datagram could have carried.

nix flake check                             # everything, virtual machine included
nix build .#checks.x86_64-linux.journald    # just the journald machine

License

MIT. See LICENSE.