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

582 lines
16 KiB
Markdown

---
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/logging-guidelines.mdc
confidence: 0.9
namespace: work
last_synced: 2026-04-28
alwaysApply: 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:
```swift
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`:
```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`):
```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
```swift
// 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
```swift
// 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**:
```swift
// 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
```swift
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
```swift
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`
```swift
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
```swift
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:
```swift
// 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:
```swift
// 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
```swift
// ✅ 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
```swift
// ✅ 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
```swift
// ✅ 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
```swift
// ❌ 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
```swift
// ❌ 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
```swift
// ❌ 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
#### 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
```bash
# 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
```bash
# 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
```bash
# 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
```swift
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
```swift
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
```swift
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
```swift
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
```swift
// 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
```swift
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.