-
Notifications
You must be signed in to change notification settings - Fork 25
feat: create new logger API - WPB-14297 #3849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: chore/rename-wirelogging-to-wirelegacylogging-WPB-14876
Are you sure you want to change the base?
Changes from 15 commits
41df6da
615eed0
91d5c8a
7afd857
14044fa
1c8fa7d
2d83cfd
abdbe34
6061f61
f4c7f69
9687c31
6ec2355
b05277a
560e6c8
8918320
898ced4
a52d55e
fc46221
b5e9410
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| # ``WireLogging`` | ||
|
|
||
| A secure logging framework that prevents accidental logging of sensitive data by restricting string interpolation. | ||
|
|
||
| ## Overview | ||
|
|
||
| The `WireLogging` framework provides a type-safe logging API that prevents accidental exposure of sensitive information in logs. Unlike standard Swift string interpolation, `WireLogMessage` only allows `StaticString` values to be interpolated by default, ensuring that dynamic values cannot be accidentally logged without explicit handling. | ||
|
|
||
| ## Security Model | ||
|
|
||
| The core principle of `WireLogging` is that **only compile-time constants (`StaticString`) can be interpolated directly**. Any attempt to interpolate dynamic values will fail at compile time: | ||
|
|
||
| ```swift | ||
| // ✅ Allowed - StaticString | ||
| logger.info("User logged in") | ||
|
|
||
| // ✅ Allowed - StaticString interpolation | ||
| let name = "World" as StaticString | ||
| logger.info("Hello, \(name)!") | ||
|
|
||
| // ❌ Compile error - String is not allowed | ||
| let userId = "12345" | ||
| logger.info("User ID: \(userId)") // Error: no matching appendInterpolation | ||
|
|
||
| // ❌ Compile error - Int is not allowed | ||
| let count = 42 | ||
| logger.info("Count: \(count)") // Error: no matching appendInterpolation | ||
| ``` | ||
|
|
||
| ## Extending for Custom Types | ||
|
|
||
| To log custom types or dynamic values, you must explicitly extend `WireLogInterpolation` and implement `appendInterpolation` methods. This ensures that: | ||
|
|
||
| 1. Logging of sensitive data is intentional | ||
| 2. Appropriate obfuscation can be applied | ||
| 3. Structured attributes can be added for better log analysis | ||
|
|
||
| ### Example: Logging a UUID | ||
|
|
||
| ```swift | ||
| extension WireLogInterpolation { | ||
| mutating func appendInterpolation(_ userID: UUID) { | ||
| // Obfuscate sensitive data | ||
| let obfuscated = String(userID.uuidString.prefix(8)) + "***" | ||
| writeText(obfuscated) | ||
|
|
||
| // Add structured attribute for log analysis | ||
| writeAttribute(.selfUserID(userID)) | ||
| } | ||
| } | ||
|
|
||
| // Now this compiles and safely logs the UUID | ||
| let userId = UUID() | ||
| logger.info("Processing request for user: \(userId)") | ||
| ``` | ||
|
|
||
| ### Example: Logging a Custom Type | ||
|
|
||
| ```swift | ||
| struct User { | ||
| let id: UUID | ||
| let email: String | ||
| let password: String // Sensitive! | ||
| } | ||
|
|
||
| extension WireLogInterpolation { | ||
| mutating func appendInterpolation(_ user: User) { | ||
| // Only log safe information | ||
| writeText("User(id: \(user.id.uuidString.prefix(8))***)") | ||
|
|
||
| // Never log sensitive fields like password | ||
| // Add structured attributes for analysis using predefined methods | ||
| writeAttribute(.selfUserID(user.id)) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Building Log Messages | ||
|
|
||
| When implementing `appendInterpolation`, use: | ||
|
|
||
| - **`writeText(_:)`** - Adds text content to the log message. The provided value is **not obfuscated**, so ensure sensitive data is handled before calling this method. | ||
| - **`writeAttribute(_:)`** - Adds structured attributes that can be used for log analysis. Attributes are separate from the message content and may be formatted differently by the logging handler. Prefer using predefined static methods on `WireLogAttribute` (e.g., `.selfUserID(_:)`) rather than initializing new instances directly. | ||
|
|
||
| ## Topics | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,156 @@ | ||||||||||||||||
| // | ||||||||||||||||
| // Wire | ||||||||||||||||
| // Copyright (C) 2025 Wire Swiss GmbH | ||||||||||||||||
| // | ||||||||||||||||
| // This program is free software: you can redistribute it and/or modify | ||||||||||||||||
| // it under the terms of the GNU General Public License as published by | ||||||||||||||||
| // the Free Software Foundation, either version 3 of the License, or | ||||||||||||||||
| // (at your option) any later version. | ||||||||||||||||
| // | ||||||||||||||||
| // This program is distributed in the hope that it will be useful, | ||||||||||||||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||||||||||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||||||||||||
| // GNU General Public License for more details. | ||||||||||||||||
| // | ||||||||||||||||
| // You should have received a copy of the GNU General Public License | ||||||||||||||||
| // along with this program. If not, see http://www.gnu.org/licenses/. | ||||||||||||||||
| // | ||||||||||||||||
|
|
||||||||||||||||
| import Foundation | ||||||||||||||||
| import os | ||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: So this is only the osLog implementation given by Apple right? What about Cocoalumberjack (to write to filesystem)? |
||||||||||||||||
|
|
||||||||||||||||
| public struct OSLogHandler: WireLogHandlerProtocol { | ||||||||||||||||
|
|
||||||||||||||||
| var subsystem: String | ||||||||||||||||
| private let cache: LoggerCache | ||||||||||||||||
|
|
||||||||||||||||
| public init(subsystem: String) { | ||||||||||||||||
| self.subsystem = subsystem | ||||||||||||||||
| self.cache = LoggerCache(subsystem: subsystem) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| public func log( | ||||||||||||||||
| tag: WireLogTag, | ||||||||||||||||
| type: WireLogType, | ||||||||||||||||
| message: WireLogMessage, | ||||||||||||||||
| additionalAttributes: [WireLogAttribute] | ||||||||||||||||
| ) { | ||||||||||||||||
|
|
||||||||||||||||
| var attributes = [String: String]() // additionalAttributes overwrite message attributes | ||||||||||||||||
| for attribute in message.interpolation.attributes + additionalAttributes { | ||||||||||||||||
| attributes[attribute.key] = attribute.value | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| var attributesString = "" | ||||||||||||||||
| for attributesKey in attributes.keys.sorted() { | ||||||||||||||||
| attributesString += "[\(attributesKey)=\(attributes[attributesKey]!)]" | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
Comment on lines
+44
to
+48
|
||||||||||||||||
| var attributesString = "" | |
| for attributesKey in attributes.keys.sorted() { | |
| attributesString += "[\(attributesKey)=\(attributes[attributesKey]!)]" | |
| } | |
| let attributesString = attributes.keys.sorted() | |
| .map { "[\($0)=\(attributes[$0]!)]" } | |
| .joined() |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I can replace it with locking.
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A valid point, I'll think about it.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // | ||
| // Wire | ||
| // Copyright (C) 2025 Wire Swiss GmbH | ||
| // | ||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
| // | ||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
| // | ||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see http://www.gnu.org/licenses/. | ||
| // | ||
|
|
||
| // sourcery: AutoMockable | ||
| public protocol WireLogHandlerProtocol: Sendable { | ||
|
|
||
| /// Logs a message with the specified tag, type, and attributes. | ||
| /// | ||
| /// - Parameters: | ||
| /// - tag: The log tag used to categorize the log message. | ||
| /// - type: The severity level of the log message. | ||
| /// - message: The log message containing content and attributes. | ||
| /// - additionalAttributes: Additional attributes to include with the log message. | ||
| /// Note: Attributes in `additionalAttributes` override any attributes in `message` with the same key. | ||
|
|
||
| func log( | ||
| tag: WireLogTag, | ||
| type: WireLogType, | ||
| message: WireLogMessage, | ||
| additionalAttributes: [WireLogAttribute] | ||
| ) | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // | ||
| // Wire | ||
| // Copyright (C) 2025 Wire Swiss GmbH | ||
| // | ||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
| // | ||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
| // | ||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see http://www.gnu.org/licenses/. | ||
| // | ||
|
|
||
| public import Foundation | ||
|
|
||
| public extension WireLogAttribute { | ||
|
|
||
| static var processID: WireLogAttribute { .init("process_id", "\(ProcessInfo.processInfo.processIdentifier)") } | ||
| static var processName: WireLogAttribute { .init("process_name", ProcessInfo.processInfo.processName) } | ||
|
|
||
| static func selfUserID(_ value: UUID) -> WireLogAttribute { .init("self_user_id", value.uuidString) } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // | ||
| // Wire | ||
| // Copyright (C) 2025 Wire Swiss GmbH | ||
| // | ||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
| // | ||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
| // | ||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see http://www.gnu.org/licenses/. | ||
| // | ||
|
|
||
| public struct WireLogAttribute: Sendable { | ||
|
|
||
| public var key: String | ||
| public var value: String | ||
|
|
||
| public init( | ||
| key: String, | ||
| value: String | ||
| ) { | ||
| self.key = key | ||
| self.value = value | ||
| } | ||
|
|
||
| public init( | ||
| _ key: String, | ||
| _ value: String | ||
| ) { | ||
| self.init(key: key, value: value) | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: really clear documentation 🙏