The app hands its log to Dev Updater on the phone, not through ai-server
Iris's call once the upload route was working: put it in Dev Updater properly. So the app now exposes its own ring through a ContentProvider at `<applicationId>.devlog` -- Dev Updater's contract, written down in that project's README, not something invented here -- and Dev Updater's phone app reads it on the same device and forwards it to its own build machine. No tunnel, no token, no second enrolment, and any app that server delivers can implement the same and get the same Runtime tab. `DevLogProvider.java` plus `devlog.rs` are the platform glue only: a flat `String[]` across JNI, a `MatrixCursor` on the Java side, and `nativeReady` telling Rust the authority the provider actually registered, so the Diagnostics pane can name somewhere a reader can query rather than composing a guess. `LogRing::newest_seq()` is the one addition in `client-core`: an in-memory ring starts again at zero, so it is what lets a reader notice the process restarted instead of silently skipping everything since. Deleted with it, so there is one mechanism: `client_core::log_upload`, `POST /client-log` on ai-server, the `AI_APP_LOG_*` baking (which left `build.rs` with nothing to do), and the uploader on both Android clients. Kept: the ring, `RingLogger`, `install_process_logger`, and the Diagnostics line -- whose second half is now `devlog provider: content://<authority>`. Verified end to end on this checkout's emulator: iris's own `iris::android::view` startup lines read out of the provider by the shell, forwarded by Dev Updater's Runtime tab, and served back from `GET /apps/android-app/components/app/logs?kind=runtime`. A component whose package has no provider says so in as many words. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
e10582a2cd
commit
06b8a1f4b0
15 files changed
+618
-842
No files matched your search
+76
-71
@@ -89,83 +89,77 @@ Three things this says about the rig, since the rig is new:
|
||||
claimed of it -- asserted at the end of every draw, and the test
|
||||
checks both directions.
|
||||
|
||||
### Phone logging, 2026-09-07 (built and verified end to end)
|
||||
### Phone logging, 2026-09-07 (rebuilt on Dev Updater's own tab)
|
||||
|
||||
**The problem**: Iris tests these builds on a phone with no `adb`, and
|
||||
Android forbids one app reading another's `logcat`, so a `log::info!` in
|
||||
the iris app could not reach her at all. What she asked for was Dev
|
||||
Updater, which she already reads.
|
||||
|
||||
**The route, in one line**: the app keeps its own bounded log ring, posts
|
||||
it to `ai-server`, and `ai-server` re-emits each line into its own
|
||||
`tracing` output -- which Dev Updater *already* shows as that component's
|
||||
**runtime log**, because it runs `ai-server` as a `Managed` service and
|
||||
that service's script redirects stdout to
|
||||
`$XDG_DATA_HOME/dev-updater/services/<key>-<component>/<...>.log` and
|
||||
reports the path. **Nothing in dev-updater changed.** The alternatives and
|
||||
what each would have cost are in docs/DECISIONS.md.
|
||||
**The route, in one line**: the app keeps its own bounded log ring and
|
||||
**exposes it on the device** through a `ContentProvider`; Dev Updater's
|
||||
phone app -- on the same phone -- reads that while the component's
|
||||
**Runtime** tab is open and forwards what is new to its own build machine,
|
||||
into that APK component's runtime log. No tunnel, no token, no second
|
||||
enrolment.
|
||||
|
||||
**It is Dev Updater's contract, not iris's feature.** Written down in
|
||||
dev-updater's `README.md` under "An app's own log", so any app that server
|
||||
delivers gets the tab by implementing it. The shape:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| authority | `<applicationId>.devlog` |
|
||||
| read permission | `dev.updater.permission.READ_DEVLOG` (`android:readPermission`) |
|
||||
| `lines?since=<seq>` | held lines with `seq >= since`, ascending: `seq, t_ms, level, target, message` |
|
||||
| `status` | one row: `held, dropped, newest_seq` |
|
||||
|
||||
`newest_seq` is `-1` for an empty ring and is what makes a **restart**
|
||||
visible: the ring is in memory, so a new process starts again at zero and
|
||||
a reader holding a cursor would otherwise skip everything since, silently.
|
||||
`insert`/`update`/`delete` throw. `notifyChange` is **not** implemented --
|
||||
the ring is filled by a `log::Log` backend on whatever thread logged, and
|
||||
routing that to a provider means a callback through `client-core` for
|
||||
every platform, so Dev Updater polls (about a second, only while the tab
|
||||
is open) and the contract says so.
|
||||
|
||||
What exists now:
|
||||
|
||||
- `client_core::log_ring` -- `LogRing` (2000 lines / 256 KiB, whichever
|
||||
bites first, with `dropped` reported rather than inferred), `RingLogger`
|
||||
(a `log::Log` backend that records *and* forwards to the platform's own
|
||||
logger), and `install_process_logger`. Reading does not consume, so the
|
||||
report and the uploader are two readers of one ring.
|
||||
- `client_core::log_upload` -- `LogUploader::flush_once` (batched at 500
|
||||
lines, retried from the same cursor on failure) and `LogUpload::spawn`
|
||||
(a thread flushing every 10s, stopped by dropping the handle).
|
||||
**Nothing in it calls `log!`**: those lines would land in the ring it is
|
||||
draining.
|
||||
- `POST /client-log` on `ai-server` -- `{source, lines:[{seq, at, level,
|
||||
target, message}]}`, re-emitted at the client's own level under the
|
||||
target **`ai_server::client_log`**. Under the crate's path deliberately:
|
||||
a bare `client_log` target is dropped by `RUST_LOG=ai_server=debug`, the
|
||||
filter AGENTS.md tells people to run with, so every line a phone sent
|
||||
vanished with nothing saying so. Found by running it.
|
||||
- `iris/android-app/src/app_log.rs` -- the platform half only:
|
||||
`android_logger` as the logger to forward to, and the destination baked
|
||||
in by `build.rs` from `AI_APP_LOG_HOST`/`_PORT`/`_TOKEN` plus the pinned
|
||||
CA (all three or none). `Copy report` appends the ring to the clipboard
|
||||
text and flushes the uploader first; `Diagnostics` gains two lines --
|
||||
how many lines are held and when the last arrived, and what the uploader
|
||||
last did.
|
||||
bites first, with `dropped` reported rather than inferred), `since(seq)`,
|
||||
`newest_seq()`, `RingLogger` (a `log::Log` backend that records *and*
|
||||
forwards to the platform's own logger), and `install_process_logger`.
|
||||
Reading does not consume, so the report and the provider are two readers
|
||||
of one ring.
|
||||
- `iris/android-app/src/devlog.rs` and
|
||||
`app/src/main/java/dev/iris/android/demo/DevLogProvider.java` -- the
|
||||
platform glue only (the sharing rule): a `String[]` across JNI, a
|
||||
`MatrixCursor` on the Java side, and `nativeReady` telling Rust the
|
||||
authority the provider actually registered under.
|
||||
- `iris/android-app/src/app_log.rs` -- `android_logger` as the logger to
|
||||
forward to, and the Diagnostics pane's two lines: how many lines are
|
||||
held, and **`devlog provider: content://<authority>`** (or "declared,
|
||||
not created yet", since Android creates a provider lazily). Named from
|
||||
what the provider registered rather than composed from the package here,
|
||||
so a screenshot of that pane is evidence the contract is live.
|
||||
- On the Dev Updater side (its own repo, commit `013d711`): the tab for
|
||||
every component rather than only a server's, `DevLog.kt`'s reader and
|
||||
cursor, `POST /apps/{key}/components/{name}/runtime-log`, and
|
||||
`$XDG_DATA_HOME/dev-updater/devlogs/<key>-<component>.log` with one
|
||||
rotation at 4 MiB.
|
||||
|
||||
**Superseded 2026-09-07, and left standing rather than edited**: Dev
|
||||
Updater is growing an on-device runtime-log reader, so this whole route --
|
||||
`log_upload`, `POST /client-log` and the `AI_APP_LOG_*` baking -- is being
|
||||
removed rather than kept current. The app's *server* destination no longer
|
||||
comes from any of it: that is the enrolment link (the queue item above).
|
||||
What follows describes the route as built.
|
||||
**Deleted with it, so there is one mechanism**: `client-core`'s
|
||||
`log_upload`, `POST /client-log` on `ai-server`, the `AI_APP_LOG_*` baking
|
||||
in `iris/android-app/build.rs` (which left that file with nothing to do,
|
||||
so it is gone), and the uploader fields on both Android clients.
|
||||
|
||||
**How to use it.** Build the APK with the destination in the environment,
|
||||
on the machine `ai-server` runs on:
|
||||
**How to use it.** Build and install the bench APK; open Dev Updater ->
|
||||
the ai-app project -> the **app** component's log button -> the
|
||||
**Runtime** tab. Nothing to configure: the tab finds the provider from the
|
||||
package the component installs. A component whose package exposes none
|
||||
says so in as many words, which is a different sentence from an empty log.
|
||||
|
||||
AI_APP_LOG_HOST=10.66.0.1 AI_APP_LOG_PORT=8443 \
|
||||
AI_APP_LOG_TOKEN=<a token that server accepts> \
|
||||
./build-apk.sh release
|
||||
|
||||
Then read it on the phone: Dev Updater -> the ai-app project -> the
|
||||
**server** component's log button -> the **Runtime** tab. The app's lines
|
||||
are the ones tagged `ai_server::client_log`, each carrying `[<source>
|
||||
<the app's own clock> #<seq>]` before the target and message. A build with
|
||||
none of those variables set still keeps its ring and still puts it on the
|
||||
clipboard from `Copy report`; the Diagnostics pane says so in as many
|
||||
words.
|
||||
|
||||
**Verified 2026-09-07, all four hops.** ai-server on 127.0.0.1:8455 with a
|
||||
scratch config; the bench APK built `--abi x86_64` with
|
||||
`AI_APP_LOG_HOST=10.0.2.2 AI_APP_LOG_PORT=8455 AI_APP_LOG_TOKEN=...`;
|
||||
installed and launched on this checkout's emulator. Twenty seconds later
|
||||
the server's log held
|
||||
|
||||
INFO ai_server::client_log: [iris-bench 20:17:02.284 #0]
|
||||
iris::android::view: iris: new_peer content_scale=2.625
|
||||
|
||||
and a scratch `dev-updater` (port 8492, `XDG_DATA_HOME=/tmp/du-test/data`)
|
||||
with this checkout registered served exactly those lines back from
|
||||
`GET /apps/ai-app-2/components/server/logs?kind=runtime` -- which is the
|
||||
JSON the phone's Runtime tab renders.
|
||||
**Verified 2026-09-07 end to end** -- see "Verified" below.
|
||||
|
||||
**Two rig traps this cost an hour to find, both in `build-apk.sh`, both
|
||||
still there.** Written down rather than fixed because fixing them belongs
|
||||
@@ -193,7 +187,9 @@ Also: the bench APK's package is `dev.iris.android.demo.bench`, not
|
||||
`dev.iris.android.demo`. An older non-bench build left installed answers
|
||||
to the second name, runs, looks right, and reports whatever *it* was built
|
||||
with -- which is how "log upload: this build has no server configured"
|
||||
came from a build that had one.
|
||||
came from a build that had one. The devlog authority carries the
|
||||
`applicationId` for exactly that reason: the two packages each get their
|
||||
own and neither can read the other's log.
|
||||
|
||||
### `iris::input`/`iris::frame` diagnostics, 2026-09-07
|
||||
|
||||
@@ -637,8 +633,16 @@ closes it.
|
||||
- [ ] Scroll clamped at both ends, and Compose's impulse velocity
|
||||
estimator with min/max fling velocity (docs/IRIS_TODO.md, 2026-09-07
|
||||
later). After the culling fix lands (same file).
|
||||
- [ ] **APK runtime logs in Dev Updater (Iris, 2026-09-07: "please add
|
||||
android / apk runtime log support to dev updater").** Supersedes the
|
||||
- [x] **APK runtime logs in Dev Updater (Iris, 2026-09-07: "please add
|
||||
android / apk runtime log support to dev updater").** **Done
|
||||
2026-09-07** -- dev-updater `013d711`, and this repo's provider half;
|
||||
"Phone logging" above is the account, docs/DECISIONS.md the decision.
|
||||
What was designed and what was built agree except in one place: the
|
||||
provider does **not** call `notifyChange` (the reason is in both), and
|
||||
the tab is drawn for every component rather than only where a provider
|
||||
resolves, since its absence would be the one thing that could not say
|
||||
which of the several reasons there was nothing to read. The design as
|
||||
written: Supersedes the
|
||||
ai-server `POST /client-log` route, which becomes the second mechanism
|
||||
and is deleted once this works (`log_upload.rs`, `app_log.rs`'s
|
||||
upload half, the route). Design: Android forbids reading another
|
||||
@@ -684,10 +688,11 @@ closes it.
|
||||
Dev Updater needed no change: its Enroll button already opens the minted
|
||||
link with `ACTION_VIEW`, and Android offers the chooser between this app
|
||||
and the Compose one.
|
||||
**The log upload was deliberately left out of it**: Dev Updater is
|
||||
growing an on-device runtime-log reader instead (Iris, 2026-09-07), so
|
||||
`log_upload`, `POST /client-log` and the `AI_APP_LOG_*` baking are on
|
||||
their way out whole rather than being rewired first.
|
||||
**The log upload was deliberately left out of it**: Dev Updater grew an
|
||||
on-device runtime-log reader instead (Iris, 2026-09-07), so
|
||||
`log_upload`, `POST /client-log` and the `AI_APP_LOG_*` baking went out
|
||||
whole rather than being rewired first -- done the same day, "Phone
|
||||
logging" above. `build.rs` had nothing left to do and is gone with them.
|
||||
- [ ] `iris/android-app/build-apk.sh`: clear Gradle's merged-native-libs
|
||||
cache when the ABI changes (the x86_64 trap), and make the debug bench
|
||||
APK installable (648 MB) -- RUST.md's logging section names both.
|
||||
|
||||
Reference in new issue
Block a user