Logging in Swift Applications: Best Practices in Practice
Logging is not just about writing messages to the console. In professional iOS/macOS development, properly configured logging becomes an indispensable tool for debugging, monitoring, and analyzing application behavior in production. In this article, we will break down best practices for logging in Swift, explore modern tools (os_log, SwiftyBeaver), and write real code examples that you can implement in your project today.
Why print() is Bad for Production
Many beginner developers use print() for debugging. However, in production applications, this leads to problems:
- Performance:
print()synchronously blocks the thread, slowing down the UI. - Security: Sensitive data (tokens, passwords) can end up in system logs.
- Lack of filtering: You cannot separate debug messages from errors.
- No structure: Each message is just a string without metadata (level, category, file, function).
Instead of print(), use the system framework os_log (Unified Logging) or third-party libraries such as SwiftyBeaver.
Best Practice #1: Using os_log (Unified Logging System)
Starting with iOS 10 / macOS 10.12, Apple recommends using os_log from the OSLog framework. This is a built-in mechanism that works asynchronously, does not affect performance, and automatically collects metadata.
Basic os_log Setup
import OSLog
// Create a logger for the "Network" modulelet networkLogger = Logger(subsystem: "com.yourapp.network", category: "HTTP")
// Log different levelsnetworkLogger.debug("Request sent: \\(url)")networkLogger.info("Response received: \\(statusCode)")networkLogger.error("Connection error: \\(error.localizedDescription)")networkLogger.fault("Critical error: data loss")Advantages of os_log:
- Asynchronous: Does not block the main thread.
- Levels: debug, info, error, fault — can be filtered in the Xcode console.
- Security: Sensitive data is automatically masked in release builds.
- Integration with Console.app: All logs are available on the user's device (with their permission).
Dynamic Levels for Debugging
For convenience, you can create a wrapper that allows enabling/disabling debug logs depending on the build scheme:
import OSLog
struct AppLogger { private static let subsystem = "com.yourapp.main" static let general = Logger(subsystem: subsystem, category: "General") static let network = Logger(subsystem: subsystem, category: "Network") static let database = Logger(subsystem: subsystem, category: "Database") static func debug(_ message: String, file: String = #file, function: String = #function, line: Int = #line) { #if DEBUG let fileName = (file as NSString).lastPathComponent general.debug("[\\(fileName):\\(line)] \\(function) - \\(message)") #endif }}
// UsageAppLogger.debug("User tapped login button")AppLogger.network.info("API call started")Best Practice #2: SwiftyBeaver — Flexible Logging for Complex Projects
If you need advanced functionality (logging to a file, sending to a server, colored console output), check out SwiftyBeaver. It is one of the most popular libraries for logging in Swift.
Installation via Swift Package Manager
// In Package.swift or via Xcode -> File -> Add Packages// URL: ht