Skip to content

Commit 57c4870

Browse files
authored
Merge pull request #11 from onetimesecret/feature/instance-specific-syslog-tags
Add instance-specific syslog tags and journalctl hints
2 parents 913521c + 01b3b22 commit 57c4870

File tree

5 files changed

+94
-13
lines changed

5 files changed

+94
-13
lines changed

src/ots_containers/commands/instance/_helpers.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@ def format_command(cmd: Sequence[str]) -> str:
1919
return " ".join(shlex.quote(arg) for arg in cmd)
2020

2121

22+
def format_journalctl_hint(instances: dict[InstanceType, list[str]]) -> str:
23+
"""Generate a journalctl command to view logs for the given instances.
24+
25+
Args:
26+
instances: Dict mapping InstanceType to list of identifiers
27+
28+
Returns:
29+
A journalctl command string with -t flags for each instance.
30+
"""
31+
tags = []
32+
for itype, ids in instances.items():
33+
for id_ in ids:
34+
tags.append(f"onetime-{itype.value}-{id_}")
35+
36+
if not tags:
37+
return ""
38+
39+
tag_args = " ".join(f"-t {shlex.quote(tag)}" for tag in tags)
40+
return f"journalctl {tag_args} -f"
41+
42+
2243
def resolve_identifiers(
2344
identifiers: tuple[str, ...],
2445
instance_type: InstanceType | None,
@@ -96,6 +117,8 @@ def for_each_instance(
96117
delay: int,
97118
action: Callable[[InstanceType, str], None],
98119
verb: str,
120+
*,
121+
show_logs_hint: bool = False,
99122
) -> int:
100123
"""Run action for each instance with delay between.
101124
@@ -104,6 +127,7 @@ def for_each_instance(
104127
delay: Seconds to wait between operations
105128
action: Callable taking (instance_type, identifier)
106129
verb: Present participle for logging (e.g., "Restarting")
130+
show_logs_hint: If True, print journalctl command to view logs
107131
108132
Returns:
109133
Total number of instances processed.
@@ -128,4 +152,10 @@ def for_each_instance(
128152
time.sleep(delay)
129153

130154
print(f"Processed {total} instance(s)")
155+
156+
if show_logs_hint:
157+
hint = format_journalctl_hint(instances)
158+
if hint:
159+
print(f"\nView logs: {hint}")
160+
131161
return total

src/ots_containers/commands/instance/app.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from ._helpers import (
1414
for_each_instance,
1515
format_command,
16+
format_journalctl_hint,
1617
resolve_identifiers,
1718
)
1819
from .annotations import (
@@ -393,7 +394,7 @@ def do_deploy(inst_type: InstanceType, id_: str) -> None:
393394
raise
394395

395396
instances = {itype: list(identifiers)}
396-
for_each_instance(instances, delay, do_deploy, "Deploying")
397+
for_each_instance(instances, delay, do_deploy, "Deploying", show_logs_hint=True)
397398

398399

399400
@app.command
@@ -505,7 +506,7 @@ def do_redeploy(inst_type: InstanceType, id_: str) -> None:
505506
raise
506507

507508
verb = "Force redeploying" if force else "Redeploying"
508-
for_each_instance(instances, delay, do_redeploy, verb)
509+
for_each_instance(instances, delay, do_redeploy, verb, show_logs_hint=True)
509510

510511

511512
@app.command
@@ -616,6 +617,10 @@ def start(
616617
systemd.start(unit)
617618
print(f"Started {unit}")
618619

620+
hint = format_journalctl_hint(instances)
621+
if hint:
622+
print(f"\nView logs: {hint}")
623+
619624

620625
@app.command
621626
def stop(
@@ -682,6 +687,10 @@ def restart(
682687
systemd.restart(unit)
683688
print(f"Restarted {unit}")
684689

690+
hint = format_journalctl_hint(instances)
691+
if hint:
692+
print(f"\nView logs: {hint}")
693+
685694

686695
@app.command
687696
def enable(

src/ots_containers/quadlet.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464
Image={image}
6565
Network=host
6666
67-
# Syslog tag for unified log filtering: journalctl -t onetime -f
68-
PodmanArgs=--log-opt tag=onetime
67+
# Syslog tag for per-instance log filtering: journalctl -t onetime-web-7043 -f
68+
PodmanArgs=--log-opt tag=onetime-web-%i
6969
7070
# Port is derived from instance name: onetime-web@7043 -> PORT=7043
7171
Environment=PORT=%i
@@ -191,8 +191,8 @@ def write_web_template(cfg: Config, env_file_path: Path | None = None) -> None:
191191
Image={image}
192192
Network=host
193193
194-
# Syslog tag for unified log filtering: journalctl -t onetime -f
195-
PodmanArgs=--log-opt tag=onetime
194+
# Syslog tag for per-instance log filtering: journalctl -t onetime-worker-1 -f
195+
PodmanArgs=--log-opt tag=onetime-worker-%i
196196
197197
# Worker ID is derived from instance name: onetime-worker@1 -> WORKER_ID=1
198198
Environment=WORKER_ID=%i
@@ -293,8 +293,8 @@ def write_worker_template(cfg: Config, env_file_path: Path | None = None) -> Non
293293
Image={image}
294294
Network=host
295295
296-
# Syslog tag for unified log filtering: journalctl -t onetime -f
297-
PodmanArgs=--log-opt tag=onetime
296+
# Syslog tag for per-instance log filtering: journalctl -t onetime-scheduler-main -f
297+
PodmanArgs=--log-opt tag=onetime-scheduler-%i
298298
299299
# Scheduler ID is derived from instance name: onetime-scheduler@main -> SCHEDULER_ID=main
300300
Environment=SCHEDULER_ID=%i

tests/commands/instance/test_helpers.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,52 @@
66
from ots_containers.commands.instance._helpers import (
77
for_each_instance,
88
format_command,
9+
format_journalctl_hint,
910
resolve_identifiers,
1011
)
1112
from ots_containers.commands.instance.annotations import InstanceType
1213

1314

15+
class TestFormatJournalctlHint:
16+
"""Test format_journalctl_hint helper."""
17+
18+
def test_single_web_instance(self):
19+
"""Should generate journalctl command for single web instance."""
20+
instances = {InstanceType.WEB: ["7043"]}
21+
result = format_journalctl_hint(instances)
22+
assert result == "journalctl -t onetime-web-7043 -f"
23+
24+
def test_multiple_web_instances(self):
25+
"""Should generate journalctl command for multiple web instances."""
26+
instances = {InstanceType.WEB: ["7043", "7044"]}
27+
result = format_journalctl_hint(instances)
28+
assert result == "journalctl -t onetime-web-7043 -t onetime-web-7044 -f"
29+
30+
def test_mixed_instance_types(self):
31+
"""Should generate journalctl command for mixed instance types."""
32+
instances = {
33+
InstanceType.WEB: ["7043"],
34+
InstanceType.WORKER: ["billing"],
35+
InstanceType.SCHEDULER: ["main"],
36+
}
37+
result = format_journalctl_hint(instances)
38+
assert "-t onetime-web-7043" in result
39+
assert "-t onetime-worker-billing" in result
40+
assert "-t onetime-scheduler-main" in result
41+
assert result.endswith(" -f")
42+
43+
def test_empty_instances(self):
44+
"""Should return empty string for empty instances."""
45+
result = format_journalctl_hint({})
46+
assert result == ""
47+
48+
def test_worker_instance(self):
49+
"""Should generate journalctl command for worker instance."""
50+
instances = {InstanceType.WORKER: ["1"]}
51+
result = format_journalctl_hint(instances)
52+
assert result == "journalctl -t onetime-worker-1 -f"
53+
54+
1455
class TestFormatCommand:
1556
"""Test format_command helper."""
1657

tests/test_quadlet.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ def test_write_web_template_includes_syslog_tag(self, mocker, tmp_path):
102102
quadlet.write_web_template(cfg)
103103

104104
content = cfg.web_template_path.read_text()
105-
# Syslog tag allows: journalctl -t onetime -f
106-
assert "PodmanArgs=--log-opt tag=onetime" in content
105+
# Syslog tag allows: journalctl -t onetime-web-7043 -f
106+
assert "PodmanArgs=--log-opt tag=onetime-web-%i" in content
107107

108108
def test_write_web_template_includes_volumes(self, mocker, tmp_path):
109109
"""Container quadlet should mount config directory and static assets."""
@@ -305,8 +305,8 @@ def test_write_worker_template_includes_syslog_tag(self, mocker, tmp_path):
305305
quadlet.write_worker_template(cfg)
306306

307307
content = cfg.worker_template_path.read_text()
308-
# Syslog tag allows: journalctl -t onetime -f
309-
assert "PodmanArgs=--log-opt tag=onetime" in content
308+
# Syslog tag allows: journalctl -t onetime-worker-1 -f
309+
assert "PodmanArgs=--log-opt tag=onetime-worker-%i" in content
310310

311311
def test_write_worker_template_sets_worker_id_env_var(self, mocker, tmp_path):
312312
"""Worker quadlet should set WORKER_ID env var from instance."""
@@ -530,7 +530,8 @@ def test_write_scheduler_template_includes_syslog_tag(self, mocker, tmp_path):
530530
quadlet.write_scheduler_template(cfg)
531531

532532
content = cfg.scheduler_template_path.read_text()
533-
assert "PodmanArgs=--log-opt tag=onetime" in content
533+
# Syslog tag allows: journalctl -t onetime-scheduler-main -f
534+
assert "PodmanArgs=--log-opt tag=onetime-scheduler-%i" in content
534535

535536
def test_write_scheduler_template_sets_scheduler_id_env_var(self, mocker, tmp_path):
536537
"""Scheduler quadlet should set SCHEDULER_ID env var from instance."""

0 commit comments

Comments
 (0)