Skip to content

Conversation

@caldrian
Copy link
Contributor

@caldrian caldrian commented Nov 6, 2025

SpikeWPB-14297 Reduce number of logs in production

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

  • Title contains a reference JIRA issue number like [WPB-XXX].
  • Description is filled and free of optional paragraphs.
  • Adds/updates automated tests.

UI accessibility checklist

If your PR includes UI changes, please utilize this checklist:

  • Make sure you use the API for UI elements that support large fonts.
  • All colors are taken from WireDesign.ColorTheme or constructed using WireDesign.BaseColorPalette.
  • New UI elements have Accessibility strings for VoiceOver.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 6, 2025

Test Results

1 902 tests   1 875 ✅  2m 16s ⏱️
  304 suites     27 💤
    3 files        0 ❌

Results for commit a52d55e.

♻️ This comment has been updated with latest results.

@caldrian caldrian marked this pull request as draft November 7, 2025 12:30
@caldrian caldrian marked this pull request as ready for review November 7, 2025 12:40
@caldrian caldrian changed the title feat: create new logger API - WPB-14297 feat: create new logger API - WPB-14297x Nov 28, 2025
@caldrian caldrian changed the title feat: create new logger API - WPB-14297x feat: create new logger API - WPB-14297 Nov 28, 2025
@caldrian caldrian requested review from Copilot and netbe November 28, 2025 11:10
Copy link
Contributor

Copilot AI left a comment

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 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, and WireLogAttribute for structured, secure logging
  • WireTaggedLogger and protocol-based API with log level methods (debug, info, notice, warn, error, critical)
  • OSLogHandler implementation 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 {
Copy link

Copilot AI Nov 28, 2025

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.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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.

Comment on lines +107 to +109
queue.async {
self.evictStaleEntries()
}
Copy link

Copilot AI Nov 28, 2025

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.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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.

Comment on lines +44 to +48
var attributesString = ""
for attributesKey in attributes.keys.sorted() {
attributesString += "[\(attributesKey)=\(attributes[attributesKey]!)]"
}

Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

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.

Suggested change
var attributesString = ""
for attributesKey in attributes.keys.sorted() {
attributesString += "[\(attributesKey)=\(attributes[attributesKey]!)]"
}
let attributesString = attributes.keys.sorted()
.map { "[\($0)=\(attributes[$0]!)]" }
.joined()

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +27
@Test
func staticStringIsNotObfuscated() async throws {
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
@Test
func staticStringInterpolationIsNotObfuscated() async throws {
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.

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)
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

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.

Suggested change
public static let appVersionMigration = WireTaggedLogger(tag: "api-version-migration", handler: logHandler)
public static let appVersionMigration = WireTaggedLogger(tag: "app-version-migration", handler: logHandler)

Copilot uses AI. Check for mistakes.
/// ```

public mutating func writeAttribute(_ attribute: WireLogAttribute) {
attributes += [attribute]
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

Using += with a single-element array creates unnecessary array allocations. Use attributes.append(attribute) for better performance.

Suggested change
attributes += [attribute]
attributes.append(attribute)

Copilot uses AI. Check for mistakes.
@wireapp wireapp deleted a comment from Copilot AI Nov 28, 2025
Copy link
Collaborator

@netbe netbe left a comment

Choose a reason for hiding this comment

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

left a couple of questions before approving

@@ -0,0 +1,86 @@
# ``WireLogging``
Copy link
Collaborator

Choose a reason for hiding this comment

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

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)
Copy link
Collaborator

Choose a reason for hiding this comment

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

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")
Copy link
Collaborator

Choose a reason for hiding this comment

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

question: should we just return ? or be assertionFailure?

//

import Foundation
import os
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)?

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.

3 participants