Apologies, when I clicked your link it just took me to a short paragraph, I didn't see the much more detailed proposal in the repo tab there.
That relieves my concerns about ServiceContext. Per-module isolation does defeat the cross-cutting propagation that's its entire goal, and you're right there's no added dependency since MetadataProvider already inverts the context read.
But I would like you to address the following points.
Tests:
Testing with task-local logger is similar in spirit to testing API with the explicit
logger:param, but with tests running in separate closures:
I meant integration/UI/end-to-end tests with a live config against a QA environment on a release build with third party dependencies. It's the last stop before millions of customers in prod. Having those tests be flaky or non-deterministic is my concern.
Reliance on Discipline with Shared State
The proposal states:
Second, the leak case is disciplinable. The only way library code propagates its own metadata downstream through
Logger.currentis by callingwithLogger(mergingMetadata:)orwithLogger(metadata:). The documented contract is that those overloads are application-side APIs: libraries readLogger.currentand use per-statementmetadata:or a local copy for their own context; they do not push. Per-module isolation would make the leak impossible by construction even when discipline fails but it would also fragment cross-cutting metadata propagation (request.idaccumulated at the application layer would not appear on library log lines without an explicit bridge from one task-local to another) — which is the propagation we most want by default.
A few comments/questions:
- Disciplinable by whom? Genuine question. See my discussion on security below.
- The reason we have access control, sealed classes, etc. is because documented contracts aren't reliable. Mixing a reliance on good behavior with shared state sets off a lot of my red flags, frankly.
- We can agree that isolation makes the bad behavior impossible, but it should be the rule not the exception. A general principle of Swift is that if you're going to do unsafe things that violate isolation then it should never be implicit. I feel like your proposal wants to compromise on that for the sake of the convenience not to have to write extra lines into APIs and call sites. It sees to prioritize developer ergonomics and convenience over what I'd argue are more important priorities in the big picture.
- What is the safeguard against an upstream library rebinding the handler?
- What is there to prevent this kind of situation?
// some BlueToothLib somewhere...
func pollRSSI(_ device: Device) async {
while connected {
Logger.current.debug("RSSI \(device.rssi)")
try? await Task.sleep(for: .milliseconds(10))
}
}
... where an app—correctly using a binding overload, no discipline broken anywhere?—bound logLevel: .trace for a scope that happens to enclose this poll, maybe to debug something unrelated. What is to stop this suddenly firing hundreds of times a second through the app's real handler? What will happen to every downstream library's debug/trace logging?
6. Can a library accidentally bind a .critical level or a /dev/null handler and silence a callee?
Security
- Does this change widen the attack surface and possibility space for things like, log injection, log masking, DoL/DoS, disk space exhaustion via spamming, user info leakage, etc.? Recalling:
Focus
Available for: macOS Sequoia
Impact: An app may be able to access sensitive user data
Description: A logging issue was addressed with improved data redaction.
CVE-2026-20668: Kirin (@Pwnrin)
- While a CWE-117-safe handler helps deal with certain attacks, my structural question is, doesn't this ambient design mean every handler in the ecosystem must now be injection-safe? Can any handler perhaps receive attacker-tainted metadata injected by some upstream layer?
Thanks. This phase is when to think critically about these proposals, thanks for being responsive about it.