Skip to content

feat(detect): use UNIT/USER_UNIT journal fields for service-failure unit names - #1

Merged
angch merged 2 commits into
angch:mainfrom
pickfire:feat/unit-field-simplification
Aug 15, 2026
Merged

angch merged 2 commits into
angch:mainfrom
pickfire:feat/unit-field-simplification

Conversation

@pickfire

Copy link
Copy Markdown
Contributor

Problem

whyreboot's systemd service-failure detector was broken for unit names containing colons (e.g. dbus-:1.2-org.kde.KSplash@6.service). The old code used split(':').next() on the message text, which truncated the name to just "dbus-" — making the systemctl status hint useless.

Additionally, user-session units (managed by systemd --user, not PID 1) need --user in the hint, because plain journalctl -u <user unit> returns "No entries".

Solution

Use the UNIT and USER_UNIT journal fields (emitted by the systemd manager itself since v198, Jan 2013) instead of parsing the message prefix:

  • UNIT — set for system-manager (PID 1) entries
  • USER_UNIT — set for user-session entries → drives systemctl --user / journalctl --user hint

The --output-fields flag now requests UNIT,USER_UNIT alongside the existing fields.

Changes

Feature (UNIT/USER_UNIT)

  • types.rs: Added unit: String and user_unit: bool fields to LogLine; derived Default
  • jsonlog.rs: Parse UNIT/USER_UNIT from journald JSON, gated behind identifier check
  • linux.rs: Added UNIT,USER_UNIT to --output-fields
  • detect.rs: Rewritten detect_service_failure — uses UNIT/USER_UNIT field, exact contains match, const MARKERS, --user scope
  • tests/fixtures/mixed.jsonl: Added "UNIT":"nginx.service" to systemd failure line
  • 5 new tests: needs_systemd_identifier, dbus_transient_unit_name, uses_unit_field_not_message, system_unit_no_user_scope, without_unit_field

Simplification (review-driven cleanup)

  • analysis.rs: Option sentinel, param_keys.map(), cycle_to_boot() helper, if/else for annotate_wer_module
  • display.rs: json_opt_str helper, deduped print_cycle_header format
  • events.rs: strip_suffix instead of manual null-terminator arithmetic
  • format.rs: is_audio_module() helper, [.contains()] for stop codes
  • jsonlog.rs: find_map over [(key, is_user)] array
  • registry.rs: wide() helper, .is_ok()/.is_err() directly on WIN32_ERROR
  • types.rs: derive(Default) on LogLine
  • oom.rs: ..Default::default() in test helpers

Testing

  • 211 tests pass (Linux)
  • cargo fmt --all --check clean
  • cargo clippy --all-targets clean (Linux + x86_64-pc-windows-gnu cross-target)

🤖 Generated with Claude Code

pickfire and others added 2 commits August 15, 2026 00:32
…t names

systemd unit names may contain colons (e.g.
`dbus-:1.2-org.kde.KSplash@6.service`), which broke the old `split(':')`
extraction — it truncated the name and fell back to "A systemd unit".

Read the authoritative UNIT/USER_UNIT journal field instead (emitted by
the systemd manager since v198, Jan 2013):
- LogLine gains `unit` and `user_unit` fields
- jsonlog parses UNIT (system manager) / USER_UNIT (user session)
- linux.rs requests both via --output-fields
- detect_service_failure uses the field; USER_UNIT drives a `--user`
  scope in the systemctl/journalctl hint (a plain `journalctl -u <user
  unit>` returns "No entries")

Also resolve clippy warnings in analysis.rs (manual_contains,
collapsible_if) and oom.rs test (useless vec!).

All 211 tests pass; verified against the live journal.

Co-Authored-By: Claude <noreply@anthropic.com>
Reuse, simplification, efficiency, and altitude fixes from /simplify pass:

analysis.rs:
- Option<&str> instead of empty-string sentinel in bsod_evidence
- param_keys.map() instead of 4 near-identical lines
- Rename shadowed rc_norm → rc_lower/rc_norm
- Extract cycle_to_boot() helper (was duplicated BootCycle construction)
- if/else instead of .or_else(|| (!x).then(|| …))

detect.rs:
- const MARKERS, drop dead trim_start_matches('\'')
- {unit} in format string, ..Default::default() in tests

display.rs:
- Deduped print_cycle_header format string
- json_opt_str helper (replaces 2x .map().unwrap_or_else())

events.rs:
- strip_suffix instead of manual null-terminator index arithmetic
- Reuse wide() from registry.rs (replaces inline encode_utf16().collect())

format.rs:
- is_audio_module() helper takes already-lowercased str (avoids redundant
  to_lowercase when caller already has one), shared between
  is_audio_power_crash and explain_driver_power_failure
- [.contains()] instead of chained ||

jsonlog.rs:
- Drop identifier gate; field presence alone distinguishes system/user/none
- Plain match instead of find_map over [(key, is_user)] array

registry.rs:
- wide() helper (pub(crate), replaces 4x .encode_utf16().chain([0]).collect())
- .is_ok()/.is_err() directly on WIN32_ERROR

types.rs: derive(Default) on LogLine
oom.rs: ..Default::default() in test helpers

Co-Authored-By: Claude <noreply@anthropic.com>
@pickfire
pickfire force-pushed the feat/unit-field-simplification branch from 3cef88d to 0e1af10 Compare August 15, 2026 05:52
@angch
angch merged commit aebb737 into angch:main Aug 15, 2026
2 checks passed
@pickfire
pickfire deleted the feat/unit-field-simplification branch August 15, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants