582 lines
16 KiB
Markdown
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.
|