This commit is contained in:
Jeffrey C. Ollie 2025-03-22 12:03:01 -05:00
commit 02c1648823
Signed by: jeff
GPG key ID: 6F86035A6D97044E
6 changed files with 322 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/zig-out
/.zig-cache
/result*

34
build.zig Normal file
View file

@ -0,0 +1,34 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// We will also create a module for our other entry point, 'main.zig'.
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "zig_logging_wrapper",
.root_module = exe_mod,
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_unit_tests = b.addTest(.{
.root_module = exe_mod,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}

14
build.zig.zon Normal file
View file

@ -0,0 +1,14 @@
.{
.name = .zig_logging_wrapper,
.version = "0.0.0",
.fingerprint = 0x259c6307f5768bcf, // Changing this has security and trust implications.
.minimum_zig_version = "0.14.0",
.dependencies = .{},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

56
src/PipeReader.zig Normal file
View file

@ -0,0 +1,56 @@
const PipeReader = @This();
const std = @import("std");
const log = std.log.scoped(.pipe_reader);
spawn_failed: *bool,
exe: *std.process.Child,
pipe: enum { stdout, stderr },
sem: std.Thread.Semaphore = .{},
thread: std.Thread = undefined,
pub fn spawn(self: *PipeReader) !void {
self.thread = try std.Thread.spawn(.{}, reader, .{self});
}
pub fn wait(self: *PipeReader) void {
self.sem.wait();
}
pub fn join(self: *PipeReader) void {
self.thread.join();
}
fn reader(self: *PipeReader) void {
const fd: std.fs.File = fd: {
defer self.sem.post();
while (!self.spawn_failed.*) {
switch (self.pipe) {
.stdout => if (self.exe.stdout) |fd| break :fd fd,
.stderr => if (self.exe.stderr) |fd| break :fd fd,
}
std.time.sleep(10 * std.time.ns_per_ms);
}
self.sem.post();
return;
};
const rdr = fd.reader();
while (true) {
var buf: [2048]u8 = undefined;
const line = rdr.readUntilDelimiter(&buf, '\n') catch |err| switch (err) {
error.EndOfStream => return,
error.NotOpenForReading => return,
else => {
log.err("{s}: {}", .{ @tagName(self.pipe), err });
return;
},
};
const ts = std.time.nanoTimestamp();
if (line.len == 0) break;
log.info("{s}: {d} {s}", .{ @tagName(self.pipe), ts, line });
}
}

129
src/UnixSocketReader.zig Normal file
View file

@ -0,0 +1,129 @@
const UnixSocketReader = @This();
const std = @import("std");
const log = std.log.scoped(.socket_reader);
sock: std.posix.socket_t = undefined,
thread: std.Thread = undefined,
pub fn init(self: *UnixSocketReader) !void {
const path: []const u8 = "/tmp/wrapper";
var dir = try std.fs.openDirAbsolute(std.fs.path.dirname(path) orelse return error.BadPath, .{});
defer dir.close();
dir.deleteFile(std.fs.path.basename(path)) catch {};
var addr = std.mem.zeroes(std.posix.sockaddr.un);
addr.family = std.posix.AF.UNIX;
@memcpy(addr.path[0..path.len], path);
self.sock = try std.posix.socket(std.posix.AF.UNIX, std.posix.SOCK.DGRAM, 0);
try std.posix.bind(self.sock, @ptrCast(&addr), @sizeOf(std.posix.sockaddr.un));
}
pub fn spawn(self: *UnixSocketReader) !void {
self.thread = try std.Thread.spawn(.{}, reader, .{self});
self.thread.detach();
}
const Severity = enum(u8) {
EMERG = 0,
ALERT = 1,
CRIT = 2,
ERR = 3,
WARNING = 4,
NOTICE = 5,
INFO = 6,
DEBUG = 7,
};
const Facility = enum(u8) {
KERN = (0 << 3),
USER = (1 << 3),
MAIL = (2 << 3),
DAEMON = (3 << 3),
AUTH = (4 << 3),
SYSLOG = (5 << 3),
LPR = (6 << 3),
NEWS = (7 << 3),
UUCP = (8 << 3),
CRON = (9 << 3),
AUTHPRIV = (10 << 3),
FTP = (11 << 3),
NTP = (12 << 3),
AUDIT = (13 << 3),
ALERT = (14 << 3),
CLOCK = (15 << 3),
LOCAL0 = (16 << 3),
LOCAL1 = (17 << 3),
LOCAL2 = (18 << 3),
LOCAL3 = (19 << 3),
LOCAL4 = (20 << 3),
LOCAL5 = (21 << 3),
LOCAL6 = (22 << 3),
LOCAL7 = (23 << 3),
};
pub fn reader(self: *UnixSocketReader) void {
while (true) {
var buf: [4096]u8 = undefined;
var addr: std.posix.sockaddr = undefined;
var addrlen: std.posix.socklen_t = @sizeOf(std.posix.sockaddr);
const len = std.posix.recvfrom(self.sock, &buf, 0, &addr, &addrlen) catch |err| {
log.info("socket reader: {}", .{err});
return;
};
const line = buf[0..len];
syslog: {
if (line.len < 3) {
log.warn("data too short to be syslog", .{});
break :syslog;
}
const pri_start_char = std.mem.indexOfScalar(u8, line, '<') orelse {
log.warn("syslog: can't find '<'", .{});
break :syslog;
};
if (pri_start_char != 0) {
log.warn("syslog does not start with '<'", .{});
break :syslog;
}
const pri_end_char = std.mem.indexOfScalarPos(u8, line, 1, '>') orelse {
log.warn("syslog: can't find '>'", .{});
break :syslog;
};
const pri = line[pri_start_char + 1 .. pri_end_char];
if (pri.len < 1) {
log.warn("pri is too short", .{});
break :syslog;
}
if (pri.len > 3) {
log.warn("pri is too long", .{});
break :syslog;
}
for (pri) |ch| if (!std.ascii.isDigit(ch)) {
log.warn("invalid digit in pri", .{});
break :syslog;
};
const prival = std.fmt.parseUnsigned(u8, pri, 10) catch |err| {
log.warn("unable to convert pri to an integer: {}", .{err});
break :syslog;
};
if (prival > 191) {
log.warn("prival out of range", .{});
break :syslog;
}
log.info("pri: \'{s}\'", .{pri});
const severity = std.meta.intToEnum(Severity, prival & 0b00000111) catch {
log.warn("unable to convert severity", .{});
break :syslog;
};
const facility = std.meta.intToEnum(Facility, prival & 0b11111000) catch {
log.warn("unable to convert facility", .{});
break :syslog;
};
log.info("severity: {s} facility: {s}", .{ @tagName(severity), @tagName(facility) });
}
log.info("{s}", .{line});
}
}

85
src/main.zig Normal file
View file

@ -0,0 +1,85 @@
const std = @import("std");
const builtin = @import("builtin");
const log = std.log.scoped(.wrapper);
const PipeReader = @import("PipeReader.zig");
const SocketReader = @import("SocketReader.zig");
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
pub fn main() !void {
const alloc, const is_debug = switch (builtin.mode) {
.Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true },
.ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false },
};
defer if (is_debug) {
_ = debug_allocator.deinit();
};
var socket: SocketReader = .{};
try socket.init();
try socket.spawn();
var exe = std.process.Child.init(
&.{
"logger",
"--stderr",
"--unix=/tmp/wrapper",
"hello",
},
alloc,
);
exe.stdin_behavior = .Ignore;
exe.stdout_behavior = .Pipe;
exe.stderr_behavior = .Pipe;
var spawn_failed: bool = false;
var stdout: PipeReader = .{
.spawn_failed = &spawn_failed,
.exe = &exe,
.pipe = .stdout,
};
try stdout.spawn();
defer stdout.join();
var stderr: PipeReader = .{
.spawn_failed = &spawn_failed,
.exe = &exe,
.pipe = .stderr,
};
try stderr.spawn();
defer stderr.join();
exe.spawn() catch |err| {
log.err("err: {}", .{err});
spawn_failed = true;
return;
};
stdout.wait();
stderr.wait();
const rc = try exe.wait();
switch (rc) {
.Exited => |code| {
if (code != 0) {
log.warn("exited with error code {d}", .{code});
}
},
.Signal => |signal| {
log.warn("terminated with signal {}", .{signal});
},
.Stopped => |signal| {
log.warn("stopped with signal {}", .{signal});
},
.Unknown => |code| {
log.warn("unknown error {}", .{code});
},
}
log.info("finished!", .{});
}