16 KiB
Executable File
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
- Add columns: Type, Library, Subsystem, Category
- Filter by process: Your app name
- 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
Recommended Filters for DuckDuckGo
// 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
- Open Debug menu
- Navigate to Logging > Export logs
- Logs are exported as a ZIP file to Desktop
- 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.