Android: route slog to logcat, so the app can say why it is exiting #160

Closed
opened 2026-08-20 17:00:00 +00:00 by logan · 1 comment
Collaborator

Every slog line the app writes on Android is discarded, including
the one naming the error it is about to exit on.

android-tier.md records this as a fact of the platform:

Go's stdout does not reach logcat. An Android app's fd 1 and 2 go
to /dev/null. [...] setprop log.redirect-stdio true does not help:
it redirects the Java runtime's System.out, and the Go code is a
c-shared native library.

The first half is true. The conclusion — that nothing can be done — is
not, and #52 is what it cost. That bug's whole diagnosis is one line
main.go already writes:

sLogger.Error("application error", "err", err.Error())
os.Exit(1)

err was "application is running or a previous run has failed", which
names the fault exactly. It went to /dev/null, so the bug presented as
a process that vanished with no tombstone, no AndroidRuntime stack and
nothing in logcat -b crash — and sat at Priority/Critical,
unverifiable, for months.

The fix is a slog.Handler that writes to __android_log_write.
It is a small amount of cgo behind the android build tag, alongside
backend/mediacontrols/android.go:

#include <android/log.h>
// __android_log_write(prio, tag, msg)

mapping slog.Level to ANDROID_LOG_{DEBUG,INFO,WARN,ERROR} and using
the app's own tag, which scripts/android-emulator.sh logs already
filters for ("$PKG":V, plus GoLog:V).

Notes for whoever takes it:

  • It belongs where main.go builds the handler, selected by build tag
    rather than by a runtime check, so desktop links no cgo for it. The
    devslog handler stays the desktop one.
  • The contract-in-an-untagged-file discipline from androidpayload.go
    applies: a tagged file is compiled by nothing make lint or
    make test runs, so keep the level mapping in an untagged file with
    a test.
  • Verify with make android-logs, which already filters the right tags
    — the check is that a deliberate slog.Error at startup appears.

Related: #52 (the bug this would have made a five-minute diagnosis), and
android-tier.md's "Three facts that make failure invisible", which
should be amended from three facts to two once this lands.

**Every `slog` line the app writes on Android is discarded, including the one naming the error it is about to exit on.** `android-tier.md` records this as a fact of the platform: > **Go's stdout does not reach logcat.** An Android app's fd 1 and 2 go > to `/dev/null`. [...] `setprop log.redirect-stdio true` does not help: > it redirects the *Java* runtime's `System.out`, and the Go code is a > c-shared native library. The first half is true. The conclusion — that nothing can be done — is not, and #52 is what it cost. That bug's whole diagnosis is one line `main.go` already writes: ```go sLogger.Error("application error", "err", err.Error()) os.Exit(1) ``` `err` was `"application is running or a previous run has failed"`, which names the fault exactly. It went to `/dev/null`, so the bug presented as a process that vanished with no tombstone, no `AndroidRuntime` stack and nothing in `logcat -b crash` — and sat at `Priority/Critical`, unverifiable, for months. **The fix is a `slog.Handler` that writes to `__android_log_write`.** It is a small amount of cgo behind the `android` build tag, alongside `backend/mediacontrols/android.go`: ```c #include <android/log.h> // __android_log_write(prio, tag, msg) ``` mapping `slog.Level` to `ANDROID_LOG_{DEBUG,INFO,WARN,ERROR}` and using the app's own tag, which `scripts/android-emulator.sh logs` already filters for (`"$PKG":V`, plus `GoLog:V`). Notes for whoever takes it: - It belongs where `main.go` builds the handler, selected by build tag rather than by a runtime check, so desktop links no cgo for it. The `devslog` handler stays the desktop one. - The contract-in-an-untagged-file discipline from `androidpayload.go` applies: a tagged file is compiled by nothing `make lint` or `make test` runs, so keep the level mapping in an untagged file with a test. - Verify with `make android-logs`, which already filters the right tags — the check is that a deliberate `slog.Error` at startup appears. Related: #52 (the bug this would have made a five-minute diagnosis), and `android-tier.md`'s "Three facts that make failure invisible", which should be amended from three facts to two once this lands.
logan added the Kind/Enhancement
Priority
High
2
Area/PackagingPlatform/Android
labels 2026-08-20 17:00:00 +00:00
logan self-assigned this 2026-08-21 20:07:12 +00:00
logan added the
Status
In Progress
label 2026-08-21 20:07:12 +00:00
Author
Collaborator

Taking this. Branch 160-android-slog-logcat.

Sequenced ahead of #135 and #186, argued on #73: #135's own Direction is
"count the underruns and log them", which is unreadable on the device
until this lands.

Approach is the one the issue settles: a slog.Handler over
__android_log_write behind the android build tag, selected where
main.go builds the handler rather than by a runtime check, so desktop
links no cgo. The level mapping goes in an untagged file with a test,
on androidpayload.go's discipline — the only tier that compiles the
tagged file is a real phone, so everything decidable off one is decided
against something make test can reach. Verified with make android-logs against the attached TLP301.

Taking this. Branch `160-android-slog-logcat`. Sequenced ahead of #135 and #186, argued on #73: #135's own Direction is "count the underruns and log them", which is unreadable on the device until this lands. Approach is the one the issue settles: a `slog.Handler` over `__android_log_write` behind the `android` build tag, selected where `main.go` builds the handler rather than by a runtime check, so desktop links no cgo. The level mapping goes in an **untagged** file with a test, on `androidpayload.go`'s discipline — the only tier that compiles the tagged file is a real phone, so everything decidable off one is decided against something `make test` can reach. Verified with `make android-logs` against the attached TLP301.
logan closed this issue 2026-08-21 20:47:45 +00:00
gitea-actions bot removed the
Status
In Progress
label 2026-08-21 20:47:56 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#160