Log is a lightweight, flexible Swift logging framework that provides elegant and customizable logging capabilities for iOS, macOS, tvOS, watchOS, and visionOS applications. With support for multiple output destinations and custom formatting, Log makes debugging and monitoring your apps easier than ever.
β¨ Multiple Output Destinations - Console, system log (os_log), and custom printers
π¨ Customizable Formatting - Prefix, timestamp, and custom formatters
π Log Levels - Fine-grained control over message severity
π§ Extensible - Easy to create custom printers and formatters
π± Cross-Platform - Works on iOS, macOS, tvOS, watchOS, and visionOS
β‘ Lightweight - Minimal footprint with zero dependencies
π― Type-Safe - Leverages Swift's type system for compile-time safety
- Requirements
- Installation
- Quick Start
- Usage
- Common Use Cases
- Communication
- Contributing
- Author
- License
| Platform | Minimum Version |
|---|---|
| iOS | 13.0+ |
| macOS | 10.15+ |
| tvOS | 13.0+ |
| watchOS | 7.0+ |
| visionOS | 1.0+ |
| Xcode | 15.3+ |
| Swift | 5.10+ |
Add the following dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/space-code/log.git", from: "1.3.0")
]Or add it through Xcode:
- File > Add Package Dependencies
- Enter package URL:
https://github.com/space-code/log.git - Select version requirements
import Log
// Create printers
let consolePrinter = ConsolePrinter()
let osPrinter = OSPrinter()
// Create logger
let log = Logger(
printers: [consolePrinter, osPrinter],
logLevel: .all
)
// Start logging
log.info(message: "Application started")
log.debug(message: "Debug information")
log.error(message: "Something went wrong")Log provides two main printer implementations out of the box:
import Log
// Console printer for Xcode console output
let consolePrinter = ConsolePrinter()
// OS printer for system console (Console.app)
let osPrinter = OSPrinter()
// Create logger with multiple printers
let log = Logger(
printers: [consolePrinter, osPrinter],
logLevel: .all
)Log supports various severity levels for organizing your messages:
import Log
let log = Logger(printers: [ConsolePrinter()], logLevel: .all)
// Different log levels
log.debug(message: "Debug information")
log.info(message: "General informational messages")
log.error(message: "Error messages")
log.fault(message: "Critical fault messages")Available Log Levels:
.debug- Debug information.info- General informational messages.error- Error messages.fault- Critical system faults.all- Log all messages
Filtering by Level:
// Only log errors
let log = Logger(
printers: [ConsolePrinter()],
logLevel: .errors
)
log.debug(message: "This won't be printed")
log.error(message: "This will be printed")Printers determine where your log messages are output:
ConsolePrinter - Outputs to Xcode console:
let consolePrinter = ConsolePrinter(
formatters: [
PrefixLogFormatter(name: "MyApp"),
TimestampLogFormatter(dateFormat: "HH:mm:ss")
]
)OSPrinter - Outputs to system log (viewable in Console.app):
let osPrinter = OSPrinter(
formatters: [
PrefixLogFormatter(name: "MyApp")
]
)Using Multiple Printers:
// Log to both Xcode console and system log
let log = Logger(
printers: [
ConsolePrinter(formatters: [TimestampLogFormatter()]),
OSPrinter(formatters: [PrefixLogFormatter(name: "MyApp")])
],
logLevel: .all
)Formatters transform your log messages before they're printed. Log includes two built-in formatters:
| Formatter | Description |
|---|---|
| PrefixLogFormatter | Adds a specified prefix to messages |
| TimestampLogFormatter | Adds a timestamp based on date format |
Using Formatters:
import Log
// Prefix formatter
let prefixFormatter = PrefixLogFormatter(name: "NetworkLayer")
let printer = ConsolePrinter(formatters: [prefixFormatter])
let log = Logger(printers: [printer], logLevel: .all)
log.info(message: "Request started")
// Output: βΉοΈ [NetworkLayer] => Request started
// Timestamp formatter
let timestampFormatter = TimestampLogFormatter(dateFormat: "yyyy-MM-dd HH:mm:ss")
let printer2 = ConsolePrinter(formatters: [timestampFormatter])
let log2 = Logger(printers: [printer2], logLevel: .all)
log2.info(message: "User logged in")
// Output: βΉοΈ 2025-12-22 15:56:42 User logged in
// Combining formatters
let combinedPrinter = ConsolePrinter(formatters: [
TimestampLogFormatter(dateFormat: "HH:mm:ss"),
PrefixLogFormatter(name: "API")
])
let log3 = Logger(printers: [combinedPrinter], logLevel: .all)
log3.error(message: "Connection failed")
// Output: β οΈ [API] => 15:56:42 Connection failedCreate custom formatters by conforming to the ILogFormatter protocol:
import Log
struct EnvironmentFormatter: ILogFormatter {
let environment: String
func format(message: String, with logLevel: LogLevel) -> String {
return "[\(environment.uppercased())] \(message)"
}
}
// Usage
let formatter = EnvironmentFormatter(environment: "production")
let printer = ConsolePrinter(formatters: [formatter])
let log = Logger(printers: [printer], logLevel: .all)
log.info(message: "Server started")
// Output: [PRODUCTION] Server startedAdvanced Custom Formatter:
struct ContextualFormatter: ILogFormatter {
let includeThread: Bool
let includeFile: Bool
func format(message: String, with logLevel: LogLevel) -> String {
var components: [String] = []
if includeThread {
components.append("[Thread: \(Thread.current.threadName)]")
}
if includeFile {
components.append("[File: \(#file)]")
}
components.append(message)
return components.joined(separator: " ")
}
}Custom Printer:
import Log
final class FilePrinter: IPrinter {
let formatters: [ILogFormatter]
let fileURL: URL
init(formatters: [ILogFormatter] = [], fileURL: URL) {
self.formatters = formatters
self.fileURL = fileURL
}
func log(_ message: String, logLevel: Log.LogLevel) {
var formattedMessage = message
for formatter in formatters {
formattedMessage = formatter.format(
message: formattedMessage,
with: logLevel
)
}
if let data = (formattedMessage + "\n").data(using: .utf8) {
// Write to file
}
}
}import Log
class AppDelegate: UIResponder, UIApplicationDelegate {
private let log = Logger(
printers: [
ConsolePrinter(formatters: [
TimestampLogFormatter(dateFormat: "HH:mm:ss.SSS"),
PrefixLogFormatter(name: "MyApp")
]),
OSPrinter(formatters: [
PrefixLogFormatter(name: "MyApp")
])
],
logLevel: .all
)
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
log.info(message: "Application launched")
setupApplication()
return true
}
private func setupApplication() {
log.debug(message: "Configuring application")
// Configuration code
log.info(message: "Application configured successfully")
}
}import Log
class NetworkManager {
private let log = Logger(
printers: [
ConsolePrinter(formatters: [
TimestampLogFormatter(),
PrefixLogFormatter(name: "Network")
])
],
logLevel: .debug
)
func fetchData(from url: URL) async throws -> Data {
log.debug(message: "Requesting: \(url.absoluteString)")
do {
let (data, response) = try await URLSession.shared.data(from: url)
if let httpResponse = response as? HTTPURLResponse {
log.info(message: "Response: \(httpResponse.statusCode)")
}
return data
} catch {
log.error(message: "Request failed: \(error.localizedDescription)")
throw error
}
}
}import Log
class DatabaseManager {
private let log = Logger(
printers: [
ConsolePrinter(formatters: [
PrefixLogFormatter(name: "Database")
])
],
logLevel: .all
)
func saveRecord(_ record: Record) throws {
log.debug(message: "Attempting to save record: \(record.id)")
do {
try database.insert(record)
log.info(message: "Record saved successfully: \(record.id)")
} catch {
log.error(message: "Failed to save record: \(error)")
throw error
}
}
func deleteRecord(id: String) throws {
log.info(message: "Deleting record: \(id)")
try database.delete(id)
log.info(message: "Record deleted: \(id)")
}
}import Log
class LoggerFactory {
static func createLogger() -> Logger {
#if DEBUG
return Logger(
printers: [
ConsolePrinter(formatters: [
TimestampLogFormatter(dateFormat: "HH:mm:ss.SSS"),
PrefixLogFormatter(name: "Debug")
])
],
logLevel: .all
)
#else
return Logger(
printers: [
OSPrinter(formatters: [
PrefixLogFormatter(name: "Production")
])
],
logLevel: .error // Only log errors in production
)
#endif
}
}
// Usage
let log = LoggerFactory.createLogger()- π Found a bug? Open an issue
- π‘ Have a feature request? Open an issue
- β Questions? Start a discussion
- π€ Want to contribute? Submit a pull request
We love contributions! Please feel free to help out with this project. If you see something that could be made better or want a new feature, open up an issue or send a Pull Request.
Bootstrap the development environment:
make bootstrapFor detailed contribution guidelines, see CONTRIBUTING.md.
Nikita Vasilev
- Email: nv3212@gmail.com
- GitHub: @ns-vasilev
Log is released under the MIT license. See LICENSE for details.
Made with β€οΈ by space-code
