Skip to content

refactor(log): route all engine output through EngineLog#13

Merged
superuser404notfound merged 1 commit into
superuser404notfound:mainfrom
Delarkz:refactor-engine-log
May 23, 2026
Merged

refactor(log): route all engine output through EngineLog#13
superuser404notfound merged 1 commit into
superuser404notfound:mainfrom
Delarkz:refactor-engine-log

Conversation

@Delarkz
Copy link
Copy Markdown
Contributor

@Delarkz Delarkz commented May 23, 2026

Summary

Every diagnostic line the engine emits now goes through one path (EngineLog.emit) with one sink contract: OSLog always, optional host handler. The stdio fallback that previously caused duplicate lines in Xcode is gone, and the SW playback subsystem gets its own log category (sw.playback) so it can be isolated from the rest of the engine in log stream / Console.app filters.

Motivation

Two distinct problems with the old logging layout:

  1. Duplicate lines in Xcode. EngineLog.emit wrote to OSLog unconditionally, then also called print(line) whenever no host handler was installed. Xcode renders both OSLog activity and the process's stdout in the same debug-area console, so every emit surfaced twice. The old doc comment claimed gating handler vs. stdout solved this — it did not, because OSLog still fired in parallel. A isatty(STDOUT_FILENO) gate was tried as an intermediate fix; Xcode wires GUI-app stdout to a pty, so the gate let duplicates through anyway. The fallback was removed entirely.

  2. 23 raw print(...) calls scattered across AVIOReader, Demuxer, AudioDecoder, AudioOutput, SoftwareVideoDecoder, SampleBufferRenderer, plus a vestigial duplicate in AetherEngine itself. These bypassed OSLog entirely, so they weren't filterable by category, didn't reach Console.app, and contributed to the Xcode duplication when their host also emitted through EngineLog.

The new swPlayback category carves the custom playback subsystem (SoftwarePlaybackHost + its decoders + SampleBufferRenderer + AudioDecoder + AudioOutput) out of the .engine catch-all, so .engine now genuinely means "cross-subsystem / lifecycle" and the SW route is filterable on its own.

Behaviour matrix

Context OSLog Handler called Net effect
Host app in Xcode, no handler installed one line per emit in Xcode console (was two)
Host app on device visible via Console.app / log stream, no stdio noise
aetherctl serve / validate (tty / piped / redirected) timestamped stdout, identical to before
aetherctl probe (tty / piped / redirected) ✓ (newly installed) identical to pre-change stdout output
Any future host with no handler OSLog only; install a handler to mirror to stdout

- Drop stdio fallback in EngineLog.emit; OSLog + optional handler only.
  Fixes the duplicate-line rendering in Xcode's debug console that
  occurred because OSLog and print both surfaced through it.
- Convert 23 #if DEBUG print() calls (AVIOReader, Demuxer, AudioDecoder,
  AudioOutput, SoftwareVideoDecoder, SampleBufferRenderer) to
  categorised EngineLog.emit.
- Add sw.playback category; move SoftwarePlaybackHost, SW + HW video
  decoders, SampleBufferRenderer, AudioDecoder and AudioOutput out of
  the .engine catch-all so the custom playback subsystem is filterable
  on its own.
- Remove vestigial memprobe print duplicate in AetherEngine.swift.
- aetherctl probe installs its own EngineLog.handler so the CLI keeps
  its inline stdout diagnostics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@superuser404notfound superuser404notfound left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Verified:

  • Engine module is now 100% print-free (grep across Sources/AetherEngine/ returns nothing); every diagnostic line flows through EngineLog.emit.
  • Root cause of the Xcode duplicates was indeed OSLog + print firing in parallel, not the print fallback alone. Removing the stdio fallback in EngineLog.emit is the right fix.
  • aetherctl probe now installs its own handler, which it needs because the previous behaviour relied on the now-removed stdio fallback. Behaviour matrix in the PR description matches what I see in Sources/aetherctl/main.swift (serve/validate/probe all install a handler now).
  • New .swPlayback category cleanly carves the SoftwarePlaybackHost subsystem (HardwareVideoDecoder, SoftwareVideoDecoder, SampleBufferRenderer, AudioDecoder, AudioOutput) out of the .engine catch-all. HardwareVideoDecoder lives under swPlayback because it runs inside SoftwarePlaybackHost, not under the AVPlayer-driven path, so the name is accurate even though "Hardware" sounds like it might belong elsewhere.
  • Public API unchanged: handler signature, emit signature, new Category case is non-breaking. swift build is green on this branch.

One follow-up I'll handle on the Sodalite side post-merge: SodaliteApp.swift:18-19 has a stale comment ("App Store builds leave the handler nil so the engine emits to print() only") that no longer reflects reality after this change. Behaviour-wise the new world is better (stdout in App Store builds with no debugger attached was always wasted), but the comment will mislead future readers. Will fix in a separate commit once this lands.

Thanks for the cleanup.

@superuser404notfound superuser404notfound merged commit b9a8710 into superuser404notfound:main May 23, 2026
@Delarkz Delarkz deleted the refactor-engine-log branch May 23, 2026 21:00
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