Commit fbbc6be
Vladimir Varankin
·
2026-05-20 17:45:00 -0400 EDT
parent 30a153e
chore: store logs in XDG_STATE_HOME Previously we were storing logs inside of our socket_dir technically worked but according to the XDG Base Directory Spec it should be stored inside of STATE. > The $XDG_STATE_HOME contains state data that should persist between (application) > restarts, but that is not important or portable enough to the user that it > should be stored in $XDG_DATA_HOME. It may contain: > - actions history (logs, history, recently used files, …) > - current state of the application that can be reused on a restart > (view, layout, open files, undo history, …) Reference: https://specifications.freedesktop.org/basedir/latest/#variables
2 files changed,
+46,
-12
+10,
-2
1@@ -295,7 +295,8 @@ Instead, this tool specifically focuses on session persistence and defers window
2 ## ssh workflow
3
4 ### Try it out quickly
5-If you'd like to try out `zmx` and `ssh` without fiddling your `ssh` config, make sure to pass the `-t` option to `ssh`. Here's an example:
6+
7+If you'd like to try out `zmx` and `ssh` without fiddling your `ssh` config, make sure to pass the `-t` option to `ssh`. Here's an example:
8
9 ```bash
10 ssh -t dev-box zmx attach default
11@@ -378,7 +379,14 @@ This is particularly useful when running `zmx` as a system service with a shared
12
13 ## debugging
14
15-We store global logs for cli commands in `{socket_dir}/logs/zmx.log`. We store session-specific logs in `{socket_dir}/logs/{session_name}.log`. Right now they are enabled by default and cannot be disabled. The idea here is to help with initial development until we reach a stable state.
16+We store global logs for cli commands in `{log_dir}/zmx.log`. We store session-specific logs in `{log_dir}/{session_name}.log`. Right now they are enabled by default and cannot be disabled. The idea here is to help with initial development until we reach a stable state.
17+
18+The log directory is resolved in this order:
19+
20+1. `ZMX_DIR/logs` if `ZMX_DIR` is set
21+1. `XDG_STATE_HOME/zmx/logs` if `XDG_STATE_HOME` is set
22+1. `HOME/.local/state/zmx/logs`
23+1. `TMPDIR/zmx-$UID` (or `/tmp/zmx-$UID`) as a last resort
24
25 ## a smol contract
26
+36,
-10
1@@ -476,7 +476,8 @@ const Cfg = struct {
2
3 pub fn init(alloc: std.mem.Allocator) !Cfg {
4 const socket_dir = try socketDir(alloc);
5- const log_dir = try std.fmt.allocPrint(alloc, "{s}/logs", .{socket_dir});
6+ errdefer alloc.free(socket_dir);
7+ const log_dir = try logDir(alloc);
8 errdefer alloc.free(log_dir);
9
10 const dir_mode = if (std.posix.getenv("ZMX_DIR_MODE")) |m|
11@@ -511,26 +512,51 @@ const Cfg = struct {
12 try std.fmt.allocPrint(alloc, "{s}/zmx", .{xdg_runtime})
13 else
14 try std.fmt.allocPrint(alloc, "{s}/zmx-{d}", .{ tmpdir, uid });
15- errdefer alloc.free(socket_dir);
16
17 return socket_dir;
18 }
19
20+ fn logDir(alloc: std.mem.Allocator) ![]const u8 {
21+ const log_dir = if (posix.getenv("ZMX_DIR")) |zmxdir|
22+ try std.fmt.allocPrint(alloc, "{s}/logs", .{zmxdir})
23+ else if (posix.getenv("XDG_STATE_HOME")) |xdg_state_home|
24+ try std.fmt.allocPrint(alloc, "{s}/zmx/logs", .{xdg_state_home})
25+ else if (posix.getenv("HOME")) |home_dir|
26+ try std.fmt.allocPrint(alloc, "{s}/.local/state/zmx/logs", .{home_dir})
27+ else fallback: {
28+ // This is the last resort: falling back to /tmp/$UID if HOME is unset.
29+ const tmpdir = std.mem.trimRight(u8, posix.getenv("TMPDIR") orelse "/tmp", "/");
30+ const uid = posix.getuid();
31+ break :fallback try std.fmt.allocPrint(alloc, "{s}/zmx-{d}", .{ tmpdir, uid });
32+ };
33+
34+ return log_dir;
35+ }
36+
37 pub fn deinit(self: *Cfg, alloc: std.mem.Allocator) void {
38 if (self.socket_dir.len > 0) alloc.free(self.socket_dir);
39 if (self.log_dir.len > 0) alloc.free(self.log_dir);
40 }
41
42 pub fn mkdir(self: *Cfg) !void {
43- posix.mkdirat(posix.AT.FDCWD, self.socket_dir, @intCast(self.dir_mode)) catch |err| switch (err) {
44- error.PathAlreadyExists => {},
45- else => return err,
46- };
47+ try mkdirAll(self.socket_dir, @intCast(self.dir_mode));
48+ try mkdirAll(self.log_dir, @intCast(self.dir_mode));
49+ }
50
51- posix.mkdirat(posix.AT.FDCWD, self.log_dir, @intCast(self.dir_mode)) catch |err| switch (err) {
52- error.PathAlreadyExists => {},
53- else => return err,
54- };
55+ fn mkdirAll(sub_dir_path: []const u8, mode: posix.mode_t) !void {
56+ var it = try std.fs.path.componentIterator(sub_dir_path);
57+ var component = it.last() orelse return error.BadPathName;
58+ while (true) {
59+ posix.mkdirat(posix.AT.FDCWD, component.path, mode) catch |err| switch (err) {
60+ error.PathAlreadyExists => {},
61+ error.FileNotFound => |e| {
62+ component = it.previous() orelse return e;
63+ continue;
64+ },
65+ else => |e| return e,
66+ };
67+ component = it.next() orelse return;
68+ }
69 }
70 };
71