Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog.d/fixed-turning-debug-logging-off-now-takes-fb2e.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
_2026-08-26_

## English

Turning Debug logging off now takes effect in the LSPosed hooks without a reboot. The flag was refreshed only by a filesystem watcher, and when that watcher stopped delivering events the hooks kept writing to logcat for as long as the device stayed up.

## Русский

Выключение отладочного лога теперь доходит до LSPosed-хуков без перезагрузки. Раньше флаг обновлялся только по событию файловой системы, и если это событие терялось, хуки продолжали писать в logcat до перезагрузки устройства.
29 changes: 18 additions & 11 deletions lsposed/app/src/main/kotlin/dev/okhsunrog/vpnhide/hook/HookLog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import dev.okhsunrog.vpnhide.VpnHideLog
* system_server logcat facade for LSPosed hooks, which can't reach the app's
* canonical-snapshot cache. Same gate-and-level policy as [VpnHideLog] (see
* [GatedLogger]) — info is per-request/hot-path and gated, error always prints
* so "hooks didn't attach" reports stay diagnosable — but the flag is read
* straight from the canonical JSON on [install] and refreshed by an inotify
* watcher, so a toggle flip lands without restarting system_server.
* so "hooks didn't attach" reports stay diagnosable — but the flag is owned by
* [SystemServerConfigCache], which re-reads the canonical JSON on the same 1s
* stat poll that keeps the hooks' targets current. A toggle flip therefore lands
* without restarting system_server, and without trusting inotify to deliver it.
*
* Every line also goes to `XposedBridge.log`, keeping Settings → Debugging →
* Debug logging visible through framework UIs that surface the Xposed log
Expand All @@ -23,23 +24,29 @@ internal object HookLog : GatedLogger() {
@Volatile private var watcher: FileObserver? = null

fun install() {
reload()
// Priming read: SystemServerConfigCache sets [enabled] whenever it
// installs a fresh config, so this both loads the config and seeds the
// flag. It is also the only step required for the flag to stay correct
// — the watcher below is latency, not correctness.
SystemServerConfigCache.load()
if (watcher != null) return
// MODIFY covers manual in-place edits; MOVED_TO/CLOSE_WRITE from
// watchSystemDataDir cover the app's atomic JSON replacement.
// Makes a toggle flip land immediately instead of within the cache's 1s
// stat interval. MODIFY covers manual in-place edits; MOVED_TO/CLOSE_WRITE
// from watchSystemDataDir cover the app's atomic JSON replacement.
//
// Do NOT make anything depend on this firing: on at least one device it
// stopped delivering events entirely (see the note in
// SystemServerConfigCache.load), which is exactly how the debug flag once
// got stuck on for days.
watcher =
watchSystemDataDir(extraEvents = FileObserver.MODIFY) { path ->
if (path == "vpnhide_config.json") {
SystemServerConfigCache.invalidate()
reload()
SystemServerConfigCache.load()
}
}
}

private fun reload() {
enabled = SystemServerConfigCache.load().debug
}

override fun emit(
priority: Int,
tag: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ internal object SystemServerConfigCache {

val result = readConfig()
val loadedFingerprint = fingerprint()
// The debug-logging flag rides this same 1s stat poll as everything
// else the hooks read. It used to be refreshed only by HookLog's
// FileObserver, and on a Pixel 8 Pro that observer silently stopped
// delivering: an atomic rename over the config produced no callback
// at all, so the flag stayed frozen at its boot value for five days
// while this cache — reading the very same file — was current the
// whole time. Turning Debug logging off in the app simply never
// reached system_server, and the hooks kept naming target UIDs in
// logcat. Correctness must not depend on inotify.
HookLog.enabled = result.debug
HookLog.i(
"VpnHide: system_server config loaded " +
"java=${result.javaTargetAppIds.size} observer=${result.observerAppIds.size} " +
Expand Down