OSLog / Logger
Apple's official unified logging system. Improved APIs introduced at WWDC 2020, designed with high performance and privacy considerations. Provides system log integration, category-based filtering, and automatic metadata collection. Supported across iOS, macOS, tvOS, and watchOS.
Library
OSLog Logger
Overview
OSLog Logger is a modern logging library for Swift that forms the core of Apple's Unified Logging System, available from iOS 14 onwards. Designed as Apple's officially recommended high-performance logging solution to replace traditional NSLog and print statements. It achieves excellent performance through structured logging, privacy protection, efficient data compression, and deferred data collection. Provides comprehensive log management experience through flexible classification with subsystems and categories, log level control, and Console.app integration.
Details
OSLog Logger is the latest API of the Unified Logging System introduced by Apple in iOS 10, serving as the recommended integrated logging solution from iOS 14 onwards. Designed to completely replace traditional ASL (Apple System Log) and Syslog, it achieves dramatic performance improvements through advanced technologies such as efficient binary data storage, log data compression, and deferred data collection. Integration with Swift 5.1's String Interpolation enables type-safe and natural syntax for log writing. Standard support for privacy protection features with automatic masking of sensitive data, logical classification through subsystems and categories, and importance management through log levels (debug, info, notice, error, fault).
Key Features
- High-Performance Log Processing: Optimized performance through binary compression and deferred collection
- Privacy Protection: Automatic masking of sensitive data and privacy control
- Structured Logging: Logical classification system through subsystems and categories
- Log Level Control: Importance management through debug/info/notice/error/fault levels
- Console.app Integration: Advanced filtering and search in macOS Console.app
- Swift String Interpolation: Type-safe and natural syntax for log writing
Pros and Cons
Pros
- Apple's officially recommended modern logging API with guaranteed future compatibility and reliability
- Dramatic performance improvements compared to traditional NSLog
- Privacy protection features enable safe handling of personal information
- Efficient log management for large-scale applications through subsystems and categories
- Professional log analysis environment through Console.app integration
- Type-safe and readable code writing through Swift String Interpolation
Cons
- iOS 14+ requirement, not available in legacy versions
- Learning curve exists due to required subsystem and category design in initial setup
- Limited flexibility for custom log formats and external log service integration
- Potential unintended information masking due to privacy control complexity
- Need to be aware of different log output behavior between debug and production builds
- Constraints in integration with log analysis tools other than Console.app
Reference Pages
- Logger | Apple Developer Documentation
- OSLog | Apple Developer Documentation
- Unified Logging | Apple Developer Documentation
Code Examples
Basic Setup
import os
// Simple configuration using Bundle ID as subsystem
let logger = Logger()
// Custom subsystem and category configuration
let networkLogger = Logger(subsystem: "com.example.myapp", category: "networking")
let uiLogger = Logger(subsystem: "com.example.myapp", category: "ui")
let dataLogger = Logger(subsystem: "com.example.myapp", category: "data")
// Unified logging system for app-wide usage
struct AppLogger {
static let network = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
static let ui = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ui")
static let data = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "data")
static let general = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "general")
}
Basic Log Output
import os
let logger = Logger(subsystem: "com.example.myapp", category: "general")
// Basic output at each log level
logger.debug("Debug info: Detailed information during app development")
logger.info("Info: User has logged in")
logger.notice("Notice: Important state change occurred")
logger.error("Error: Network connection failed")
logger.fault("Critical error: Database connection completely failed")
// String Interpolation with variables
let userName = "John Doe"
let userId = 12345
let loginTime = Date()
logger.info("User login successful: \(userName, privacy: .public) (ID: \(userId, privacy: .private)) at \(loginTime)")
// Detailed privacy control specification
let email = "[email protected]"
let password = "secretpassword"
logger.debug("Authentication attempt: email=\(email, privacy: .public), password=\(password, privacy: .private)")
// Numeric and array log output
let responseTime = 1.234
let userList = ["Alice", "Bob", "Charlie"]
logger.info("API response time: \(responseTime, format: .fixed(precision: 3)) seconds")
logger.debug("User list: \(userList, privacy: .public)")
Advanced Configuration
import os
// Category-based log management system
class LoggingManager {
// Logger for each functional area
private let networkLogger = Logger(subsystem: "com.example.myapp", category: "network")
private let dbLogger = Logger(subsystem: "com.example.myapp", category: "database")
private let uiLogger = Logger(subsystem: "com.example.myapp", category: "ui")
private let securityLogger = Logger(subsystem: "com.example.myapp", category: "security")
static let shared = LoggingManager()
private init() {}
// Network-related logs
func logNetworkRequest(url: String, method: String, headers: [String: String]?) {
networkLogger.info("Request started: \(method, privacy: .public) \(url, privacy: .public)")
if let headers = headers {
networkLogger.debug("Header info: \(headers, privacy: .private)")
}
}
func logNetworkResponse(statusCode: Int, responseTime: TimeInterval) {
if statusCode >= 400 {
networkLogger.error("Request failed: Status code \(statusCode)")
} else {
networkLogger.info("Request successful: Status code \(statusCode), response time \(responseTime, format: .fixed(precision: 3)) seconds")
}
}
// Database-related logs
func logDatabaseOperation(operation: String, table: String, recordCount: Int? = nil) {
if let count = recordCount {
dbLogger.info("DB operation: \(operation, privacy: .public) on \(table, privacy: .public) - \(count) records")
} else {
dbLogger.info("DB operation: \(operation, privacy: .public) on \(table, privacy: .public)")
}
}
func logDatabaseError(operation: String, error: Error) {
dbLogger.error("DB operation error: \(operation, privacy: .public) - \(error.localizedDescription)")
}
// UI-related logs
func logScreenView(screenName: String, loadTime: TimeInterval? = nil) {
if let loadTime = loadTime {
uiLogger.info("Screen displayed: \(screenName, privacy: .public) - load time \(loadTime, format: .fixed(precision: 3)) seconds")
} else {
uiLogger.info("Screen displayed: \(screenName, privacy: .public)")
}
}
func logUserAction(action: String, context: [String: Any]? = nil) {
uiLogger.info("User action: \(action, privacy: .public)")
if let context = context {
uiLogger.debug("Action context: \(context, privacy: .private)")
}
}
// Security-related logs
func logSecurityEvent(event: String, severity: SecuritySeverity, details: [String: Any]? = nil) {
switch severity {
case .low:
securityLogger.notice("Security event: \(event, privacy: .public)")
case .medium:
securityLogger.error("Security warning: \(event, privacy: .public)")
case .high:
securityLogger.fault("Security threat: \(event, privacy: .public)")
}
if let details = details {
securityLogger.debug("Event details: \(details, privacy: .private)")
}
}
}
enum SecuritySeverity {
case low, medium, high
}
// Performance monitoring logger
class PerformanceLogger {
private let logger = Logger(subsystem: "com.example.myapp", category: "performance")
private var timers: [String: CFAbsoluteTime] = [:]
func startTimer(_ name: String) {
timers[name] = CFAbsoluteTimeGetCurrent()
logger.debug("Performance measurement started: \(name, privacy: .public)")
}
func endTimer(_ name: String) {
guard let startTime = timers.removeValue(forKey: name) else {
logger.error("Performance measurement error: Timer '\(name, privacy: .public)' not found")
return
}
let duration = CFAbsoluteTimeGetCurrent() - startTime
if duration > 1.0 {
logger.notice("Performance warning: \(name, privacy: .public) - \(duration, format: .fixed(precision: 3)) seconds (threshold exceeded)")
} else {
logger.info("Performance measurement completed: \(name, privacy: .public) - \(duration, format: .fixed(precision: 3)) seconds")
}
}
}
Error Handling
import os
class ErrorLogger {
private let logger = Logger(subsystem: "com.example.myapp", category: "error")
func logError(_ error: Error, context: String? = nil, file: String = #file, line: Int = #line, function: String = #function) {
let fileName = (file as NSString).lastPathComponent
let location = "\(fileName):\(line) \(function)"
if let context = context {
logger.error("Error occurred [\(location, privacy: .public)]: \(context, privacy: .public) - \(error.localizedDescription)")
} else {
logger.error("Error occurred [\(location, privacy: .public)]: \(error.localizedDescription)")
}
// Record detailed error information at debug level
logger.debug("Error details: \(String(describing: error), privacy: .private)")
}
func logNetworkError(_ error: Error, url: String, statusCode: Int? = nil) {
if let statusCode = statusCode {
logger.error("Network error: \(url, privacy: .public) - Status \(statusCode) - \(error.localizedDescription)")
} else {
logger.error("Network error: \(url, privacy: .public) - \(error.localizedDescription)")
}
// Detailed analysis of URLError
if let urlError = error as? URLError {
switch urlError.code {
case .notConnectedToInternet:
logger.fault("Critical: No internet connection")
case .timedOut:
logger.error("Network timeout")
case .cannotFindHost:
logger.error("Host resolution failed: \(url, privacy: .public)")
default:
logger.error("URL Error Code: \(urlError.errorCode)")
}
}
}
func logValidationError(field: String, value: Any, rule: String) {
logger.error("Validation error: Field '\(field, privacy: .public)' - Rule '\(rule, privacy: .public)'")
logger.debug("Validation failed value: \(String(describing: value), privacy: .private)")
}
func logDatabaseError(_ error: Error, operation: String, table: String? = nil) {
if let table = table {
logger.error("Database error: \(operation, privacy: .public) on \(table, privacy: .public) - \(error.localizedDescription)")
} else {
logger.error("Database error: \(operation, privacy: .public) - \(error.localizedDescription)")
}
// Special handling for Core Data errors
if let coreDataError = error as NSError? {
logger.debug("Core Data Error Details: Domain=\(coreDataError.domain), Code=\(coreDataError.code)")
if let userInfo = coreDataError.userInfo as? [String: Any] {
for (key, value) in userInfo {
logger.debug("UserInfo: \(key)=\(String(describing: value), privacy: .private)")
}
}
}
}
}
// Usage examples
let errorLogger = ErrorLogger()
// General error logging
do {
try someRiskyOperation()
} catch {
errorLogger.logError(error, context: "During user data processing")
}
// Network error-specific logging
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
let statusCode = (response as? HTTPURLResponse)?.statusCode
errorLogger.logNetworkError(error, url: url.absoluteString, statusCode: statusCode)
}
}.resume()
Practical Examples
import os
// Application-wide log management
class AppLifecycleLogger {
private let logger = Logger(subsystem: "com.example.myapp", category: "lifecycle")
func logAppStart() {
logger.notice("Application started")
logger.info("Bundle Version: \(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown", privacy: .public)")
logger.info("Device Model: \(UIDevice.current.model, privacy: .public)")
logger.info("iOS Version: \(UIDevice.current.systemVersion, privacy: .public)")
}
func logAppState(state: UIApplication.State) {
switch state {
case .active:
logger.info("Application transitioned to active state")
case .background:
logger.info("Application transitioned to background state")
case .inactive:
logger.info("Application transitioned to inactive state")
@unknown default:
logger.notice("Application transitioned to unknown state")
}
}
func logMemoryWarning(availableMemory: Int64) {
logger.error("Memory warning: Available memory \(availableMemory / 1024 / 1024) MB")
}
}
// API communication log management
class APILogger {
private let logger = Logger(subsystem: "com.example.myapp", category: "api")
func logRequest<T: Codable>(endpoint: String, method: String, parameters: T?) {
logger.info("API Request: \(method, privacy: .public) \(endpoint, privacy: .public)")
if let parameters = parameters {
do {
let jsonData = try JSONEncoder().encode(parameters)
let jsonString = String(data: jsonData, encoding: .utf8) ?? "Invalid JSON"
logger.debug("Request Parameters: \(jsonString, privacy: .private)")
} catch {
logger.error("Parameter encoding error: \(error.localizedDescription)")
}
}
}
func logResponse<T: Codable>(endpoint: String, statusCode: Int, responseTime: TimeInterval, data: T?) {
if statusCode >= 200 && statusCode < 300 {
logger.info("API Success: \(endpoint, privacy: .public) - \(statusCode) in \(responseTime, format: .fixed(precision: 3))s")
} else {
logger.error("API Error: \(endpoint, privacy: .public) - Status \(statusCode)")
}
if let data = data {
do {
let jsonData = try JSONEncoder().encode(data)
let jsonString = String(data: jsonData, encoding: .utf8) ?? "Invalid JSON"
logger.debug("Response Data: \(jsonString, privacy: .private)")
} catch {
logger.error("Response encoding error: \(error.localizedDescription)")
}
}
}
}
// User behavior analytics logging
class UserAnalyticsLogger {
private let logger = Logger(subsystem: "com.example.myapp", category: "analytics")
func logScreenView(screenName: String, previousScreen: String? = nil, loadTime: TimeInterval) {
if let previous = previousScreen {
logger.info("Screen Navigation: \(previous, privacy: .public) -> \(screenName, privacy: .public) (\(loadTime, format: .fixed(precision: 3))s)")
} else {
logger.info("Screen View: \(screenName, privacy: .public) (\(loadTime, format: .fixed(precision: 3))s)")
}
}
func logUserAction(action: String, screen: String, properties: [String: Any]? = nil) {
logger.info("User Action: \(action, privacy: .public) on \(screen, privacy: .public)")
if let properties = properties {
for (key, value) in properties {
logger.debug("Action Property: \(key, privacy: .public)=\(String(describing: value), privacy: .private)")
}
}
}
func logConversionEvent(event: String, value: Double?, currency: String?) {
if let value = value, let currency = currency {
logger.notice("Conversion: \(event, privacy: .public) - \(value, format: .fixed(precision: 2)) \(currency, privacy: .public)")
} else {
logger.notice("Conversion: \(event, privacy: .public)")
}
}
}
// Usage examples
let appLogger = AppLifecycleLogger()
let apiLogger = APILogger()
let analyticsLogger = UserAnalyticsLogger()
// At application start
appLogger.logAppStart()
// API calls
struct UserRequest: Codable {
let username: String
let email: String
}
let userRequest = UserRequest(username: "johndoe", email: "[email protected]")
apiLogger.logRequest(endpoint: "/api/users", method: "POST", parameters: userRequest)
// User behavior recording
analyticsLogger.logScreenView(screenName: "Profile", previousScreen: "Home", loadTime: 0.5)
analyticsLogger.logUserAction(action: "Button Tap", screen: "Profile", properties: ["button_id": "save_profile"])
analyticsLogger.logConversionEvent(event: "Purchase", value: 29.99, currency: "USD")