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
| ... | ... | @@ -295,7 +295,8 @@ Instead, this tool specifically focuses on session persistence and defers window | |
| 295 | 295 | ## ssh workflow | |
| 296 | 296 | ||
| 297 | 297 | ### Try it out quickly | |
| 298 | - | 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: | |
| 298 | + | ||
| 299 | + | 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: | |
| 299 | 300 | ||
| 300 | 301 | ```bash | |
| 301 | 302 | ssh -t dev-box zmx attach default |
| ... | ... | @@ -378,7 +379,14 @@ This is particularly useful when running `zmx` as a system service with a shared | |
| 378 | 379 | ||
| 379 | 380 | ## debugging | |
| 380 | 381 | ||
| 381 | - | 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. | |
| 382 | + | 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. | |
| 383 | + | ||
| 384 | + | The log directory is resolved in this order: | |
| 385 | + | ||
| 386 | + | 1. `ZMX_DIR/logs` if `ZMX_DIR` is set | |
| 387 | + | 1. `XDG_STATE_HOME/zmx/logs` if `XDG_STATE_HOME` is set | |
| 388 | + | 1. `HOME/.local/state/zmx/logs` | |
| 389 | + | 1. `TMPDIR/zmx-$UID` (or `/tmp/zmx-$UID`) as a last resort | |
| 382 | 390 | ||
| 383 | 391 | ## a smol contract | |
| 384 | 392 |
+36,
-10
| ... | ... | @@ -476,7 +476,8 @@ const Cfg = struct { | |
| 476 | 476 | ||
| 477 | 477 | pub fn init(alloc: std.mem.Allocator) !Cfg { | |
| 478 | 478 | const socket_dir = try socketDir(alloc); | |
| 479 | - | const log_dir = try std.fmt.allocPrint(alloc, "{s}/logs", .{socket_dir}); | |
| 479 | + | errdefer alloc.free(socket_dir); | |
| 480 | + | const log_dir = try logDir(alloc); | |
| 480 | 481 | errdefer alloc.free(log_dir); | |
| 481 | 482 | ||
| 482 | 483 | const dir_mode = if (std.posix.getenv("ZMX_DIR_MODE")) |m| |
| ... | ... | @@ -511,26 +512,51 @@ const Cfg = struct { | |
| 511 | 512 | try std.fmt.allocPrint(alloc, "{s}/zmx", .{xdg_runtime}) | |
| 512 | 513 | else | |
| 513 | 514 | try std.fmt.allocPrint(alloc, "{s}/zmx-{d}", .{ tmpdir, uid }); | |
| 514 | - | errdefer alloc.free(socket_dir); | |
| 515 | 515 | ||
| 516 | 516 | return socket_dir; | |
| 517 | 517 | } | |
| 518 | 518 | ||
| 519 | + | fn logDir(alloc: std.mem.Allocator) ![]const u8 { | |
| 520 | + | const log_dir = if (posix.getenv("ZMX_DIR")) |zmxdir| | |
| 521 | + | try std.fmt.allocPrint(alloc, "{s}/logs", .{zmxdir}) | |
| 522 | + | else if (posix.getenv("XDG_STATE_HOME")) |xdg_state_home| | |
| 523 | + | try std.fmt.allocPrint(alloc, "{s}/zmx/logs", .{xdg_state_home}) | |
| 524 | + | else if (posix.getenv("HOME")) |home_dir| | |
| 525 | + | try std.fmt.allocPrint(alloc, "{s}/.local/state/zmx/logs", .{home_dir}) | |
| 526 | + | else fallback: { | |
| 527 | + | // This is the last resort: falling back to /tmp/$UID if HOME is unset. | |
| 528 | + | const tmpdir = std.mem.trimRight(u8, posix.getenv("TMPDIR") orelse "/tmp", "/"); | |
| 529 | + | const uid = posix.getuid(); | |
| 530 | + | break :fallback try std.fmt.allocPrint(alloc, "{s}/zmx-{d}", .{ tmpdir, uid }); | |
| 531 | + | }; | |
| 532 | + | ||
| 533 | + | return log_dir; | |
| 534 | + | } | |
| 535 | + | ||
| 519 | 536 | pub fn deinit(self: *Cfg, alloc: std.mem.Allocator) void { | |
| 520 | 537 | if (self.socket_dir.len > 0) alloc.free(self.socket_dir); | |
| 521 | 538 | if (self.log_dir.len > 0) alloc.free(self.log_dir); | |
| 522 | 539 | } | |
| 523 | 540 | ||
| 524 | 541 | pub fn mkdir(self: *Cfg) !void { | |
| 525 | - | posix.mkdirat(posix.AT.FDCWD, self.socket_dir, @intCast(self.dir_mode)) catch |err| switch (err) { | |
| 526 | - | error.PathAlreadyExists => {}, | |
| 527 | - | else => return err, | |
| 528 | - | }; | |
| 542 | + | try mkdirAll(self.socket_dir, @intCast(self.dir_mode)); | |
| 543 | + | try mkdirAll(self.log_dir, @intCast(self.dir_mode)); | |
| 544 | + | } | |
| 529 | 545 | ||
| 530 | - | posix.mkdirat(posix.AT.FDCWD, self.log_dir, @intCast(self.dir_mode)) catch |err| switch (err) { | |
| 531 | - | error.PathAlreadyExists => {}, | |
| 532 | - | else => return err, | |
| 533 | - | }; | |
| 546 | + | fn mkdirAll(sub_dir_path: []const u8, mode: posix.mode_t) !void { | |
| 547 | + | var it = try std.fs.path.componentIterator(sub_dir_path); | |
| 548 | + | var component = it.last() orelse return error.BadPathName; | |
| 549 | + | while (true) { | |
| 550 | + | posix.mkdirat(posix.AT.FDCWD, component.path, mode) catch |err| switch (err) { | |
| 551 | + | error.PathAlreadyExists => {}, | |
| 552 | + | error.FileNotFound => |e| { | |
| 553 | + | component = it.previous() orelse return e; | |
| 554 | + | continue; | |
| 555 | + | }, | |
| 556 | + | else => |e| return e, | |
| 557 | + | }; | |
| 558 | + | component = it.next() orelse return; | |
| 559 | + | } | |
| 534 | 560 | } | |
| 535 | 561 | }; | |
| 536 | 562 |