Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,38 @@ jobs:
if: failure()
run: ./scripts/ci-diagnostics.sh

# This will be replaced once #6945 is merged.
cocoalumberjack-integration-unit-tests:
name: SentryCocoaLumberjack Unit Tests
if: needs.files-changed.outputs.run_unit_tests_for_prs == 'true'
needs: files-changed
runs-on: macos-15
steps:
- uses: actions/checkout@v6

- name: Select Xcode
run: ./scripts/ci-select-xcode.sh 16.4

- name: Setup local sentry-cocoa dependency
working-directory: 3rd-party-integrations/SentryCocoaLumberjack
run: swift package edit sentry-cocoa --path ../..

- name: Run CocoaLumberjack tests
working-directory: 3rd-party-integrations/SentryCocoaLumberjack
run: swift test

- name: Archiving Raw Logs
uses: actions/upload-artifact@v5
if: ${{ failure() || cancelled() }}
with:
name: raw-output-cocoalumberjack-integration
path: |
3rd-party-integrations/SentryCocoaLumberjack/.build/**/*.log

- name: Run CI Diagnostics
if: failure()
run: ./scripts/ci-diagnostics.sh

# This check validates that either all unit tests passed or were skipped, which allows us
# to make unit tests a required check with only running the unit tests when required.
# So, we don't have to run unit tests, for example, for Changelog or ReadMe changes.
Expand Down
100 changes: 100 additions & 0 deletions 3rd-party-integrations/SentryCocoaLumberjack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# --- macOS ---

# General
.DS_Store
__MACOSX/
.AppleDouble
.LSOverride
Icon[]

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# --- Swift ---

# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# --- Xcode ---

## User settings
xcuserdata/

# Archive
*.xcarchive
34 changes: 34 additions & 0 deletions 3rd-party-integrations/SentryCocoaLumberjack/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// swift-tools-version:6.0
import PackageDescription

let package = Package(
name: "SentryCocoaLumberjack",
platforms: [.iOS(.v15), .macOS(.v10_14), .tvOS(.v15), .watchOS(.v8), .visionOS(.v1)],
products: [
.library(
name: "SentryCocoaLumberjack",
targets: ["SentryCocoaLumberjack"]
)
],
dependencies: [
.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack", from: "3.8.0"),
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.0.0")
],
targets: [
.target(
name: "SentryCocoaLumberjack",
dependencies: [
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
.product(name: "Sentry", package: "sentry-cocoa")
]
),
.testTarget(
name: "SentryCocoaLumberjackTests",
dependencies: [
"SentryCocoaLumberjack",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
.product(name: "Sentry", package: "sentry-cocoa")
]
)
]
)
87 changes: 87 additions & 0 deletions 3rd-party-integrations/SentryCocoaLumberjack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Sentry CocoaLumberjack Integration

A [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack) logger that forwards log entries to Sentry's structured logging system, automatically capturing application logs with full context including source location, thread information, and log levels.

> [!NOTE]
> This repo is a mirror of [github.com/getsentry/sentry-cocoa](https://github.com/getsentry/sentry-cocoa). The source code lives in `3rd-party-integrations/SentryCocoaLumberjack/`. This allows users to import only what they need via SPM while keeping all integration code in the main repository.

## Installation

### Swift Package Manager

Add the following dependencies to your `Package.swift` or Xcode package dependencies:

```swift
dependencies: [
.package(url: "https://github.com/getsentry/sentry-cocoa-cocoalumberjack", from: "9.0.0")
]
```

## Quick Start

```swift
import Sentry
import SentryCocoaLumberjack
import CocoaLumberjackSwift

SentrySDK.start { options in
options.dsn = "YOUR_DSN"
options.enableLogs = true
}

DDLog.add(SentryCocoaLumberjackLogger(), with: .info)

DDLogInfo("User logged in")
DDLogError("Payment failed")
DDLogWarn("API rate limit approaching")
DDLogDebug("Processing request")
DDLogVerbose("Detailed trace information")
```

## Configuration

### Log Level Threshold

Use CocoaLumberjack's built-in filtering API when adding the logger to set the minimum log level for messages to be sent to Sentry:

```swift
// Only send error logs to Sentry
DDLog.add(SentryCocoaLumberjackLogger(), with: .error)
```

## Log Level Mapping

CocoaLumberjack log levels are automatically mapped to Sentry log levels:

| CocoaLumberjack Level | Sentry Log Level |
| --------------------- | ---------------- |
| `.error` | `.error` |
| `.warning` | `.warn` |
| `.info` | `.info` |
| `.debug` | `.debug` |
| `.verbose` | `.trace` |

## Automatic Attributes

The logger automatically includes the following attributes with every log entry:

- `sentry.origin`: `"auto.logging.cocoalumberjack"`
- `cocoalumberjack.level`: The original CocoaLumberjack log level (error, warning, info, debug, verbose)
- `cocoalumberjack.file`: The source file name
- `cocoalumberjack.function`: The function name
- `cocoalumberjack.line`: The line number
- `cocoalumberjack.context`: The log context (integer)
- `cocoalumberjack.timestamp`: The log timestamp
- `cocoalumberjack.threadID`: The thread ID
- `cocoalumberjack.threadName`: The thread name (if available)
- `cocoalumberjack.queueLabel`: The dispatch queue label (if available)

## Documentation

- [Sentry Cocoa SDK Documentation](https://docs.sentry.io/platforms/apple/)
- [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/)
- [CocoaLumberjack Repo](https://github.com/CocoaLumberjack/CocoaLumberjack)

## License

This integration follows the same license as the Sentry Cocoa SDK. See the [LICENSE](https://github.com/getsentry/sentry-cocoa/blob/main/LICENSE.md) file for details.
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import CocoaLumberjackSwift
import Sentry

/// A CocoaLumberjack logger that forwards log entries to Sentry's structured logging system.
///
/// `SentryCocoaLumberjackLogger` implements CocoaLumberjack's `DDAbstractLogger` protocol, allowing you
/// to integrate Sentry's structured logging capabilities with CocoaLumberjack. This enables you to capture
/// application logs from CocoaLumberjack and send them to Sentry for analysis and monitoring.
///
/// ## Level Filtering
/// Use CocoaLumberjack's built-in filtering API when adding the logger:
/// ```swift
/// DDLog.add(SentryCocoaLumberjackLogger(), with: .info)
/// ```
/// This ensures only logs at or above the specified level are sent to Sentry.
///
/// ## Level Mapping
/// CocoaLumberjack log levels are mapped to Sentry log levels:
/// - `.error` → `.error`
/// - `.warning` → `.warn`
/// - `.info` → `.info`
/// - `.debug` → `.debug`
/// - `.verbose` → `.trace`
///
/// ## Usage
/// ```swift
/// import CocoaLumberjackSwift
/// import Sentry
/// import SentryCocoaLumberjack
///
/// // Initialize Sentry SDK
/// SentrySDK.start { options in
/// options.dsn = "YOUR_DSN"
/// }
///
/// // Add SentryCocoaLumberjackLogger to CocoaLumberjack
/// // Only logs at .info level and above will be sent to Sentry
/// DDLog.add(SentryCocoaLumberjackLogger(), with: .info)
///
/// // Use CocoaLumberjack as usual
/// DDLogInfo("User logged in")
/// DDLogError("Payment failed")
/// ```
///
/// - Note: Sentry Logs is currently in Beta. See the [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/).
/// - Warning: This logger requires Sentry SDK to be initialized before use.
public class SentryCocoaLumberjackLogger: DDAbstractLogger {

/// Creates a new SentryCocoaLumberjackLogger instance.
///
/// - Note: Make sure to initialize the Sentry SDK before creating this logger.
/// Use `DDLog.add(_:with:)` to configure log level filtering.
public override init() {
super.init()
}

/// Logs a message from CocoaLumberjack to Sentry.
///
/// - Parameter logMessage: The log message from CocoaLumberjack containing the message, level, and metadata.
public override func log(message logMessage: DDLogMessage) {
var attributes: [String: Any] = [:]
attributes["sentry.origin"] = "auto.logging.cocoalumberjack"

attributes["cocoalumberjack.level"] = logFlagToString(logMessage.flag)
attributes["cocoalumberjack.file"] = logMessage.file
attributes["cocoalumberjack.function"] = logMessage.function ?? ""
attributes["cocoalumberjack.line"] = String(logMessage.line)
attributes["cocoalumberjack.context"] = String(logMessage.context)
attributes["cocoalumberjack.timestamp"] = logMessage.timestamp.timeIntervalSince1970
attributes["cocoalumberjack.threadID"] = String(logMessage.threadID)

if let threadName = logMessage.threadName, !threadName.isEmpty {
attributes["cocoalumberjack.threadName"] = threadName
}

if !logMessage.queueLabel.isEmpty {
attributes["cocoalumberjack.queueLabel"] = logMessage.queueLabel
}

forwardToSentry(message: logMessage.message, flag: logMessage.flag, attributes: attributes)
}

private func forwardToSentry(message: String, flag: DDLogFlag, attributes: [String: Any]) {
if flag.contains(.error) {
SentrySDK.logger.error(message, attributes: attributes)
} else if flag.contains(.warning) {
SentrySDK.logger.warn(message, attributes: attributes)
} else if flag.contains(.info) {
SentrySDK.logger.info(message, attributes: attributes)
} else if flag.contains(.debug) {
SentrySDK.logger.debug(message, attributes: attributes)
} else if flag.contains(.verbose) {
SentrySDK.logger.trace(message, attributes: attributes)
} else {
SentrySDK.logger.info(message, attributes: attributes)
}
}

private func logFlagToString(_ flag: DDLogFlag) -> String {
if flag.contains(.error) {
return "error"
} else if flag.contains(.warning) {
return "warning"
} else if flag.contains(.info) {
return "info"
} else if flag.contains(.debug) {
return "debug"
} else if flag.contains(.verbose) {
return "verbose"
} else {
return "unknown"
}
}
}
Loading
Loading