{"id":20541027,"url":"https://github.com/sentryco/logger","last_synced_at":"2025-08-24T12:43:41.245Z","repository":{"id":160164642,"uuid":"615018438","full_name":"sentryco/Logger","owner":"sentryco","description":"🔍 Simple console logger","archived":false,"fork":false,"pushed_at":"2025-03-01T21:45:36.000Z","size":137,"stargazers_count":5,"open_issues_count":11,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-04-14T08:47:31.774Z","etag":null,"topics":["console","crash-reporting","crashalytics","firebase","google-analytics","ios","log","logger","logging","logging-library","macos","print","swift","swiftpackagemanager","trace","trace-level"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sentryco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-03-16T19:33:09.000Z","updated_at":"2025-03-01T21:45:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"a06a3af0-3770-4881-9fb6-21e21db134f6","html_url":"https://github.com/sentryco/Logger","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sentryco/Logger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FLogger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FLogger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FLogger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FLogger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sentryco","download_url":"https://codeload.github.com/sentryco/Logger/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FLogger/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271866955,"owners_count":24836413,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-24T02:00:11.135Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["console","crash-reporting","crashalytics","firebase","google-analytics","ios","log","logger","logging","logging-library","macos","print","swift","swiftpackagemanager","trace","trace-level"],"created_at":"2024-11-16T01:18:53.959Z","updated_at":"2025-08-24T12:43:41.194Z","avatar_url":"https://github.com/sentryco.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![mit](https://img.shields.io/badge/License-MIT-brightgreen.svg)\n![platform](https://img.shields.io/badge/Platform-iOS/macOS-blue.svg)\n![Lang](https://img.shields.io/badge/Language-Swift%205-orange.svg)\n[![SPM compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://github.com/apple/swift)\n[![Tests](https://github.com/sentryco/Logger/actions/workflows/Tests.yml/badge.svg)](https://github.com/sentryco/Logger/actions/workflows/Tests.yml)\n[![codebeat badge](https://codebeat.co/badges/1b701174-9272-4fc9-9de4-3e12af2094d6)](https://codebeat.co/projects/github-com-sentryco-logger-main)\n\n# 🔍 Logger\n\n\u003e Simple console logger\n\n### Features\n- Four levels of severity: 🔴 Error, 🟠 Warning, 🔵 Debug, 🟣 Info\n- Nine tag types: 📡 Network, 🗄 Database, 🖥 UI, 💾 File, 🔑 Security, 🛍 Payment, ⚙️ System, 🧰 Utility, 📝 Other\n- Output to **console**, **file**, or a **custom endpoint** like Google Analytics or Firebase Crashlytics\n\n### Why Logger?\n- Efficiently debug complex apps by filtering logs to avoid console clutter.\n- Easily toggle logging for specific components like UI or database interactions.\n- Send errors and warnings to external services like Google Analytics or Firebase Crashlytics for better monitoring.\n\n### Logging format:\n```swift\nLogger.debug(\"Network.connect - connection established successfully\", tag: .net)\n// Output: [🔵 Debug] [2023-12-24 22:00:45] ➞ 📡 Network.connect: connection established successfully\n\nLogger.warning(\"Network.connect \\(error.localizedDescription)\", tag: .net)\n// Output: [🟠 Warning] [2023-12-24 22:00:45] ➞ 📡 Network.connect: Wi-Fi is not turned on\n\nLogger.error(\"Network.processData \\(error.localizedDescription)\", tag: .net)\n// Output: [🔴 Error] [2023-12-24 22:00:45] ➞ 📡 Network.processData: Decoding was unsuccessful. Nothing was saved\n```\n\n### Configure:\n```swift\n// Configure the logger output format\nLogger.config = .plain  // Options: .plain (no date), .full (includes date and verbose level)\n\n// Set the output transport method\nLogger.type = .console  // Options: .console, .file(filePath: String), .custom(onLog: LogType.OnLog)\n\n// Define the logging mode for levels and tags\nLogger.mode = .everything  // Options: .everything (all logs), .nothing (disable logging), .essential (warnings and errors only)\n\n// Convenient one-liner setup\nLogger.setup(config: .full, mode: .essential, type: .console)\n```\n\n### Add custom log end-point like GA or Firebase crashalytics\n```swift\n// Define a custom logging function\nlet onLog: LogType.OnLog = { msg, level, tag in\n    // Only send warnings and errors to the custom endpoint\n    if [.error, .warning].contains(level) {\n        sendToAnalytics(msg, level: level, tag: tag)\n    }\n}\n\n// Set the logger to use the custom output\nLogger.type = .custom(onLog)\n\nLogger.warn(\"User session expired\", tag: .security)  // This will be sent to the custom endpoint\nLogger.error(\"Failed to save data\", tag: .db)        // This will be sent to the custom endpoint\nLogger.info(\"User opened settings\", tag: .ui)        // This will not be sent\n```\n\n\u003e [!NOTE]  \n\u003e Since iOS14+ Target apples own Logger class, write: `os.Logger`\n\n### Logging to Console.app\nIf mesages in console.app only shows messages as private. Read the logger article on eon.codes on how to change that.\n\n```swift\nimport os // Need to import os.Logger\n\nlet logger = os.Logger(subsystem: \"co.acme.ExampleApp\", category: \"ExampleApp\")\nlet onLog: LogType.OnLog = { msg, level, _ in\n   logger.log(\"\\(msg, privacy: .public)\") // Reveals the redacted text from the message\n}\nLogger.type = .custom(onLog) // Add the custom output closure to the logger\nLogger.info(\"Something happened\") // Prints to Console.app (filter by category or subsystem)\n```\n\n### Tracing\n\n The `Trace` class can be combined with `Logger` to include function names, class names, and line numbers in your logs.\n\n```swift\nclass Test {\n   func myFunction() {\n      Trace.trace(\"This msg\")\n   }\n}\nTest().myFunction() // Prints \"This msg is called from function: myFunction in class: Test on line: 13\"\n```\n\n### Trace + Logger\n```swift\nLogger.warn(\"\\(Trace.trace() - error occured\", tag: .net) - error occured\") // Called inside NetManager.connect\n// Prints: [️🟠 Warning] [23-12-24 22:00:45] ➞ 📡 NetManager.connect - error occured\n```\n\n### Gotchas\n- Print only works when debugging an app. When the app is built for running, Swift.print doesn't work anymore. Use file logging in release if needed.\n- Use the Telemetry for GA hook.\n\n## Installation\nAdd the following line to your `Package.swift` file:\n\n```swift\n.package(url: \"https://github.com/sentryco/Logger\", branch: \"main\")\n```\n\nThen add `Logger` as a dependency for your targets:\n\n```swift\n.target(\n    name: \"MyTarget\",\n    dependencies: [\n        .product(name: \"Logger\", package: \"Logger\"),\n    ]\n),\n```\n\n### Todo:\n- Consider including the `Trace.trace()` call in log call so it can be toggled on and off\n- Add the tag-type emoji to output just before the message\n- Research how to log fatal crashes, if possible. Exception handling needs to be explored\n- Conduct more research on logging best practices\n- Add terminal color to formatting text: https://github.com/sushichop/Puppy/blob/main/Sources/Puppy/LogColor.swift\n- Add native OS support:  https://www.avanderlee.com/debugging/oslog-unified-logging/\n- Test Firebase Crashlytics in a demo project\n- Add support for oslog in the framework. We currently support it in the ad-hoc callback. Add this to unit test as well as instructions on Console.app usage and limitations.\n- Consider adding another log type called \"important\"\n- Add usage gif exploring system console, google-analytics, xcode consol\n- Add problem / solution to readme\n- Add a note about apples OS.Logger. And its limitations.\n- Add protocol oriented design: \n\n```swift\nprotocol LoggerProtocol {\n    func log(message: String, level: LogLevel, tag: LogTag)\n}\n\n// Implementations\nclass ConsoleLogger: LoggerProtocol {\n    func log(message: String, level: LogLevel, tag: LogTag) {\n        Swift.print(message)\n    }\n}\n\nclass FileLogger: LoggerProtocol {\n    // File logging implementation...\n}\n```\n- Add console color codes: \n\n```swift\nextension String {\n    enum ConsoleColor: String {\n        case red = \"\\u{001B}[0;31m\"\n        case orange = \"\\u{001B}[0;33m\"\n        case blue = \"\\u{001B}[0;34m\"\n        case purple = \"\\u{001B}[0;35m\"\n        case reset = \"\\u{001B}[0;0m\"\n    }\n\n    func colored(_ color: ConsoleColor) -\u003e String {\n        return \"\\(color.rawValue)\\(self)\\(ConsoleColor.reset.rawValue)\"\n    }\n}\n\n// Apply Colors in Logging:\n\nextension Logger {\n    fileprivate static func formatMessage(_ msg: String, level: LogLevel, tag: LogTag) -\u003e String {\n        // Existing formatting code...\n        var text = \"[\\(levelText)]\"\n        if config.showDate {\n            let date = config.dateFormatter.string(from: Date())\n            text += \" [\\(date)]\"\n        }\n        text += \" ➞ \\(tag.rawValue) \\(msg)\"\n\n        // Apply color based on log level\n        switch level {\n        case .error:\n            text = text.colored(.red)\n        case .warning:\n            text = text.colored(.orange)\n        case .debug:\n            text = text.colored(.blue)\n        case .info:\n            text = text.colored(.purple)\n        }\n\n        return text\n    }\n}\n```\n\n- Add Native OS Logging Support (os.log)\n\n```swift\nimport os\n\npublic enum LogType {\n    // Existing cases...\n    case osLog(OSLog = .default)\n}\n\nextension LogType {\n    internal func log(msg: String, level: LogLevel, tag: LogTag) {\n        switch self {\n        // Existing cases...\n        case let .osLog(logger):\n            if #available(iOS 14.0, macOS 11.0, *) {\n                logger.log(\"\\(msg, privacy: .public)\")\n            } else {\n                os_log(\"%{public}@\", log: logger, type: .default, msg)\n            }\n        }\n    }\n}\n\n// Configure logger to use osLog\nLogger.type = .osLog()\n\n// Log messages\nLogger.info(\"Application started\", tag: .system)\n\n```\n\n- Implement Exception Handling for Fatal Crashes\n\n\n```swift\n// Set Up Exception Handler:\nfunc setUpExceptionHandler() {\n    NSSetUncaughtExceptionHandler { exception in\n        Logger.error(\"Uncaught exception: \\(exception)\", tag: .system)\n    }\n}\n// Set Up Signal Handler:\nimport Darwin\n\nfunc setUpSignalHandler() {\n    signal(SIGABRT) { _ in\n        Logger.error(\"Received SIGABRT signal\", tag: .system)\n    }\n    signal(SIGILL) { _ in\n        Logger.error(\"Received SIGILL signal\", tag: .system)\n    }\n    // Add handlers for other signals as needed\n}\n// Call Handlers at App Launch:\n// In AppDelegate or main entry point\nfunc applicationDidFinishLaunching(_ application: UIApplication) {\n    setUpExceptionHandler()\n    setUpSignalHandler()\n    // Other initialization code...\n}\n// Extend LogType:\npublic enum LogType {\n    // Existing cases...\n    case crashlytics\n}\n// Implement Crashlytics Logging:\nextension LogType {\n    internal func log(msg: String, level: LogLevel, tag: LogTag) {\n        switch self {\n        // Existing cases...\n        case .crashlytics:\n            Crashlytics.crashlytics().log(msg)\n            if level == .error {\n                let error = NSError(domain: Bundle.main.bundleIdentifier ?? \"Logger\", code: 0, userInfo: [NSLocalizedDescriptionKey: msg])\n                Crashlytics.crashlytics().record(error: error)\n            }\n        }\n    }\n}\n// Usage Example:\nLogger.type = .crashlytics\nLogger.error(\"Critical failure\", tag: .system)\n\n```\n\n**Introduce a New Log Level \"Important\"**\n\n**Description**: Add a new log level for messages that are more significant than `info` but not quite `warning`.\n\n**Implementation**:\n\n- **Add New Case in `LogLevel`**:\n\n```swift\npublic enum LogLevel: String, CaseIterable {\n    // Existing cases...\n    case important = \"🟢\"\n}\n```\n\n- **Add Title for the New Level**:\n\n```swift\nextension LogLevel {\n    var title: String {\n        switch self {\n        // Existing cases...\n        case .important:\n            return \"Important\"\n        }\n    }\n}\n```\n- **Add Method in `Logger+Command`**:\n\n```swift\nextension Logger {\n    public static func important(_ msg: String, tag: LogTag = .other) {\n        log(msg, level: .important, tag: tag)\n    }\n}\n```\n\n- **Usage Example**:\n\n```swift\nLogger.important(\"User achieved a significant milestone\", tag: .achievement)\n```\n\n\n**Adopt Protocol-Oriented Design**\n\n**Description**: Refactor the logger to use protocols, allowing for greater flexibility, easier testing, and adherence to SOLID principles.\n\n**Implementation**:\n\n- **Define `LoggerProtocol`**:\n\n```swift\npublic protocol LoggerProtocol {\n    func log(_ msg: String, level: LogLevel, tag: LogTag)\n}\n```\n\n- **Create Concrete Implementations**:\n\n```swift\npublic class ConsoleLogger: LoggerProtocol {\n    public func log(_ msg: String, level: LogLevel, tag: LogTag) {\n        print(msg)\n    }\n}\n\npublic class FileLogger: LoggerProtocol {\n    private let filePath: String\n\n    public init(filePath: String) {\n        self.filePath = filePath\n    }\n\n    public func log(_ msg: String, level: LogLevel, tag: LogTag) {\n        // Implement file writing logic here\n    }\n}\n```\n\n- **Modify `Logger` to Use `LoggerProtocol`**:\n\n```swift\npublic final class Logger {\n    public static var logger: LoggerProtocol = ConsoleLogger()\n    // Modify log methods to use `logger.log(...)`\n}\n```\n\n- **Usage Example**:\n```\nLogger.logger = FileLogger(filePath: \"/path/to/log.txt\")\nLogger.info(\"This will be logged to a file\")\n```\n\n**Add Support for Swift Concurrency (`async`/`await`)**\n\n**Description**: Modernize the logger to be compatible with Swift's async/await concurrency model.\n\n**Implementation**:\n\n```swift\nextension Logger {\n    public static func logAsync(_ msg: String, level: LogLevel, tag: LogTag) async {\n        await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .utility).async {\n                log(msg, level: level, tag: tag)\n                continuation.resume()\n            }\n        }\n    }\n}\nTask {\n    await Logger.logAsync(\"Asynchronous log message\", level: .info, tag: .system)\n}\n```\n\n**Description**: Ensure that logging is safe in multi-threaded environments, particularly when writing to shared resources like files.\n\n**Implementation**:\n\n- **Use Serial Dispatch Queues**:\n\n```swift\nextension LogType {\n    private static let fileWriteQueue = DispatchQueue(label: \"com.logger.fileWriteQueue\")\n\n    static func writeToFile(string: String, filePath: String) {\n        fileWriteQueue.async {\n            // File writing code...\n        }\n    }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsentryco%2Flogger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsentryco%2Flogger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsentryco%2Flogger/lists"}