Skip to content

Skip handler dispatch for setters under MaxLogLevelNone#465

Merged
rnro merged 4 commits into
apple:mainfrom
rnro:reduce_MaxLogLevelNone_costs
May 21, 2026
Merged

Skip handler dispatch for setters under MaxLogLevelNone#465
rnro merged 4 commits into
apple:mainfrom
rnro:reduce_MaxLogLevelNone_costs

Conversation

@rnro
Copy link
Copy Markdown
Contributor

@rnro rnro commented May 20, 2026

Skip handler dispatch for setters under MaxLogLevelNone

Motivation

  • When MaxLogLevelNone is enabled every Logger.trace/debug/.../critical method and Logger.log(level:) compiles down to an empty body, so no log statement can ever observe what the underlying LogHandler sees.
  • Despite this, the subscript(metadataKey:) and logLevel setters still went through the handler. Code like logger[metadataKey: "user-id"] = "\(id)" would CoW the logger storage and call into the handler even though nothing could read the metadata back out via a log statement.

Modifications

  • Wrap the bodies of Logger.subscript(metadataKey:) { set } and Logger.logLevel { set } in #if !MaxLogLevelNone, mirroring the existing guards on the level-specific log methods.
  • Note this behaviour in DisableLogLevelsDuringCompilation.md so callers know reads still go through the handler but writes do not.
  • Add a new benchmark which ensures that the operations don't allocate.
  • Update some missed swift-tools-version bumps.
  • Loosen p90 instruction threshold to 10%. The same source produces results that swing by up to ~8% between back-to-back runs.

Result

rnro added 2 commits May 20, 2026 15:56
Motivation

* The root `Package.swift` and other targets are on
  `swift-tools-version:6.2`. The three benchmark manifests were left at
  `6.1` by oversight.

Modifications

* Update `swift-tools-version` from `6.1` to `6.2` in
  `Benchmarks/Package.swift`, `Benchmarks/NoTraits/Package.swift`, and
  `Benchmarks/MaxLogLevelWarning/Package.swift`.

Result

* All benchmark manifests target the same Swift tools version as the
  rest of the repository.
Motivation

* When `MaxLogLevelNone` is enabled every `Logger.trace`/`debug`/.../
  `critical` method and `Logger.log(level:)` compiles down to an empty
  body, so no log statement can ever observe what the underlying
  `LogHandler` sees.
* Despite this, the `subscript(metadataKey:)` and `logLevel` setters
  still went through the handler. Code like
  `logger[metadataKey: "user-id"] = "\(id)"` would CoW the logger
  storage and call into the handler even though nothing could read the
  metadata back out via a log statement.

Modifications

* Wrap the bodies of `Logger.subscript(metadataKey:) { set }` and
  `Logger.logLevel { set }` in `#if !MaxLogLevelNone`, mirroring the
  existing guards on the level-specific log methods.
* Note this behaviour in `DisableLogLevelsDuringCompilation.md` so
  callers know reads still go through the handler but writes do not.

Result

* `logger[metadataKey:] = ...` and `logger.logLevel = ...` compile to
  empty bodies when `MaxLogLevelNone` is set, matching the
  zero-runtime-overhead guarantee the trait already provides for log
  emission.
@rnro rnro added the 🔨 semver/patch No public API change. label May 20, 2026
Motivation

* `Benchmarks/MaxLogLevelWarning/` already pins `objectAllocCount: 0`
  for its trait via `package-benchmark` thresholds, so any regression
  that re-introduces a path through the `LogHandler` would fail CI.
* There was no equivalent subpackage for `MaxLogLevelNone`, which is
  what let the metadata subscript and `logLevel` setters slip through
  unguarded.

Modifications

* Add `Benchmarks/MaxLogLevelNone/`, mirroring
  `Benchmarks/MaxLogLevelWarning/`: a `Package.swift` depending on
  `swift-log` with the `MaxLogLevelNone` trait, a `.gitignore`, and
  benchmark cases for `critical`, `log(level: .critical, ...)`,
  `logger[metadataKey:] = ...`, and `logger.logLevel = ...`.
* Check in the `Xcode swift 6.3` threshold baselines so each benchmark
  asserts `objectAllocCount: 0` and a p90 instruction ceiling.
* Add a `macos-benchmarks-MaxLogLevelNoneBenchmarks-trait` job to both
  `pull_request.yml` and `main.yml`, requesting Swift 6.3 to match the
  other trait benchmark job, chained after the `MaxLogLevelWarning`
  job to keep the macOS benchmark runs serialised.

Result

* A future change that re-introduces handler dispatch for any of the
  guarded paths under `MaxLogLevelNone` trips the `objectAllocCount: 0`
  threshold and fails CI.
@rnro rnro force-pushed the reduce_MaxLogLevelNone_costs branch 2 times, most recently from 54bc472 to 573c380 Compare May 20, 2026 21:42
@rnro rnro marked this pull request as ready for review May 20, 2026 21:45
Motivation

* The relative p90 instruction threshold in `BenchmarksFactory` is set
  to 2%, but on the macOS CI runner the same source produces results
  that swing by up to ~8% between back-to-back runs (worst observed
  was the `MaxLogLevelNone` `_metadata_set` benchmark at +7.6%), so
  the threshold trips on noise rather than on real regressions.
* Chasing the moving baseline by repeatedly amending the checked-in
  threshold JSONs doesn't fix this — each refresh would just need to
  be refreshed again.

Modifications

* Bump the `.instructions` relative p90 threshold from `2.0` to `10.0`
  in `Benchmarks/Sources/BenchmarksFactory/MakeBenchmark.swift`.
* Leave the `.objectAllocCount` absolute threshold of `0` alone — that
  is the load-bearing invariant the benchmarks exist to police.

Result

* Normal runner-to-runner instruction count jitter no longer fails the
  benchmark jobs while still flagging anything that drifts more than
  10%.
@rnro rnro force-pushed the reduce_MaxLogLevelNone_costs branch from 573c380 to b29c6a0 Compare May 21, 2026 10:46
@rnro rnro merged commit 1069d31 into apple:main May 21, 2026
57 of 58 checks passed
@rnro rnro deleted the reduce_MaxLogLevelNone_costs branch May 21, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logger metadata setter is not a no-op when MaxLogLevelNone trait is enabled

2 participants