|
| 1 | +import CocoaLumberjackSwift |
| 2 | +import Sentry |
| 3 | + |
| 4 | +/// A CocoaLumberjack logger that forwards log entries to Sentry's structured logging system. |
| 5 | +/// |
| 6 | +/// `SentryCocoaLumberjackLogger` implements CocoaLumberjack's `DDAbstractLogger` protocol, allowing you |
| 7 | +/// to integrate Sentry's structured logging capabilities with CocoaLumberjack. This enables you to capture |
| 8 | +/// application logs from CocoaLumberjack and send them to Sentry for analysis and monitoring. |
| 9 | +/// |
| 10 | +/// ## Level Filtering |
| 11 | +/// The logger supports filtering by log level. Only logs at or above the configured `logLevel` will be |
| 12 | +/// sent to Sentry. Defaults to `.info`. |
| 13 | +/// |
| 14 | +/// ## Level Mapping |
| 15 | +/// CocoaLumberjack log levels are mapped to Sentry log levels: |
| 16 | +/// - `.error` → `.error` |
| 17 | +/// - `.warning` → `.warn` |
| 18 | +/// - `.info` → `.info` |
| 19 | +/// - `.debug` → `.debug` |
| 20 | +/// - `.verbose` → `.trace` |
| 21 | +/// |
| 22 | +/// ## Usage |
| 23 | +/// ```swift |
| 24 | +/// import CocoaLumberjackSwift |
| 25 | +/// import Sentry |
| 26 | +/// import SentryCocoaLumberjack |
| 27 | +/// |
| 28 | +/// // Initialize Sentry SDK |
| 29 | +/// SentrySDK.start { options in |
| 30 | +/// options.dsn = "YOUR_DSN" |
| 31 | +/// } |
| 32 | +/// |
| 33 | +/// // Add SentryCocoaLumberjackLogger to CocoaLumberjack |
| 34 | +/// // Only logs at .info level and above will be sent to Sentry |
| 35 | +/// DDLog.add(SentryCocoaLumberjackLogger(logLevel: .info)) |
| 36 | +/// |
| 37 | +/// // Use CocoaLumberjack as usual |
| 38 | +/// DDLogInfo("User logged in") |
| 39 | +/// DDLogError("Payment failed") |
| 40 | +/// ``` |
| 41 | +/// |
| 42 | +/// - Note: Sentry Logs is currently in Beta. See the [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/). |
| 43 | +/// - Warning: This logger requires Sentry SDK to be initialized before use. |
| 44 | +public class SentryCocoaLumberjackLogger: DDAbstractLogger { |
| 45 | + |
| 46 | + private let sentryLogger: SentryLogger |
| 47 | + |
| 48 | + /// The minimum log level for messages to be sent to Sentry. |
| 49 | + /// |
| 50 | + /// Messages below this level will be filtered out and not sent to Sentry. |
| 51 | + /// Defaults to `.info`. |
| 52 | + public var logLevel: DDLogLevel |
| 53 | + |
| 54 | + /// Creates a new SentryCocoaLumberjackLogger instance. |
| 55 | + /// |
| 56 | + /// - Parameter logLevel: The minimum log level for messages to be sent to Sentry. |
| 57 | + /// Defaults to `.info`. |
| 58 | + /// - Note: Make sure to initialize the Sentry SDK before creating this logger. |
| 59 | + public init(logLevel: DDLogLevel = .info) { |
| 60 | + self.sentryLogger = SentrySDK.logger |
| 61 | + self.logLevel = logLevel |
| 62 | + super.init() |
| 63 | + } |
| 64 | + |
| 65 | + init(logLevel: DDLogLevel = .info, sentryLogger: SentryLogger) { |
| 66 | + self.sentryLogger = sentryLogger |
| 67 | + self.logLevel = logLevel |
| 68 | + super.init() |
| 69 | + } |
| 70 | + |
| 71 | + /// Logs a message from CocoaLumberjack to Sentry. |
| 72 | + /// |
| 73 | + /// - Parameter logMessage: The log message from CocoaLumberjack containing the message, level, and metadata. |
| 74 | + public override func log(message logMessage: DDLogMessage) { |
| 75 | + guard logMessage.level.rawValue <= logLevel.rawValue else { |
| 76 | + return |
| 77 | + } |
| 78 | + |
| 79 | + var attributes: [String: Any] = [:] |
| 80 | + attributes["sentry.origin"] = "auto.logging.cocoalumberjack" |
| 81 | + |
| 82 | + attributes["cocoalumberjack.level"] = logFlagToString(logMessage.flag) |
| 83 | + attributes["cocoalumberjack.file"] = logMessage.file |
| 84 | + attributes["cocoalumberjack.function"] = logMessage.function ?? "" |
| 85 | + attributes["cocoalumberjack.line"] = String(logMessage.line) |
| 86 | + attributes["cocoalumberjack.context"] = String(logMessage.context) |
| 87 | + attributes["cocoalumberjack.timestamp"] = logMessage.timestamp.timeIntervalSince1970 |
| 88 | + attributes["cocoalumberjack.threadID"] = String(logMessage.threadID) |
| 89 | + |
| 90 | + if let threadName = logMessage.threadName, !threadName.isEmpty { |
| 91 | + attributes["cocoalumberjack.threadName"] = threadName |
| 92 | + } |
| 93 | + |
| 94 | + if !logMessage.queueLabel.isEmpty { |
| 95 | + attributes["cocoalumberjack.queueLabel"] = logMessage.queueLabel |
| 96 | + } |
| 97 | + |
| 98 | + forwardToSentry(message: logMessage.message, flag: logMessage.flag, attributes: attributes) |
| 99 | + } |
| 100 | + |
| 101 | + private func forwardToSentry(message: String, flag: DDLogFlag, attributes: [String: Any]) { |
| 102 | + if flag.contains(.error) { |
| 103 | + sentryLogger.error(message, attributes: attributes) |
| 104 | + } else if flag.contains(.warning) { |
| 105 | + sentryLogger.warn(message, attributes: attributes) |
| 106 | + } else if flag.contains(.info) { |
| 107 | + sentryLogger.info(message, attributes: attributes) |
| 108 | + } else if flag.contains(.debug) { |
| 109 | + sentryLogger.debug(message, attributes: attributes) |
| 110 | + } else if flag.contains(.verbose) { |
| 111 | + sentryLogger.trace(message, attributes: attributes) |
| 112 | + } else { |
| 113 | + sentryLogger.info(message, attributes: attributes) |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + private func logFlagToString(_ flag: DDLogFlag) -> String { |
| 118 | + if flag.contains(.error) { |
| 119 | + return "error" |
| 120 | + } else if flag.contains(.warning) { |
| 121 | + return "warning" |
| 122 | + } else if flag.contains(.info) { |
| 123 | + return "info" |
| 124 | + } else if flag.contains(.debug) { |
| 125 | + return "debug" |
| 126 | + } else if flag.contains(.verbose) { |
| 127 | + return "verbose" |
| 128 | + } else { |
| 129 | + return "unknown" |
| 130 | + } |
| 131 | + } |
| 132 | +} |
0 commit comments