Files
obsidian-vault/work/wiki/apple-browsers/logging-guidelines.md
T

16 KiB

source, confidence, namespace, last_synced, alwaysApply
source confidence namespace last_synced alwaysApply
~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/logging-guidelines.mdc 0.9 work 2026-04-28 false

Logging Guidelines & Telemetry Capture

Overview

The DuckDuckGo browser apps for iOS and macOS leverage Apple's Unified Logging System for capturing telemetry and debugging information. This system enables efficient tracking of app behavior, issue diagnosis, and performance monitoring in a structured and privacy-conscious manner.

Key Benefits:

  • Privacy-first: Built-in privacy controls for sensitive data
  • Performance: Optimized for minimal overhead
  • Integration: Native Apple ecosystem support
  • Debugging: Rich contextual information and filtering

How to Log

Using the Logger Class

We utilize the Logger class from Apple's os framework for all logging activities:

import os

// Basic logging examples
Logger.yourFeatureName.debug("Something to log, with info: \(infoVar)")
Logger.anotherFeatureName.error("Some error happened: \(error.localizedDescription, privacy: .public)")
Logger.networking.info("API request completed for endpoint: \(endpoint, privacy: .public)")
Logger.performance.debug("Operation took \(duration)ms to complete")

Creating Custom Loggers

Single Feature Logger

For new features, create a dedicated logger file named Logger+YourFeatureName.swift:

import os

public extension Logger {
    static var yourFeatureName: Logger = { 
        Logger(subsystem: "Your Feature Name", category: "") 
    }()
    
    static var anotherFeatureName: Logger = { 
        Logger(subsystem: "Another feature name", category: "Subsystem in the feature") 
    }()
}

Multiple Feature Loggers

For related features, add to existing logger extensions (e.g., Logger+Multiple.swift):

import os

public extension Logger {
    // Networking loggers
    static var networking: Logger = { 
        Logger(subsystem: "Networking", category: "API") 
    }()
    
    static var cache: Logger = { 
        Logger(subsystem: "Networking", category: "Cache") 
    }()
    
    // UI loggers  
    static var tabManagement: Logger = { 
        Logger(subsystem: "UI", category: "Tab Management") 
    }()
    
    static var bookmarks: Logger = { 
        Logger(subsystem: "UI", category: "Bookmarks") 
    }()
}

Logger Placement Strategy

Framework/Package Level: For shared functionality across iOS and macOS

// In BrowserServicesKit
public extension Logger {
    static var secureVault: Logger = { 
        Logger(subsystem: "BrowserServicesKit", category: "SecureVault") 
    }()
    
    static var sync: Logger = { 
        Logger(subsystem: "BrowserServicesKit", category: "Sync") 
    }()
}

App Level: For platform-specific features

// In iOS app
public extension Logger {
    static var widgets: Logger = { 
        Logger(subsystem: "iOS App", category: "Widgets") 
    }()
}

// In macOS app  
public extension Logger {
    static var windowManagement: Logger = { 
        Logger(subsystem: "macOS App", category: "Window Management") 
    }()
}

Subsystem and Category Guidelines

Subsystem Naming

Purpose: Corresponds to large functional areas of your app

Examples:

  • "Networking" - All network-related functionality
  • "UI" - User interface components
  • "Data Storage" - Database and persistence
  • "Security" - Authentication and encryption
  • "Performance" - Performance monitoring and optimization

Category Naming

Purpose: Specific components or features within subsystems

Examples:

// Networking subsystem categories
Logger(subsystem: "Networking", category: "API Calls")
Logger(subsystem: "Networking", category: "Cache Management")
Logger(subsystem: "Networking", category: "Request Retry")

// UI subsystem categories  
Logger(subsystem: "UI", category: "Tab Management")
Logger(subsystem: "UI", category: "Settings")
Logger(subsystem: "UI", category: "Bookmarks")

// Data Storage subsystem categories
Logger(subsystem: "Data Storage", category: "SecureVault")
Logger(subsystem: "Data Storage", category: "Core Data")
Logger(subsystem: "Data Storage", category: "User Defaults")

Log Levels and Privacy

Choosing Log Levels

debug - Development and Troubleshooting

  • Purpose: Verbose output for development debugging
  • Retention: Short-lived in memory
  • Use cases: Variable values, execution flow, temporary debugging
Logger.networking.debug("Request headers: \(headers)")
Logger.ui.debug("User tapped button at coordinates: \(point)")
Logger.performance.debug("Cache hit for key: \(key)")

info - Important Events

  • Purpose: Interesting or important information
  • Retention: Longer than debug, available for analysis
  • Use cases: User actions, system state changes, feature usage
Logger.auth.info("User successfully authenticated")
Logger.sync.info("Sync operation completed with \(itemCount) items")
Logger.features.info("Feature flag \(flagName, privacy: .public) enabled")

error - Handled Errors

  • Purpose: Something went wrong but was handled gracefully
  • Retention: Available for longer-term analysis
  • Requirements: Always include error.localizedDescription
Logger.networking.error("API request failed: \(error.localizedDescription, privacy: .public)")
Logger.database.error("Failed to save context: \(error.localizedDescription, privacy: .public)")
Logger.auth.error("Keychain access denied: \(error.localizedDescription, privacy: .public)")

fault - Critical Issues

  • Purpose: Critical issues preventing normal app function
  • Retention: Highest priority, always preserved
  • Requirements: Include error description when available
Logger.database.fault("Database corruption detected: \(error.localizedDescription, privacy: .public)")
Logger.security.fault("Critical security violation: \(details, privacy: .public)")
Logger.system.fault("App unable to initialize required services")

Privacy Settings

Default Privacy Behavior

All interpolated values are .private by default - only visible in debug builds:

// These values are private by default
Logger.auth.info("User \(username) logged in")  // username is private
Logger.network.debug("Response time: \(responseTime)ms")  // responseTime is private

Public Information

Mark non-sensitive information as .public for visibility in release builds:

// Error descriptions should typically be public
Logger.network.error("Connection failed: \(error.localizedDescription, privacy: .public)")

// System information can be public
Logger.performance.info("App launched in \(launchTime, privacy: .public)ms")

// Feature flags and settings (non-PII) can be public
Logger.features.info("Dark mode: \(isDarkMode, privacy: .public)")

Privacy Decision Matrix

Data Type Privacy Level Example
User PII .private (default) Email, username, personal data
Error descriptions .public error.localizedDescription
System metrics .public Performance timings, counts
Feature states .public Feature flags, app settings
Debug values .private (default) Variable contents, internal state

Best Practices

DO

Direct Logging

// ✅ CORRECT: Log directly where events occur
func authenticateUser() {
    Logger.auth.info("Starting user authentication")
    
    do {
        let result = try performAuthentication()
        Logger.auth.info("Authentication successful")
    } catch {
        Logger.auth.error("Authentication failed: \(error.localizedDescription, privacy: .public)")
    }
}

Meaningful Context

// ✅ CORRECT: Include relevant context
Logger.sync.info("Sync completed: \(syncedItems, privacy: .public) items, \(conflicts, privacy: .public) conflicts")
Logger.network.debug("Cache hit for URL: \(url.absoluteString, privacy: .public)")
Logger.ui.debug("View controller \(type(of: self)) appeared")

Consistent Logger Usage

// ✅ CORRECT: Use established loggers consistently
extension BookmarkManager {
    func addBookmark(_ bookmark: Bookmark) {
        Logger.bookmarks.info("Adding bookmark: \(bookmark.title ?? "Untitled")")
        // Implementation
    }
    
    func deleteBookmark(_ bookmark: Bookmark) {
        Logger.bookmarks.info("Deleting bookmark: \(bookmark.title ?? "Untitled")")
        // Implementation
    }
}

DON'T

Wrapper Functions

// ❌ AVOID: Wrapper functions obscure context
func logError(_ message: String) {
    Logger.general.error("\(message)")  // Loses class, line number context
}

// Use direct logging instead
Logger.networking.error("Connection timeout: \(error.localizedDescription, privacy: .public)")

Logger Injection

// ❌ AVOID: Injecting loggers
class NetworkManager {
    private let logger: Logger
    
    init(logger: Logger) {  // Unnecessary complexity
        self.logger = logger
    }
}

// ✅ CORRECT: Use global logger extensions
class NetworkManager {
    func performRequest() {
        Logger.networking.info("Starting network request")
    }
}

Overly Verbose Debug Logging

// ❌ AVOID: Too much debug noise
func processItems(_ items: [Item]) {
    Logger.processing.debug("Starting to process items")
    for item in items {
        Logger.processing.debug("Processing item: \(item.id)")
        Logger.processing.debug("Item name: \(item.name)")
        Logger.processing.debug("Item processed successfully")
    }
    Logger.processing.debug("Finished processing all items")
}

// ✅ CORRECT: Focused, meaningful debug logs
func processItems(_ items: [Item]) {
    Logger.processing.debug("Processing \(items.count) items")
    // Process items...
    Logger.processing.debug("Item processing completed")
}

Reading and Filtering Logs

1. Xcode Console

Best for: App-specific debugging during development

Setup for Optimal Readability

  1. Add columns: Type, Library, Subsystem, Category
  2. Filter by process: Your app name
  3. Use contextual menu: Show/Hide specific log types

Console Filtering

// Filter by subsystem
subsystem:com.yourapp.Networking

// Filter by category  
category:API

// Hide system noise
subsystem:com.apple. (!contains)

// Show only errors and faults
type:error OR type:fault

2. Console.app

Best for: System-wide debugging and cross-app analysis

// Focus on DuckDuckGo process
process:duckduckgo (contains)

// Hide system noise
subsystem:com.apple. (!contains)
subsystem:PrototypeTools (!contains)
library:Security (!contains)
library:TextInput (!contains)

// Show specific subsystems
subsystem:Networking (contains)
subsystem:UI (contains)

Advanced Filtering Examples

// Errors in the last hour
type:error AND time:>-1h

// Specific feature debugging
subsystem:BrowserServicesKit AND category:SecureVault

// Performance monitoring
message:performance (contains) AND type:info

3. Command Line Tool

Best for: Scripting and automated analysis

Basic Usage

# Show logs for specific subsystem
log show --predicate 'subsystem == "com.duckduckgo.Networking"' --info

# Show recent errors
log show --predicate 'messageType == "Error"' --last 1h

# Export logs to file
log show --predicate 'process == "DuckDuckGo"' --start '2024-01-01 00:00:00' > app_logs.txt

# Real-time streaming
log stream --predicate 'subsystem == "com.duckduckgo.UI"'

Advanced Command Examples

# Debugging specific feature
log show --predicate 'subsystem == "BrowserServicesKit" AND category == "SecureVault"' --debug

# Performance analysis
log show --predicate 'message CONTAINS "performance"' --info --last 24h

# Error analysis with context
log show --predicate 'messageType >= "Error"' --info --start '2024-01-01'

# Multiple conditions
log show --predicate 'subsystem BEGINSWITH "com.duckduckgo" AND messageType == "Error"' --last 2h

4. Sysdiagnose

Best for: Remote debugging and Apple DTS submissions

What's Included

  • Complete system snapshot
  • All system and app logs
  • Memory usage data
  • Kernel information
  • Crash reports
  • Network status
  • Performance data

Usage

# Generate sysdiagnose
sudo sysdiagnose

# The generated file can be analyzed with Console.app
# Located in /var/tmp/ or Desktop

Log Export for Internal Users

macOS Debug Menu Export

Available to: Internal users only
Platform: macOS only

How to Export

  1. Open Debug menu
  2. Navigate to Logging > Export logs
  3. Logs are exported as a ZIP file to Desktop
  4. Includes filtered logs based on app subsystems

Export Contents

The exported ZIP contains:

  • App-specific logs filtered by subsystem
  • Recent system logs relevant to the app
  • Crash reports if available
  • Basic system information

Logging Patterns by Feature

Authentication & Security

public extension Logger {
    static var auth: Logger = { Logger(subsystem: "Security", category: "Authentication") }()
    static var keychain: Logger = { Logger(subsystem: "Security", category: "Keychain") }()
    static var encryption: Logger = { Logger(subsystem: "Security", category: "Encryption") }()
}

// Usage examples
Logger.auth.info("User authentication attempt")
Logger.keychain.error("Keychain access failed: \(error.localizedDescription, privacy: .public)")
Logger.encryption.debug("Encrypting data with algorithm: \(algorithm, privacy: .public)")

Networking & API

public extension Logger {
    static var networking: Logger = { Logger(subsystem: "Networking", category: "HTTP") }()
    static var api: Logger = { Logger(subsystem: "Networking", category: "API") }()
    static var cache: Logger = { Logger(subsystem: "Networking", category: "Cache") }()
}

// Usage examples
Logger.networking.info("HTTP request to \(endpoint, privacy: .public)")
Logger.api.error("API call failed: \(error.localizedDescription, privacy: .public)")
Logger.cache.debug("Cache hit for key: \(cacheKey)")

Data & Storage

public extension Logger {
    static var database: Logger = { Logger(subsystem: "Data Storage", category: "Core Data") }()
    static var secureVault: Logger = { Logger(subsystem: "Data Storage", category: "SecureVault") }()
    static var sync: Logger = { Logger(subsystem: "Data Storage", category: "Sync") }()
}

// Usage examples
Logger.database.info("Core Data migration completed")
Logger.secureVault.error("SecureVault operation failed: \(error.localizedDescription, privacy: .public)")
Logger.sync.info("Sync completed: \(itemCount, privacy: .public) items")

Performance Monitoring

public extension Logger {
    static var performance: Logger = { Logger(subsystem: "Performance", category: "Metrics") }()
    static var memory: Logger = { Logger(subsystem: "Performance", category: "Memory") }()
    static var startup: Logger = { Logger(subsystem: "Performance", category: "Startup") }()
}

// Usage examples
Logger.performance.info("Operation completed in \(duration, privacy: .public)ms")
Logger.memory.debug("Memory usage: \(memoryUsage, privacy: .public)MB")
Logger.startup.info("App launch completed in \(launchTime, privacy: .public)ms")

Integration with App Lifecycle

State Machine Logging

// In app lifecycle state machine
class Launching {
    func init() {
        Logger.lifecycle.info("App entering Launching state")
        // Initialization logic
        Logger.lifecycle.info("Launching state completed")
    }
}

class Foreground {
    func onTransition() {
        Logger.lifecycle.info("App transitioning to Foreground")
    }
    
    func didReturn() {
        Logger.lifecycle.info("App returned to Foreground state")
    }
}

Service Lifecycle Logging

class MyService {
    func start() {
        Logger.services.info("Starting \(type(of: self)) service")
        // Service startup logic
    }
    
    func stop() {
        Logger.services.info("Stopping \(type(of: self)) service")
        // Service cleanup logic
    }
}

Following these logging guidelines ensures consistent, privacy-conscious, and effective telemetry capture across the DuckDuckGo browser ecosystem, enabling better debugging, monitoring, and user experience optimization.