Skip to content

Commit 730e39c

Browse files
committed
Add SentryCocoaLumberjack integration
1 parent 0f5f086 commit 730e39c

File tree

4 files changed

+693
-0
lines changed

4 files changed

+693
-0
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# --- macOS ---
2+
3+
# General
4+
.DS_Store
5+
__MACOSX/
6+
.AppleDouble
7+
.LSOverride
8+
Icon[]
9+
10+
# Thumbnails
11+
._*
12+
13+
# Files that might appear in the root of a volume
14+
.DocumentRevisions-V100
15+
.fseventsd
16+
.Spotlight-V100
17+
.TemporaryItems
18+
.Trashes
19+
.VolumeIcon.icns
20+
.com.apple.timemachine.donotpresent
21+
22+
# Directories potentially created on remote AFP share
23+
.AppleDB
24+
.AppleDesktop
25+
Network Trash Folder
26+
Temporary Items
27+
.apdisk
28+
29+
# --- Swift ---
30+
31+
# Xcode
32+
#
33+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
34+
35+
## User settings
36+
xcuserdata/
37+
38+
## Obj-C/Swift specific
39+
*.hmap
40+
41+
## App packaging
42+
*.ipa
43+
*.dSYM.zip
44+
*.dSYM
45+
46+
## Playgrounds
47+
timeline.xctimeline
48+
playground.xcworkspace
49+
50+
# Swift Package Manager
51+
#
52+
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
53+
# Packages/
54+
# Package.pins
55+
# Package.resolved
56+
# *.xcodeproj
57+
#
58+
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
59+
# hence it is not needed unless you have added a package configuration file to your project
60+
# .swiftpm
61+
62+
.build/
63+
64+
# CocoaPods
65+
#
66+
# We recommend against adding the Pods directory to your .gitignore. However
67+
# you should judge for yourself, the pros and cons are mentioned at:
68+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
69+
#
70+
# Pods/
71+
#
72+
# Add this line if you want to avoid checking in source code from the Xcode workspace
73+
# *.xcworkspace
74+
75+
# Carthage
76+
#
77+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
78+
# Carthage/Checkouts
79+
80+
Carthage/Build/
81+
82+
# fastlane
83+
#
84+
# It is recommended to not store the screenshots in the git repo.
85+
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
86+
# For more information about the recommended setup visit:
87+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
88+
89+
fastlane/report.xml
90+
fastlane/Preview.html
91+
fastlane/screenshots/**/*.png
92+
fastlane/test_output
93+
94+
# --- Xcode ---
95+
96+
## User settings
97+
xcuserdata/
98+
99+
# Archive
100+
*.xcarchive
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// swift-tools-version:6.0
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "SentryCocoaLumberjack",
6+
platforms: [.iOS(.v15), .macOS(.v10_14), .tvOS(.v15), .watchOS(.v8), .visionOS(.v1)],
7+
products: [
8+
.library(
9+
name: "SentryCocoaLumberjack",
10+
targets: ["SentryCocoaLumberjack"]
11+
)
12+
],
13+
dependencies: [
14+
.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack", from: "3.8.0"),
15+
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.0.0")
16+
],
17+
targets: [
18+
.target(
19+
name: "SentryCocoaLumberjack",
20+
dependencies: [
21+
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
22+
.product(name: "Sentry", package: "sentry-cocoa")
23+
]
24+
),
25+
.testTarget(
26+
name: "SentryCocoaLumberjackTests",
27+
dependencies: [
28+
"SentryCocoaLumberjack",
29+
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
30+
.product(name: "Sentry", package: "sentry-cocoa")
31+
]
32+
)
33+
]
34+
)
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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

Comments
 (0)