Skip to content

feat(logging): rotate the sandbox file log into a Logs/ subdirectory#5

Merged
tkgstrator merged 1 commit into
developfrom
feat/log-rotation-clean
Jun 22, 2026
Merged

feat(logging): rotate the sandbox file log into a Logs/ subdirectory#5
tkgstrator merged 1 commit into
developfrom
feat/log-rotation-clean

Conversation

@tkgstrator

Copy link
Copy Markdown
Contributor

Supersedes #4 (`style:` type isn't in the commitlint enum — that PR is now closed and the same change is squashed into one clean commit here).

What

Replaces the previous "single append-only `.log`" with a size-bounded rotation scheme so a long-running consumer can't fill the sandbox with a multi-GB log.

  • Files now live under `/Logs/` (created on init), so an operator can grab the whole rotated set (`.log` + `.{1..N-1}.log`) by copying one directory instead of fishing individual files out. PascalCase matches iOS's own sandbox conventions (`Documents/`, `Library/`, etc.). Base stays the existing `tmp/` or, with `IPA_LOG_TO_DOCUMENTS=1`, the Files.app-visible `Documents/`.
  • `IPALogPath` samples the current size every `IPA_LOG_ROTATE_CHECK_EVERY` writes (default 64) and, once it crosses `IPA_LOG_MAX_BYTES` (default 4 MiB), shifts `.log → .1.log → … → .{N-1}.log`, dropping anything past N-1. N defaults to 3 generations (so up to 12 MiB on disk per consumer at the default ceiling).
  • All three knobs (max bytes, generation count, sample interval) are `-D` overridable at build time so a noisy consumer can dial up retention without forking this file.

Why

Bridge in particular has been emitting tens of MB per session (an 80 MB `.log` was seen in the field). The Files-app delivery path makes pulling such a file painful, and tail-only reads miss earlier context.

Design notes

  • Best-effort throughout — failed `mkdir` falls back to writing into the parent directory; failed rename / stat just leaves the file alone.
  • No explicit lock around rotate — the every-64-writes sampling cadence already keeps two concurrent writers from both rotating the same file in the same window, and a stray double-rotate just produces an empty `.1.log` instead of corrupting data. Adding a serial queue would buy stronger correctness at the cost of serialising all IPALog() calls; not worth the trade.
  • Sampling not exact — the size ceiling can be exceeded by up to one batch's worth of writes (≤ 64 lines). Acceptable per the bug report ("ファイルサイズは厳密じゃなくていい").

Test plan

  • Compiles against the post-feat(logging): add debug-only LAN TCP log stream on port 18082 #2 develop tip (logserver.h / IPALogServerStart / IPALogServerPush still wired through unchanged).
  • On-device: write more than 4 MiB through `IPALog()` and confirm the directory ends up with `.log` (newest), `.1.log`, `.2.log`, and nothing newer.
  • On-device: with `IPA_LOG_TO_DOCUMENTS=1`, confirm `Documents/Logs/` shows up in Files.app and contains the rotated set.

🤖 Generated with Claude Code

Replaces the previous "single append-only <tag>.log" with a size-bounded
rotation scheme so a long-running consumer can't fill the sandbox with a
multi-GB log.

- Files now live under <base>/Logs/ (created on init), so an operator
  can grab the whole rotated set (<tag>.log + <tag>.{1..N-1}.log) by
  copying one directory instead of fishing individual files out.
  PascalCase matches iOS's own sandbox conventions (Documents/,
  Library/, etc.). Base stays the existing tmp/ or, with
  IPA_LOG_TO_DOCUMENTS=1, the Files.app-visible Documents/.
- IPALogPath samples the current size every IPA_LOG_ROTATE_CHECK_EVERY
  writes (default 64) and, once it crosses IPA_LOG_MAX_BYTES (default
  4 MiB), shifts <tag>.log -> <tag>.1.log -> ... -> <tag>.{N-1}.log,
  dropping anything past N-1. N defaults to 3 generations (so up to
  12 MiB on disk per consumer at the default ceiling).
- All three knobs (max bytes, generation count, sample interval) are
  -D overridable at build time so a noisy consumer can dial up retention
  without forking this file.

Best-effort throughout: failed mkdir falls back to writing into the
parent directory, and rotation runs without an explicit lock — the
sampling cadence already keeps two concurrent writers from both
rotating in the same window, and a stray double-rotate just produces
an empty .1.log instead of corrupting data.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 01:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the iOS sandbox file logger to prevent unbounded growth by introducing size-based log rotation and consolidating all log files under a dedicated Logs/ subdirectory (under either tmp/ or Documents/, depending on build flags).

Changes:

  • Add size-bounded rotation for the sandbox file log with configurable max size, generation count, and sampling interval (IPA_LOG_MAX_BYTES, IPA_LOG_GENERATIONS, IPA_LOG_ROTATE_CHECK_EVERY).
  • Move the sandbox log destination into <base>/Logs/, creating the directory on init with best-effort fallback to the base directory.
  • Introduce an atomic write counter to amortize filesystem size checks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread logging.m
Comment on lines +112 to +115
static void IPAMaybeRotate(NSString *path) {
unsigned n = atomic_fetch_add_explicit(&g_writeCount, 1, memory_order_relaxed);
if ((n % IPA_LOG_ROTATE_CHECK_EVERY) != 0) return;

Comment thread logging.m
Comment on lines +69 to +72
static os_log_t g_log = NULL;
static NSString *g_logSandbox = nil;
static NSString *g_tag = @"tweak";
static atomic_uint g_writeCount __attribute__((unused)) = 0;
Comment thread logging.m
Comment on lines +23 to +38
// File destination layout — every flavor writes into a `Logs/` subdirectory
// of its base path (created on init), so operators can grab the whole
// directory at once instead of fishing individual files out of tmp/ or
// Documents/. The directory name follows iOS's own sandbox PascalCase
// convention (sibling to `Documents/`, `Library/`). The default base is
// NSTemporaryDirectory() (which resolves to
// /var/mobile/Containers/Data/Application/<UUID>/tmp/), so the full path is
// .../tmp/Logs/<tag>.log
//
// When IPA_LOG_TO_DOCUMENTS=1 is defined at build time, the file destination
// is moved to <sandbox>/Documents/<tag>.log instead. That directory is
// exposed through Files.app once the host app's Info.plist carries
// UIFileSharingEnabled + LSSupportsOpeningDocumentsInPlace — typical for
// the statically-patched / sideload-injected distribution flavor where the
// operator has no SSH access. The same log can then be read over the
// Files app on a non-jailbroken device.
// When IPA_LOG_TO_DOCUMENTS=1 is defined at build time, the base moves to
// <sandbox>/Documents/ instead. That directory is exposed through Files.app
// once the host app's Info.plist carries UIFileSharingEnabled +
// LSSupportsOpeningDocumentsInPlace — typical for the statically-patched /
// sideload-injected distribution flavor where the operator has no SSH
// access. The same log can then be read over the Files app on a
// non-jailbroken device.
@tkgstrator
tkgstrator merged commit 9dde9ea into develop Jun 22, 2026
3 checks passed
@tkgstrator
tkgstrator deleted the feat/log-rotation-clean branch June 22, 2026 02:38
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