feat: create new logger API - WPB-14297#3849
feat: create new logger API - WPB-14297#3849caldrian wants to merge 19 commits intochore/rename-wirelogging-to-wirelegacylogging-WPB-14876from
Conversation
Test Results1 883 tests 1 856 ✅ 2m 14s ⏱️ Results for commit b5e9410. ♻️ This comment has been updated with latest results. |
…' into feat/create-new-logger-WPB-14297
…' into feat/create-new-logger-WPB-14297
…' into feat/create-new-logger-WPB-14297
…' into feat/create-new-logger-WPB-14297
There was a problem hiding this comment.
Pull request overview
This PR introduces a new type-safe logging API (WireLogging) that prevents accidental logging of sensitive data by restricting automatic string interpolation to StaticString values only. The design forces developers to explicitly define how custom types are converted to log messages through WireLogInterpolation extensions.
Key changes:
- New logging infrastructure with
WireLogMessage,WireLogInterpolation, andWireLogAttributefor structured, secure logging WireTaggedLoggerand protocol-based API with log level methods (debug, info, notice, warn, error, critical)OSLogHandlerimplementation with logger caching and tag-based categorization
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| WireLogInterpolationTests.swift | Basic tests for static string interpolation |
| sourcery.yml | Configuration update to import WireLogging in generated mocks |
| AutoMockable.manual.swift | Manual mock for WireTaggedLoggerProtocol |
| WireTaggedLoggerProtocol.swift | Protocol defining tagged logger interface |
| WireTaggedLogger.swift | Tagged logger implementation with log level methods |
| WireLogger+Singletons.swift | Static logger instances initialized with shared handler |
| WireLogType.swift | Enum defining log levels (renamed from WireLogLevel) |
| WireLogTag.swift | String-based tag type for categorizing logs |
| WireLogMessage.swift | Log message type supporting custom interpolation |
| WireLogInterpolation.swift | Custom string interpolation restricting to StaticString by default |
| WireLogAttribute.swift | Key-value pair for structured log metadata |
| WireLogAttribute+predefined.swift | Predefined attribute helpers |
| WireLogHandlerProtocol.swift | Protocol for log handlers |
| OSLogHandler.swift | os.Logger-based handler with caching |
| Documentation.md | Comprehensive documentation for the logging API |
| WireLogger+Instances.swift | Minor formatting change in legacy logger |
| Package.swift | Added WireLogging dependency to WireLegacyLogging |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| func logger(for tag: WireLogTag) -> Logger { | ||
| // Synchronously get or create logger and update access time | ||
| let logger = queue.sync { |
There was a problem hiding this comment.
Every log call triggers an async eviction task (line 107-109), which creates many unnecessary async dispatches. Consider adding a throttling mechanism (e.g., only evict every N calls or after a time interval) or moving eviction to a background timer to reduce overhead.
There was a problem hiding this comment.
Maybe I can replace it with locking.
| queue.async { | ||
| self.evictStaleEntries() | ||
| } |
There was a problem hiding this comment.
Every log call triggers an async eviction task (line 107-109), which creates many unnecessary async dispatches. Consider adding a throttling mechanism (e.g., only evict every N calls or after a time interval) or moving eviction to a background timer to reduce overhead.
There was a problem hiding this comment.
A valid point, I'll think about it.
| var attributesString = "" | ||
| for attributesKey in attributes.keys.sorted() { | ||
| attributesString += "[\(attributesKey)=\(attributes[attributesKey]!)]" | ||
| } | ||
|
|
There was a problem hiding this comment.
String concatenation in a loop (line 46) creates multiple intermediate String objects. Use a String builder pattern or join the array for better performance, especially when many attributes are present.
| var attributesString = "" | |
| for attributesKey in attributes.keys.sorted() { | |
| attributesString += "[\(attributesKey)=\(attributes[attributesKey]!)]" | |
| } | |
| let attributesString = attributes.keys.sorted() | |
| .map { "[\($0)=\(attributes[$0]!)]" } | |
| .joined() |
| @Test | ||
| func staticStringIsNotObfuscated() async throws { |
There was a problem hiding this comment.
The new logging infrastructure lacks comprehensive test coverage. Missing tests include: WireLogAttribute handling, OSLogHandler behavior, logger caching in OSLogHandler, WireTaggedLogger methods, and the setup/singleton pattern in WireLogger. Consider adding tests for these critical components.
| @Test | ||
| func staticStringInterpolationIsNotObfuscated() async throws { |
There was a problem hiding this comment.
The new logging infrastructure lacks comprehensive test coverage. Missing tests include: WireLogAttribute handling, OSLogHandler behavior, logger caching in OSLogHandler, WireTaggedLogger methods, and the setup/singleton pattern in WireLogger. Consider adding tests for these critical components.
|
|
||
| public static let analytics = WireTaggedLogger(tag: "analytics", handler: logHandler) | ||
| public static let apiMigration = WireTaggedLogger(tag: "api-migration", handler: logHandler) | ||
| public static let appVersionMigration = WireTaggedLogger(tag: "api-version-migration", handler: logHandler) |
There was a problem hiding this comment.
The tag "api-version-migration" does not match the logger name appVersionMigration. This inconsistency could cause confusion. Consider renaming the tag to "app-version-migration" for consistency.
| public static let appVersionMigration = WireTaggedLogger(tag: "api-version-migration", handler: logHandler) | |
| public static let appVersionMigration = WireTaggedLogger(tag: "app-version-migration", handler: logHandler) |
| /// ``` | ||
|
|
||
| public mutating func writeAttribute(_ attribute: WireLogAttribute) { | ||
| attributes += [attribute] |
There was a problem hiding this comment.
Using += with a single-element array creates unnecessary array allocations. Use attributes.append(attribute) for better performance.
| attributes += [attribute] | |
| attributes.append(attribute) |
netbe
left a comment
There was a problem hiding this comment.
left a couple of questions before approving
| @@ -0,0 +1,86 @@ | |||
| # ``WireLogging`` | |||
There was a problem hiding this comment.
praise: really clear documentation 🙏
| /// This method must be called very early on app start, before any other interaction with ``WireLogger``. | ||
|
|
||
| public static func setup(_ logHandler: any WireLogHandlerProtocol) { | ||
| precondition(Thread.isMainThread) |
There was a problem hiding this comment.
question: why is this check necessary?
| public static func setup(_ logHandler: any WireLogHandlerProtocol) { | ||
| precondition(Thread.isMainThread) | ||
| guard self.logHandler == nil else { | ||
| fatalError("WireLogger is already set up") |
There was a problem hiding this comment.
question: should we just return ? or be assertionFailure?
| // | ||
|
|
||
| import Foundation | ||
| import os |
There was a problem hiding this comment.
question: So this is only the osLog implementation given by Apple right? What about Cocoalumberjack (to write to filesystem)?
|
This PR is stale because it has been open 30 days with no activity. Please update it or close it in case is not relevant anymore. |
…' into feat/create-new-logger-WPB-14297
Issue
This is the second PR in a series,
This PR creates a new interface for logging, which forces calling code to explicitly specify how types are converted to a string.
Checklist
[WPB-XXX].UI accessibility checklist
If your PR includes UI changes, please utilize this checklist: