Files
jellytau/src-tauri/Cargo.toml
T
dtourolle f3fa45f742
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 22m12s
🏗️ Build and Test JellyTau / Supply Chain (pull_request) Successful in 37s
Traceability Validation / Check Requirement Traces (pull_request) Successful in 11s
🏗️ Build and Test JellyTau / Android Compile Check (pull_request) Successful in 4m10s
feat(diagnostics): persistent redacted logging and an exportable bundle
The app forgot everything it did the moment it exited. The Rust half
logged through env_logger to stdout only -- invisible to anyone who
launched from a desktop icon, and on Android worse than that: stdout is
not logcat, so the backend produced no visible output at all on the
platform carrying this project's hardest bugs. The autoplay deadlock,
the truncated-stream restart and the background-audio stall were all
diagnosed by talking a user through `adb logcat`, because there was no
other way to see anything. A panic left nothing behind at all.

Logs now go to a size-capped rotating file, to logcat on Android, and to
the webview console in dev. A panic is recorded with its backtrace before
the process dies. The frontend's messages are forwarded into the same
file, so one timeline holds both halves of the app in order -- which is
what makes a race between them legible after the fact, and races between
them are the expensive bug class here.

Redaction runs in the log FORMATTER, not at export time. A credential
sitting in a file on the device is already a disclosure; stripping it on
the way out would be too late. The exporter redacts a second time to
cover files written by builds that predate this. api_key, X-Emby-Token,
Authorization, "AccessToken" and Token="..." all reduce to [REDACTED],
while host, item ids and filenames are deliberately kept -- a log scrubbed
of those is one nobody can debug anything from. Server URLs keep scheme
and host and drop any embedded user:pass@.

Two things the tests caught that review would not have:

  - redact_headers recursed on its own output. The replacement keeps the
    header NAME, so the next call matched the same header forever; the
    test died with a stack overflow. It is a forward scan now.
  - The frontend forwarder used `void plugin.error(...)`. `void` discards
    a promise's value but not its rejection, so in any webview without
    IPC -- a unit test, SSR, a browser preview -- every log line became an
    unhandled rejection. 20 of them showed up the first time coverage
    ran. Each call now attaches a catch.

Only info and above cross the IPC boundary: debug is per-tick player
state and forwarding it would be thousands of calls a minute for output
nobody reads. A failing forwarder never propagates and never prevents the
console write.

Nothing is transmitted anywhere. The export writes a zip and reports its
path; the user attaches it themselves, which is also what keeps this from
becoming telemetry. An Android share intent is explicitly out of scope --
it is Kotlin work that belongs with the other native code.

The panic hook chains to the previous hook rather than replacing it,
because utils/lock.rs installs a silencing hook around tests that provoke
poisoned locks on purpose.

Spec in docs/specs/diagnostics-and-logging.md; UR-078 / DR-218 / UT-209.

Verified: 1079 frontend tests and the coverage gate, 759 Rust tests,
clippy -D warnings, svelte-check 0 errors, and cargo check for
aarch64-linux-android.
2026-08-21 18:58:57 +02:00

125 lines
5.0 KiB
TOML

[package]
name = "jellytau"
version = "0.9.1"
description = "A cross-platform Jellyfin client"
authors = ["Duncan Tourolle <duncan@tourolle.paris>"]
license = "MIT"
repository = "https://gitea.tourolle.paris/dtourolle/jellytau"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "jellytau_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
# Keep debug info minimal to reduce target/ size in CI (line numbers in
# backtraces are preserved; the bulky full debuginfo is dropped).
[profile.dev]
debug = "line-tables-only"
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
# protocol-asset serves cached thumbnails to the webview (asset://localhost on
# Linux/macOS, http://asset.localhost on Windows/Android); without it
# convertFileSrc yields a URL nothing answers. Paired with
# app.security.assetProtocol in tauri.conf.json, which scopes it to
# $APPDATA/thumbnails/** — the one directory still read through this protocol.
# Downloaded media went the same way until DR-137 moved it to the loopback media
# server, so the database, the encrypted-token fallback file and downloads/ are
# all outside the grant now.
# TRACES: UR-012, UR-071 | DR-134, DR-137, DR-198
tauri = { version = "2", features = ["protocol-asset"] }
tauri-plugin-opener = "2"
tauri-plugin-os = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
rand = "0.8"
tokio = { version = "1", features = ["sync", "rt-multi-thread", "time", "fs", "io-util", "macros"] }
tokio-util = "0.7"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json"] }
urlencoding = "2"
futures-util = "0.3"
async-trait = "0.1"
# SQLite for offline storage
tokio-rusqlite = "0.6"
rusqlite = { version = "0.32", features = ["bundled"] }
chrono = { version = "0.4", features = ["serde"] }
directories = "5"
# Secure credential storage (system keyring with encrypted file fallback)
keyring = "3"
aes-gcm = "0.10"
base64 = "0.22"
sha2 = "0.10"
getrandom = "0.2"
log = "0.4"
env_logger = "0.11"
# Persistent, rotating, redacted logging on every platform -- and on Android the
# only thing that puts Rust output into logcat at all (env_logger writes to
# stdout, which Android discards, which is why the backend was invisible on the
# platform where the hardest bugs live).
#
# TRACES: UR-078 | DR-218
tauri-plugin-log = "2"
# Zip for the diagnostics export bundle.
zip = { version = "2", default-features = false, features = ["deflate"] }
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
specta-typescript = "=0.0.9"
specta = { version = "=2.0.0-rc.22", features = ["chrono", "derive"] }
tiny_http = { version = "0.12.0", default-features = false }
# In-app update, desktop only.
#
# `cfg(desktop)` is not decoration: tauri-plugin-updater does not support
# Android at all -- an APK cannot replace itself, that is the package manager's
# job -- and building it for the Android target fails. Android is offered the
# releases page through tauri-plugin-opener instead (see the frontend's
# updateCheck module). tauri-plugin-process supplies the relaunch that has to
# follow a desktop install.
#
# The cfg is spelled out as "not android, not iOS" rather than `cfg(desktop)`:
# Cargo evaluates a [target.'cfg(...)'] table against *target-triple* cfgs only
# (target_os, target_arch, target_family, unix/windows). `desktop` is a cfg
# Tauri's build script emits for use in Rust source, so `cfg(desktop)` here
# matches nothing, silently drops the dependency, and the build then fails much
# later with "Permission updater:default not found".
#
# TRACES: UR-077 | DR-217
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
# Linux-specific dependencies
[target.'cfg(target_os = "linux")'.dependencies]
hostname = "0.4"
libc = "0.2"
# The crates.io release of libmpv predates the MPV versions we support, so this
# tracks the upstream git repo.
#
# Pinned by `rev`, not `branch = "master"`. With a branch, the revision is
# whatever Cargo.lock happens to hold and any `cargo update` silently swaps in
# new upstream code -- for the one dependency here that is not from crates.io,
# is not signed, and links a C library into the player. The rev below is the
# commit the lockfile already resolved to, so this pins current behaviour rather
# than changing it. To take upstream fixes, bump this deliberately.
libmpv = { git = "https://github.com/ParadoxSpiral/libmpv-rs.git", rev = "3e6c389b716f52a595cc5e8e3fa1f96cb76b3de7" }
# JNI for Android ExoPlayer integration
[target.'cfg(target_os = "android")'.dependencies]
jni = "0.21"
ndk-context = "0.1"
[dev-dependencies]
tempfile = "3.24.0"