From 8f396d4181c22258bd0d44177a99050bbca97400 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 1 Dec 2025 16:48:53 +0100 Subject: [PATCH 01/11] Add `SenrtySwiftLog` integration --- .../logs/sentry-cocoa-swiftlog/Package.swift | 34 ++ .../logs/sentry-cocoa-swiftlog/README.md | 187 +++++++++++ .../Sources/SentryLogHandler.swift | 125 ++++++++ .../Tests/SentryLogHandlerTests.swift | 296 ++++++++++++++++++ 4 files changed, 642 insertions(+) create mode 100644 integrations/logs/sentry-cocoa-swiftlog/Package.swift create mode 100644 integrations/logs/sentry-cocoa-swiftlog/README.md create mode 100644 integrations/logs/sentry-cocoa-swiftlog/Sources/SentryLogHandler.swift create mode 100644 integrations/logs/sentry-cocoa-swiftlog/Tests/SentryLogHandlerTests.swift diff --git a/integrations/logs/sentry-cocoa-swiftlog/Package.swift b/integrations/logs/sentry-cocoa-swiftlog/Package.swift new file mode 100644 index 0000000000..56822f20c3 --- /dev/null +++ b/integrations/logs/sentry-cocoa-swiftlog/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:6.0 +import PackageDescription + +let package = Package( + name: "SentrySwiftLog", + platforms: [.iOS(.v15), .macOS(.v10_14), .tvOS(.v15), .watchOS(.v8), .visionOS(.v1)], + products: [ + .library( + name: "SentrySwiftLog", + targets: ["SentrySwiftLog"] + ) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-log", from: "1.5.0"), + .package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.0.0") + ], + targets: [ + .target( + name: "SentrySwiftLog", + dependencies: [ + .product(name: "Logging", package: "swift-log"), + .product(name: "Sentry", package: "sentry-cocoa") + ] + ), + .testTarget( + name: "SentrySwiftLogTests", + dependencies: [ + "SentrySwiftLog", + .product(name: "Logging", package: "swift-log"), + .product(name: "Sentry", package: "sentry-cocoa") + ] + ) + ] +) diff --git a/integrations/logs/sentry-cocoa-swiftlog/README.md b/integrations/logs/sentry-cocoa-swiftlog/README.md new file mode 100644 index 0000000000..14bb4f0cdd --- /dev/null +++ b/integrations/logs/sentry-cocoa-swiftlog/README.md @@ -0,0 +1,187 @@ +# Sentry Swift-Log Integration + +A `swift-log` handler that forwards log entries to Sentry's structured logging system, automatically capturing application logs with full context including metadata, source location, and log levels. + +## Installation + +### Swift Package Manager + +Add the following dependencies to your `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.0.0"), + .package(url: "https://github.com/getsentry/sentry-cocoa-swiftlog", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-log", from: "1.5.0") +] +``` + +Then add the required products to your target dependencies: + +```swift +.target( + name: "YourTarget", + dependencies: [ + .product(name: "Sentry", package: "sentry-cocoa"), // Required for SentrySDK initialization + .product(name: "SentrySwiftLog", package: "sentry-cocoa-swiftlog"), + .product(name: "Logging", package: "swift-log") // Required for Logger and LoggingSystem + ] +) +``` + +## Quick Start + +### 1. Initialize Sentry SDK + +First, initialize the Sentry SDK with logs enabled: + +```swift +import Sentry + +SentrySDK.start { options in + options.dsn = "YOUR_DSN" + options.enableLogs = true // Required for logging integration +} +``` + +### 2. Register SentryLogHandler + +Bootstrap the logging system with `SentryLogHandler`: + +```swift +import Logging + +LoggingSystem.bootstrap { _ in + return SentryLogHandler(logLevel: .info) +} +``` + +### 3. Use the Logger + +Create and use loggers throughout your application: + +```swift +let logger = Logger(label: "com.example.app") + +logger.info("User logged in", metadata: ["userId": "12345"]) +logger.error("Payment failed", metadata: ["errorCode": 500]) +logger.warning("API rate limit approaching", metadata: ["remaining": 10]) +``` + +## Configuration + +### Log Level Threshold + +Set the minimum log level for messages to be sent to Sentry: + +```swift +// Only send info level and above +let handler = SentryLogHandler(logLevel: .info) + +// Send all logs including trace and debug +let handler = SentryLogHandler(logLevel: .trace) +``` + +Messages below the configured threshold will be filtered out and not sent to Sentry. + +### Handler Metadata + +Add metadata that will be included with all log entries: + +```swift +var handler = SentryLogHandler(logLevel: .info) +handler.metadata["app_version"] = "1.0.0" +handler.metadata["environment"] = "production" + +LoggingSystem.bootstrap { _ in handler } +``` + +Handler metadata is merged with call-site metadata, with call-site metadata taking precedence. + +### Metadata Subscript Access + +You can also access metadata using subscript syntax: + +```swift +var handler = SentryLogHandler(logLevel: .info) +handler[metadataKey: "app_version"] = "1.0.0" +let version = handler[metadataKey: "app_version"] +``` + +## Log Level Mapping + +`swift-log` levels are automatically mapped to Sentry log levels: + +| swift-log Level | Sentry Log Level | +| --------------- | ---------------- | +| `.trace` | `.trace` | +| `.debug` | `.debug` | +| `.info` | `.info` | +| `.notice` | `.info` | +| `.warning` | `.warn` | +| `.error` | `.error` | +| `.critical` | `.fatal` | + +## Metadata Handling + +The handler supports all `swift-log` metadata types: + +### String Metadata + +```swift +logger.info("User action", metadata: [ + "userId": "12345", + "action": "purchase" +]) +``` + +### Dictionary Metadata + +```swift +logger.info("User profile", metadata: [ + "user": [ + "id": "12345", + "name": "John Doe" + ] +]) +``` + +### Array Metadata + +```swift +logger.info("Tags", metadata: [ + "tags": ["production", "api", "v2"] +]) +``` + +### String Convertible Metadata + +```swift +logger.info("Metrics", metadata: [ + "count": 42, + "enabled": true, + "score": 3.14159 +]) +``` + +All metadata is automatically prefixed with `swift-log.` in Sentry attributes (e.g., `swift-log.userId`, `swift-log.user.id`). + +## Automatic Attributes + +The handler automatically includes the following attributes with every log entry: + +- `sentry.origin`: `"auto.logging.swift-log"` +- `swift-log.level`: The original swift-log level +- `swift-log.source`: The log source +- `swift-log.file`: The source file name +- `swift-log.function`: The function name +- `swift-log.line`: The line number + +## Documentation + +- [Sentry Cocoa SDK Documentation](https://docs.sentry.io/platforms/apple/) +- [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/) + +## 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. diff --git a/integrations/logs/sentry-cocoa-swiftlog/Sources/SentryLogHandler.swift b/integrations/logs/sentry-cocoa-swiftlog/Sources/SentryLogHandler.swift new file mode 100644 index 0000000000..f734a208f1 --- /dev/null +++ b/integrations/logs/sentry-cocoa-swiftlog/Sources/SentryLogHandler.swift @@ -0,0 +1,125 @@ +import Logging +import Sentry + +/// A `swift-log` handler that forwards log entries to Sentry's structured logging system. +/// +/// `SentryLogHandler` implements the `swift-log` `LogHandler` protocol, allowing you to integrate +/// Sentry's structured logging capabilities with Swift's standard logging framework. This enables +/// you to capture application logs and send them to Sentry for analysis and monitoring. +/// +/// ## Level Mapping +/// `swift-log` levels are mapped to Sentry log levels: +/// - `.trace` → `.trace` +/// - `.debug` → `.debug` +/// - `.info` → `.info` +/// - `.notice` → `.info` (notice maps to info as SentryLog doesn't have notice) +/// - `.warning` → `.warn` +/// - `.error` → `.error` +/// - `.critical` → `.fatal` +/// +/// ## Usage +/// ```swift +/// import Logging +/// import Sentry +/// +/// // Initialize Sentry SDK +/// SentrySDK.start { options in +/// options.dsn = "YOUR_DSN" +/// } +/// +/// // Register SentryLogHandler +/// LoggingSystem.bootstrap { _ in +/// return SentryLogHandler(logLevel: .trace) +/// } +/// +/// // Create & use the logger +/// let logger = Logger(label: "com.example.app") +/// logger.info("User logged in", metadata: ["userId": "12345"]) +/// logger.error("Payment failed", metadata: ["errorCode": 500]) +/// ``` +/// +/// - Note: Sentry Logs is currently in Beta. See the [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/). +/// - Warning: This handler requires Sentry SDK to be initialized before use. +public struct SentryLogHandler: LogHandler { + + /// Logger metadata that will be included with all log entries. + /// + /// This metadata is merged with any metadata provided at the call site, + /// with call-site metadata taking precedence over handler metadata. + public var metadata = Logger.Metadata() + + /// The minimum log level for messages to be sent to Sentry. + /// + /// Messages below this level will be filtered out and not sent to Sentry. + /// Defaults to `.info`. + public var logLevel: Logger.Level + + /// Creates a new SentryLogHandler with the specified log level. + /// + /// - Parameter logLevel: The minimum log level for messages to be sent to Sentry. + /// Defaults to `.info`. + public init(logLevel: Logger.Level = .info) { + self.logLevel = logLevel + } + + public func log( + level: Logger.Level, + message: Logger.Message, + metadata: Logger.Metadata?, + source: String, + file: String, + function: String, + line: UInt + ) { + // Filter out messages below the configured log level threshold + guard level >= self.logLevel else { + return + } + + var attributes: [String: Any] = [:] + attributes["sentry.origin"] = "auto.logging.swift-log" + attributes["swift-log.level"] = level.rawValue + attributes["swift-log.source"] = source + attributes["swift-log.file"] = file + attributes["swift-log.function"] = function + attributes["swift-log.line"] = String(line) + + let allMetadata = self.metadata.merging(metadata ?? [:]) { _, new in + new + } + for (key, value) in allMetadata { + attributes["swift-log.\(key)"] = "\(value)" + } + + // Call the appropriate SentryLog method based on level + let messageString = String(describing: message) + let logger = SentrySDK.logger + + switch level { + case .trace: + logger.trace(messageString, attributes: attributes) + case .debug: + logger.debug(messageString, attributes: attributes) + case .info: + logger.info(messageString, attributes: attributes) + case .notice: + // Map notice to info as SentryLog doesn't have notice + logger.info(messageString, attributes: attributes) + case .warning: + logger.warn(messageString, attributes: attributes) + case .error: + logger.error(messageString, attributes: attributes) + case .critical: + logger.fatal(messageString, attributes: attributes) + } + } + + public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { + get { + metadata[metadataKey] + } + set(newValue) { + metadata[metadataKey] = newValue + } + } +} diff --git a/integrations/logs/sentry-cocoa-swiftlog/Tests/SentryLogHandlerTests.swift b/integrations/logs/sentry-cocoa-swiftlog/Tests/SentryLogHandlerTests.swift new file mode 100644 index 0000000000..45896a69ef --- /dev/null +++ b/integrations/logs/sentry-cocoa-swiftlog/Tests/SentryLogHandlerTests.swift @@ -0,0 +1,296 @@ +import Logging +import Sentry +@testable import SentrySwiftLog +import XCTest + +final class SentryLogHandlerTests: XCTestCase { + + private var capturedLogs: [SentryLog] = [] + + override func setUp() { + super.setUp() + capturedLogs = [] + SentrySDK.start { options in + options.dsn = "https://test@test.ingest.sentry.io/123456" + options.enableLogs = true + options.beforeSendLog = { [weak self] log in + self?.capturedLogs.append(log) + return nil + } + } + } + + override func tearDown() { + super.tearDown() + SentrySDK.close() + capturedLogs = [] + } + + // MARK: - Basic Logging Tests + + func testLog_WithInfoLevel() throws { + let sut = SentryLogHandler(logLevel: .info) + sut.log(level: .info, message: "Test info message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 42) + + XCTAssertEqual(capturedLogs.count, 1, "Expected exactly one log to be captured") + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .info) + XCTAssertEqual(log.body, "Test info message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swift-log") + XCTAssertEqual(log.attributes["swift-log.level"]?.value as? String, "info") + XCTAssertEqual(log.attributes["swift-log.source"]?.value as? String, "test") + XCTAssertEqual(log.attributes["swift-log.file"]?.value as? String, "TestFile.swift") + XCTAssertEqual(log.attributes["swift-log.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swift-log.line"]?.value as? String, "42") + } + + func testLog_WithErrorLevel() throws { + let sut = SentryLogHandler(logLevel: .info) + sut.log(level: .error, message: "Test error message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 100) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .error) + XCTAssertEqual(log.body, "Test error message") + XCTAssertEqual(log.attributes["swift-log.level"]?.value as? String, "error") + } + + func testLog_WithTraceLevel() throws { + let sut = SentryLogHandler(logLevel: .trace) + sut.log(level: .trace, message: "Test trace message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 1) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .trace) + XCTAssertEqual(log.body, "Test trace message") + XCTAssertEqual(log.attributes["swift-log.level"]?.value as? String, "trace") + } + + func testLog_WithDebugLevel() throws { + let sut = SentryLogHandler(logLevel: .debug) + sut.log(level: .debug, message: "Test debug message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 50) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .debug) + XCTAssertEqual(log.body, "Test debug message") + XCTAssertEqual(log.attributes["swift-log.level"]?.value as? String, "debug") + } + + func testLog_WithWarningLevel() throws { + let sut = SentryLogHandler(logLevel: .info) + sut.log(level: .warning, message: "Test warning message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 75) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .warn) + XCTAssertEqual(log.body, "Test warning message") + XCTAssertEqual(log.attributes["swift-log.level"]?.value as? String, "warning") + } + + func testLog_WithCriticalLevel() throws { + let sut = SentryLogHandler(logLevel: .info) + sut.log(level: .critical, message: "Test critical message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 200) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .fatal) + XCTAssertEqual(log.body, "Test critical message") + XCTAssertEqual(log.attributes["swift-log.level"]?.value as? String, "critical") + } + + func testLog_WithNoticeLevel() throws { + let sut = SentryLogHandler(logLevel: .info) + sut.log(level: .notice, message: "Test notice message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 150) + + // Notice should map to info + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .info) + XCTAssertEqual(log.body, "Test notice message") + XCTAssertEqual(log.attributes["swift-log.level"]?.value as? String, "notice") + } + + // MARK: - Metadata Tests + + func testLog_WithStringMetadata() throws { + let sut = SentryLogHandler(logLevel: .info) + let metadata: Logger.Metadata = [ + "user_id": "12345", + "session_id": "abc-def-ghi" + ] + + sut.log(level: .info, message: "Test with metadata", metadata: metadata, source: "test", file: "TestFile.swift", function: "testFunction", line: 10) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.attributes["swift-log.user_id"]?.value as? String, "12345") + XCTAssertEqual(log.attributes["swift-log.session_id"]?.value as? String, "abc-def-ghi") + } + + func testLog_WithHandlerMetadata() throws { + var sut = SentryLogHandler(logLevel: .info) + sut.metadata["app_version"] = "1.0.0" + sut.metadata["environment"] = "test" + + sut.log(level: .info, message: "Test with handler metadata", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 20) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.attributes["swift-log.app_version"]?.value as? String, "1.0.0") + XCTAssertEqual(log.attributes["swift-log.environment"]?.value as? String, "test") + } + + func testLog_WithMergedMetadata() throws { + var sut = SentryLogHandler(logLevel: .info) + sut.metadata["app_version"] = "1.0.0" + + let logMetadata: Logger.Metadata = [ + "user_id": "12345", + "app_version": "2.0.0" // This should override handler metadata + ] + + sut.log(level: .info, message: "Test with merged metadata", metadata: logMetadata, source: "test", file: "TestFile.swift", function: "testFunction", line: 30) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.attributes["swift-log.user_id"]?.value as? String, "12345") + XCTAssertEqual(log.attributes["swift-log.app_version"]?.value as? String, "2.0.0") // Should be overridden + } + + func testLog_WithStringConvertibleMetadata() throws { + let sut = SentryLogHandler(logLevel: .info) + let metadata: Logger.Metadata = [ + "count": Logger.MetadataValue.stringConvertible(42), + "enabled": Logger.MetadataValue.stringConvertible(true), + "score": Logger.MetadataValue.stringConvertible(3.14159) + ] + + sut.log(level: .info, message: "Test with string convertible metadata", metadata: metadata, source: "test", file: "TestFile.swift", function: "testFunction", line: 40) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.attributes["swift-log.count"]?.value as? String, "42") + XCTAssertEqual(log.attributes["swift-log.enabled"]?.value as? String, "true") + XCTAssertEqual(log.attributes["swift-log.score"]?.value as? String, "3.14159") + } + + func testLog_WithDictionaryMetadata() throws { + let sut = SentryLogHandler(logLevel: .info) + let metadata: Logger.Metadata = [ + "user": Logger.MetadataValue.dictionary([ + "id": "12345", + "name": "John Doe" + ]) + ] + + sut.log(level: .info, message: "Test with dictionary metadata", metadata: metadata, source: "test", file: "TestFile.swift", function: "testFunction", line: 50) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + let userString = log.attributes["swift-log.user"]?.value as? String + XCTAssertNotNil(userString) + XCTAssertTrue(userString?.contains("\"id\": \"12345\"") ?? false) + XCTAssertTrue(userString?.contains("\"name\": \"John Doe\"") ?? false) + } + + func testLog_WithArrayMetadata() throws { + let sut = SentryLogHandler(logLevel: .info) + let metadata: Logger.Metadata = [ + "tags": Logger.MetadataValue.array([ + Logger.MetadataValue.string("production"), + Logger.MetadataValue.string("api"), + Logger.MetadataValue.stringConvertible(42) + ]) + ] + + sut.log(level: .info, message: "Test with array metadata", metadata: metadata, source: "test", file: "TestFile.swift", function: "testFunction", line: 60) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + let tagsString = log.attributes["swift-log.tags"]?.value as? String + XCTAssertNotNil(tagsString) + XCTAssertTrue(tagsString?.contains("\"production\"") ?? false) + XCTAssertTrue(tagsString?.contains("\"api\"") ?? false) + XCTAssertTrue(tagsString?.contains("\"42\"") ?? false) + } + + // MARK: - Log Level Configuration Tests + + func testLogLevelConfiguration() { + let sut = SentryLogHandler(logLevel: .info) + XCTAssertEqual(sut.logLevel, .info) + } + + func testLogLevelFiltering_InfoThreshold_FiltersTraceAndDebug() { + let sut = SentryLogHandler(logLevel: .info) + + sut.log(level: .trace, message: "Trace message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 1) + sut.log(level: .debug, message: "Debug message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 2) + + XCTAssertEqual(capturedLogs.count, 0, "Expected no logs to be captured") + } + + func testLogLevelFiltering_InfoThreshold_CapturesInfo() { + let sut = SentryLogHandler(logLevel: .info) + + sut.log(level: .info, message: "Info message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 3) + + XCTAssertEqual(capturedLogs.count, 1) + XCTAssertEqual(capturedLogs.first?.level, .info) + } + + func testLogLevelFiltering_InfoThreshold_CapturesNotice() { + let sut = SentryLogHandler(logLevel: .info) + + sut.log(level: .notice, message: "Notice message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 4) + + XCTAssertEqual(capturedLogs.count, 1) + XCTAssertEqual(capturedLogs.first?.level, .info) // Notice maps to info + } + + func testLogLevelFiltering_InfoThreshold_CapturesWarning() { + let sut = SentryLogHandler(logLevel: .info) + + sut.log(level: .warning, message: "Warning message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 5) + + XCTAssertEqual(capturedLogs.count, 1) + XCTAssertEqual(capturedLogs.first?.level, .warn) + } + + func testLogLevelFiltering_InfoThreshold_CapturesError() { + let sut = SentryLogHandler(logLevel: .info) + + sut.log(level: .error, message: "Error message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 6) + + XCTAssertEqual(capturedLogs.count, 1) + XCTAssertEqual(capturedLogs.first?.level, .error) + } + + func testLogLevelFiltering_InfoThreshold_CapturesCritical() { + let sut = SentryLogHandler(logLevel: .info) + + sut.log(level: .critical, message: "Critical message", metadata: nil, source: "test", file: "TestFile.swift", function: "testFunction", line: 7) + + XCTAssertEqual(capturedLogs.count, 1) + XCTAssertEqual(capturedLogs.first?.level, .fatal) + } + + func testMetadataSubscript() { + var sut = SentryLogHandler(logLevel: .info) + XCTAssertNil(sut.metadata["test_key"]) + XCTAssertNil(sut[metadataKey: "test_key_2"]) + + sut.metadata["test_key"] = "test_value" + XCTAssertEqual(sut.metadata["test_key"], .string("test_value")) + + sut[metadataKey: "test_key_2"] = "test_value_2" + XCTAssertEqual(sut[metadataKey: "test_key_2"], .string("test_value_2")) + + sut.metadata["test_key"] = nil + XCTAssertNil(sut.metadata["test_key"]) + + sut[metadataKey: "test_key_2"] = nil + XCTAssertNil(sut[metadataKey: "test_key_2"]) + } +} From 2e79d06017be4315aa42a54af565afebf2e2a413 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 1 Dec 2025 16:50:51 +0100 Subject: [PATCH 02/11] Add default `swift package init` .gitignore --- integrations/logs/sentry-cocoa-swiftlog/.gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 integrations/logs/sentry-cocoa-swiftlog/.gitignore diff --git a/integrations/logs/sentry-cocoa-swiftlog/.gitignore b/integrations/logs/sentry-cocoa-swiftlog/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/integrations/logs/sentry-cocoa-swiftlog/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc From 9fdbc3953dbfb646eee52f68cf5c9809046a5f9d Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Wed, 3 Dec 2025 10:54:43 +0100 Subject: [PATCH 03/11] Add ci job to run test --- .github/file-filters.yml | 1 + .github/workflows/test.yml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/.github/file-filters.yml b/.github/file-filters.yml index bbae62f26e..376740bab4 100644 --- a/.github/file-filters.yml +++ b/.github/file-filters.yml @@ -25,6 +25,7 @@ run_unit_tests_for_prs: &run_unit_tests_for_prs - "SentryTestUtils/**" - "SentryTestUtilsDynamic/**" - "SentryTestUtilsTests/**" + - "integrations/**" # GH Actions - ".github/workflows/test.yml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15075829c7..eae00d3c93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -369,6 +369,36 @@ jobs: device: "Apple TV" scheme: "Sentry" + swiftlog-integration-unit-tests: + name: SentrySwiftLog Unit Tests + runs-on: macos-15 + # TODO: Add gate when this is run before merging... + steps: + - uses: actions/checkout@v6 + + - name: Select Xcode + run: ./scripts/ci-select-xcode.sh 16.4 + + - name: Setup local sentry-cocoa dependency + working-directory: integrations/logs/sentry-cocoa-swiftlog + run: swift package edit sentry-cocoa --path ../../.. + + - name: Run SwiftLog tests + working-directory: integrations/logs/sentry-cocoa-swiftlog + run: swift test + + - name: Archiving Raw Logs + uses: actions/upload-artifact@v5 + if: ${{ failure() || cancelled() }} + with: + name: raw-output-swiftlog-integration + path: | + integrations/logs/sentry-cocoa-swiftlog/.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. From b09685284445e402fa6fdaa5dc618d7cdaf3ea26 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Wed, 3 Dec 2025 11:01:29 +0100 Subject: [PATCH 04/11] Add files-changed gate --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eae00d3c93..151e66a16c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -370,9 +370,10 @@ jobs: scheme: "Sentry" swiftlog-integration-unit-tests: - name: SentrySwiftLog Unit Tests + name: Integrations/Logs/SentrySwiftLog Unit Tests + if: needs.files-changed.outputs.run_unit_tests_for_prs == 'true' + needs: files-changed runs-on: macos-15 - # TODO: Add gate when this is run before merging... steps: - uses: actions/checkout@v6 From 5a7218780699b0a194f26f8e3ab70f8f8b6ab6e3 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Thu, 4 Dec 2025 12:04:52 +0100 Subject: [PATCH 05/11] update folder structure --- .github/file-filters.yml | 2 +- .github/workflows/test.yml | 8 ++++---- .../swift-log}/.gitignore | 0 .../swift-log}/Package.swift | 0 .../swift-log}/README.md | 0 .../swift-log}/Sources/SentryLogHandler.swift | 0 .../swift-log}/Tests/SentryLogHandlerTests.swift | 0 7 files changed, 5 insertions(+), 5 deletions(-) rename {integrations/logs/sentry-cocoa-swiftlog => 3rd-party-integrations/swift-log}/.gitignore (100%) rename {integrations/logs/sentry-cocoa-swiftlog => 3rd-party-integrations/swift-log}/Package.swift (100%) rename {integrations/logs/sentry-cocoa-swiftlog => 3rd-party-integrations/swift-log}/README.md (100%) rename {integrations/logs/sentry-cocoa-swiftlog => 3rd-party-integrations/swift-log}/Sources/SentryLogHandler.swift (100%) rename {integrations/logs/sentry-cocoa-swiftlog => 3rd-party-integrations/swift-log}/Tests/SentryLogHandlerTests.swift (100%) diff --git a/.github/file-filters.yml b/.github/file-filters.yml index efa4a32b2d..869093154d 100644 --- a/.github/file-filters.yml +++ b/.github/file-filters.yml @@ -25,7 +25,7 @@ run_unit_tests_for_prs: &run_unit_tests_for_prs - "SentryTestUtils/**" - "SentryTestUtilsDynamic/**" - "SentryTestUtilsTests/**" - - "integrations/**" + - "3rd-party-integrations/**" # GH Actions - ".github/workflows/test.yml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 151e66a16c..b33c37fc6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -381,11 +381,11 @@ jobs: run: ./scripts/ci-select-xcode.sh 16.4 - name: Setup local sentry-cocoa dependency - working-directory: integrations/logs/sentry-cocoa-swiftlog - run: swift package edit sentry-cocoa --path ../../.. + working-directory: 3rd-party-integrations/SwiftLog + run: swift package edit sentry-cocoa --path ../.. - name: Run SwiftLog tests - working-directory: integrations/logs/sentry-cocoa-swiftlog + working-directory: 3rd-party-integrations/SwiftLog run: swift test - name: Archiving Raw Logs @@ -394,7 +394,7 @@ jobs: with: name: raw-output-swiftlog-integration path: | - integrations/logs/sentry-cocoa-swiftlog/.build/**/*.log + 3rd-party-integrations/SwiftLog/.build/**/*.log - name: Run CI Diagnostics if: failure() diff --git a/integrations/logs/sentry-cocoa-swiftlog/.gitignore b/3rd-party-integrations/swift-log/.gitignore similarity index 100% rename from integrations/logs/sentry-cocoa-swiftlog/.gitignore rename to 3rd-party-integrations/swift-log/.gitignore diff --git a/integrations/logs/sentry-cocoa-swiftlog/Package.swift b/3rd-party-integrations/swift-log/Package.swift similarity index 100% rename from integrations/logs/sentry-cocoa-swiftlog/Package.swift rename to 3rd-party-integrations/swift-log/Package.swift diff --git a/integrations/logs/sentry-cocoa-swiftlog/README.md b/3rd-party-integrations/swift-log/README.md similarity index 100% rename from integrations/logs/sentry-cocoa-swiftlog/README.md rename to 3rd-party-integrations/swift-log/README.md diff --git a/integrations/logs/sentry-cocoa-swiftlog/Sources/SentryLogHandler.swift b/3rd-party-integrations/swift-log/Sources/SentryLogHandler.swift similarity index 100% rename from integrations/logs/sentry-cocoa-swiftlog/Sources/SentryLogHandler.swift rename to 3rd-party-integrations/swift-log/Sources/SentryLogHandler.swift diff --git a/integrations/logs/sentry-cocoa-swiftlog/Tests/SentryLogHandlerTests.swift b/3rd-party-integrations/swift-log/Tests/SentryLogHandlerTests.swift similarity index 100% rename from integrations/logs/sentry-cocoa-swiftlog/Tests/SentryLogHandlerTests.swift rename to 3rd-party-integrations/swift-log/Tests/SentryLogHandlerTests.swift From 2077ce0384f31d5a5973877a9277b72b95c136d7 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Thu, 4 Dec 2025 12:07:31 +0100 Subject: [PATCH 06/11] update name --- 3rd-party-integrations/{swift-log => SwiftLog}/.gitignore | 0 3rd-party-integrations/{swift-log => SwiftLog}/Package.swift | 0 3rd-party-integrations/{swift-log => SwiftLog}/README.md | 0 .../{swift-log => SwiftLog}/Sources/SentryLogHandler.swift | 0 .../{swift-log => SwiftLog}/Tests/SentryLogHandlerTests.swift | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename 3rd-party-integrations/{swift-log => SwiftLog}/.gitignore (100%) rename 3rd-party-integrations/{swift-log => SwiftLog}/Package.swift (100%) rename 3rd-party-integrations/{swift-log => SwiftLog}/README.md (100%) rename 3rd-party-integrations/{swift-log => SwiftLog}/Sources/SentryLogHandler.swift (100%) rename 3rd-party-integrations/{swift-log => SwiftLog}/Tests/SentryLogHandlerTests.swift (100%) diff --git a/3rd-party-integrations/swift-log/.gitignore b/3rd-party-integrations/SwiftLog/.gitignore similarity index 100% rename from 3rd-party-integrations/swift-log/.gitignore rename to 3rd-party-integrations/SwiftLog/.gitignore diff --git a/3rd-party-integrations/swift-log/Package.swift b/3rd-party-integrations/SwiftLog/Package.swift similarity index 100% rename from 3rd-party-integrations/swift-log/Package.swift rename to 3rd-party-integrations/SwiftLog/Package.swift diff --git a/3rd-party-integrations/swift-log/README.md b/3rd-party-integrations/SwiftLog/README.md similarity index 100% rename from 3rd-party-integrations/swift-log/README.md rename to 3rd-party-integrations/SwiftLog/README.md diff --git a/3rd-party-integrations/swift-log/Sources/SentryLogHandler.swift b/3rd-party-integrations/SwiftLog/Sources/SentryLogHandler.swift similarity index 100% rename from 3rd-party-integrations/swift-log/Sources/SentryLogHandler.swift rename to 3rd-party-integrations/SwiftLog/Sources/SentryLogHandler.swift diff --git a/3rd-party-integrations/swift-log/Tests/SentryLogHandlerTests.swift b/3rd-party-integrations/SwiftLog/Tests/SentryLogHandlerTests.swift similarity index 100% rename from 3rd-party-integrations/swift-log/Tests/SentryLogHandlerTests.swift rename to 3rd-party-integrations/SwiftLog/Tests/SentryLogHandlerTests.swift From 20dd7d95618a866a61c9013754afed863f28a329 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 9 Dec 2025 14:43:29 +0100 Subject: [PATCH 07/11] ranem folder --- .github/workflows/test.yml | 8 ++++---- .../{SwiftLog => SentrySwiftLog}/.gitignore | 0 .../{SwiftLog => SentrySwiftLog}/Package.swift | 0 .../{SwiftLog => SentrySwiftLog}/README.md | 0 .../Sources/SentryLogHandler.swift | 0 .../Tests/SentryLogHandlerTests.swift | 0 6 files changed, 4 insertions(+), 4 deletions(-) rename 3rd-party-integrations/{SwiftLog => SentrySwiftLog}/.gitignore (100%) rename 3rd-party-integrations/{SwiftLog => SentrySwiftLog}/Package.swift (100%) rename 3rd-party-integrations/{SwiftLog => SentrySwiftLog}/README.md (100%) rename 3rd-party-integrations/{SwiftLog => SentrySwiftLog}/Sources/SentryLogHandler.swift (100%) rename 3rd-party-integrations/{SwiftLog => SentrySwiftLog}/Tests/SentryLogHandlerTests.swift (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b33c37fc6a..084dc6f97b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -370,7 +370,7 @@ jobs: scheme: "Sentry" swiftlog-integration-unit-tests: - name: Integrations/Logs/SentrySwiftLog Unit Tests + name: SentrySwiftLog Unit Tests if: needs.files-changed.outputs.run_unit_tests_for_prs == 'true' needs: files-changed runs-on: macos-15 @@ -381,11 +381,11 @@ jobs: run: ./scripts/ci-select-xcode.sh 16.4 - name: Setup local sentry-cocoa dependency - working-directory: 3rd-party-integrations/SwiftLog + working-directory: 3rd-party-integrations/SentrySwiftLog run: swift package edit sentry-cocoa --path ../.. - name: Run SwiftLog tests - working-directory: 3rd-party-integrations/SwiftLog + working-directory: 3rd-party-integrations/SentrySwiftLog run: swift test - name: Archiving Raw Logs @@ -394,7 +394,7 @@ jobs: with: name: raw-output-swiftlog-integration path: | - 3rd-party-integrations/SwiftLog/.build/**/*.log + 3rd-party-integrations/SentrySwiftLog/.build/**/*.log - name: Run CI Diagnostics if: failure() diff --git a/3rd-party-integrations/SwiftLog/.gitignore b/3rd-party-integrations/SentrySwiftLog/.gitignore similarity index 100% rename from 3rd-party-integrations/SwiftLog/.gitignore rename to 3rd-party-integrations/SentrySwiftLog/.gitignore diff --git a/3rd-party-integrations/SwiftLog/Package.swift b/3rd-party-integrations/SentrySwiftLog/Package.swift similarity index 100% rename from 3rd-party-integrations/SwiftLog/Package.swift rename to 3rd-party-integrations/SentrySwiftLog/Package.swift diff --git a/3rd-party-integrations/SwiftLog/README.md b/3rd-party-integrations/SentrySwiftLog/README.md similarity index 100% rename from 3rd-party-integrations/SwiftLog/README.md rename to 3rd-party-integrations/SentrySwiftLog/README.md diff --git a/3rd-party-integrations/SwiftLog/Sources/SentryLogHandler.swift b/3rd-party-integrations/SentrySwiftLog/Sources/SentryLogHandler.swift similarity index 100% rename from 3rd-party-integrations/SwiftLog/Sources/SentryLogHandler.swift rename to 3rd-party-integrations/SentrySwiftLog/Sources/SentryLogHandler.swift diff --git a/3rd-party-integrations/SwiftLog/Tests/SentryLogHandlerTests.swift b/3rd-party-integrations/SentrySwiftLog/Tests/SentryLogHandlerTests.swift similarity index 100% rename from 3rd-party-integrations/SwiftLog/Tests/SentryLogHandlerTests.swift rename to 3rd-party-integrations/SentrySwiftLog/Tests/SentryLogHandlerTests.swift From ba00c983d770578cf4b83ab78931e8095ba7cafb Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 9 Dec 2025 14:49:39 +0100 Subject: [PATCH 08/11] uypdate .gitignore --- .../SentrySwiftLog/.gitignore | 104 +++++++++++++++++- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/3rd-party-integrations/SentrySwiftLog/.gitignore b/3rd-party-integrations/SentrySwiftLog/.gitignore index 0023a53406..cffaa2c670 100644 --- a/3rd-party-integrations/SentrySwiftLog/.gitignore +++ b/3rd-party-integrations/SentrySwiftLog/.gitignore @@ -1,8 +1,100 @@ +# --- macOS --- + +# General .DS_Store -/.build -/Packages +__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/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc + +## 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 From 8e76c5dc50ad5c5385c5e217b8761e15300b1e0f Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 9 Dec 2025 14:53:31 +0100 Subject: [PATCH 09/11] add a note to readme --- 3rd-party-integrations/SentrySwiftLog/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/3rd-party-integrations/SentrySwiftLog/README.md b/3rd-party-integrations/SentrySwiftLog/README.md index 14bb4f0cdd..aee2e8b852 100644 --- a/3rd-party-integrations/SentrySwiftLog/README.md +++ b/3rd-party-integrations/SentrySwiftLog/README.md @@ -2,6 +2,9 @@ A `swift-log` handler that forwards log entries to Sentry's structured logging system, automatically capturing application logs with full context including metadata, source location, 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/SentrySwiftLog/`. This allows users to import only what they need via SPM while keeping all integration code in the main repository. + ## Installation ### Swift Package Manager From aa7809584cf73375c5ca6f27e2af9f8c734b6d4e Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Wed, 10 Dec 2025 11:37:08 +0100 Subject: [PATCH 10/11] update readme --- .../SentrySwiftLog/README.md | 115 +++--------------- 1 file changed, 19 insertions(+), 96 deletions(-) diff --git a/3rd-party-integrations/SentrySwiftLog/README.md b/3rd-party-integrations/SentrySwiftLog/README.md index aee2e8b852..6e46fe88e9 100644 --- a/3rd-party-integrations/SentrySwiftLog/README.md +++ b/3rd-party-integrations/SentrySwiftLog/README.md @@ -1,6 +1,6 @@ -# Sentry Swift-Log Integration +# Sentry SwiftLog Integration -A `swift-log` handler that forwards log entries to Sentry's structured logging system, automatically capturing application logs with full context including metadata, source location, and log levels. +A [SwiftLog](https://github.com/apple/swift-log) handler that forwards log entries to Sentry's structured logging system, automatically capturing application logs with full context including metadata, source location, 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/SentrySwiftLog/`. This allows users to import only what they need via SPM while keeping all integration code in the main repository. @@ -9,104 +9,66 @@ A `swift-log` handler that forwards log entries to Sentry's structured logging s ### Swift Package Manager -Add the following dependencies to your `Package.swift`: +Add the following dependencies to your `Package.swift` or Xcode package dependencies: ```swift dependencies: [ - .package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.0.0"), - .package(url: "https://github.com/getsentry/sentry-cocoa-swiftlog", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-log", from: "1.5.0") + .package(url: "https://github.com/getsentry/sentry-cocoa-swiftlog", from: "1.0.0") ] ``` -Then add the required products to your target dependencies: - -```swift -.target( - name: "YourTarget", - dependencies: [ - .product(name: "Sentry", package: "sentry-cocoa"), // Required for SentrySDK initialization - .product(name: "SentrySwiftLog", package: "sentry-cocoa-swiftlog"), - .product(name: "Logging", package: "swift-log") // Required for Logger and LoggingSystem - ] -) -``` - ## Quick Start -### 1. Initialize Sentry SDK - -First, initialize the Sentry SDK with logs enabled: - ```swift import Sentry +import Logging SentrySDK.start { options in options.dsn = "YOUR_DSN" - options.enableLogs = true // Required for logging integration + options.enableLogs = true } -``` -### 2. Register SentryLogHandler - -Bootstrap the logging system with `SentryLogHandler`: - -```swift -import Logging - -LoggingSystem.bootstrap { _ in - return SentryLogHandler(logLevel: .info) -} -``` - -### 3. Use the Logger +var handler = SentryLogHandler(logLevel: .info) +handler.metadata["app_version"] = "1.0.0" +handler.metadata["environment"] = "production" -Create and use loggers throughout your application: +LoggingSystem.bootstrap { _ in handler } -```swift let logger = Logger(label: "com.example.app") logger.info("User logged in", metadata: ["userId": "12345"]) logger.error("Payment failed", metadata: ["errorCode": 500]) logger.warning("API rate limit approaching", metadata: ["remaining": 10]) + +logger.info("User action", metadata: [ + "userId": "12345", + "action": "purchase" +]) ``` ## Configuration ### Log Level Threshold -Set the minimum log level for messages to be sent to Sentry: +Set the minimum log level for messages to be sent to Sentry. Messages below the configured threshold will be filtered out and not sent to Sentry. ```swift -// Only send info level and above -let handler = SentryLogHandler(logLevel: .info) - -// Send all logs including trace and debug let handler = SentryLogHandler(logLevel: .trace) ``` -Messages below the configured threshold will be filtered out and not sent to Sentry. - ### Handler Metadata -Add metadata that will be included with all log entries: +Add metadata that will be included with all log entries. Handler metadata is merged with call-site metadata, with call-site metadata taking precedence. ```swift var handler = SentryLogHandler(logLevel: .info) handler.metadata["app_version"] = "1.0.0" handler.metadata["environment"] = "production" - -LoggingSystem.bootstrap { _ in handler } ``` -Handler metadata is merged with call-site metadata, with call-site metadata taking precedence. - -### Metadata Subscript Access - You can also access metadata using subscript syntax: ```swift -var handler = SentryLogHandler(logLevel: .info) handler[metadataKey: "app_version"] = "1.0.0" let version = handler[metadataKey: "app_version"] ``` @@ -127,47 +89,7 @@ let version = handler[metadataKey: "app_version"] ## Metadata Handling -The handler supports all `swift-log` metadata types: - -### String Metadata - -```swift -logger.info("User action", metadata: [ - "userId": "12345", - "action": "purchase" -]) -``` - -### Dictionary Metadata - -```swift -logger.info("User profile", metadata: [ - "user": [ - "id": "12345", - "name": "John Doe" - ] -]) -``` - -### Array Metadata - -```swift -logger.info("Tags", metadata: [ - "tags": ["production", "api", "v2"] -]) -``` - -### String Convertible Metadata - -```swift -logger.info("Metrics", metadata: [ - "count": 42, - "enabled": true, - "score": 3.14159 -]) -``` - -All metadata is automatically prefixed with `swift-log.` in Sentry attributes (e.g., `swift-log.userId`, `swift-log.user.id`). +The handler supports all `swift-log` metadata types including strings, dictionaries, arrays, and string convertible types (numbers, booleans, etc.). All metadata is automatically prefixed with `swift-log.` in Sentry attributes (e.g., `swift-log.userId`, `swift-log.user.id`). See the Quick Start section above for examples of each metadata type. ## Automatic Attributes @@ -184,6 +106,7 @@ The handler automatically includes the following attributes with every log entry - [Sentry Cocoa SDK Documentation](https://docs.sentry.io/platforms/apple/) - [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/) +- [SwiftLog Repo](https://github.com/apple/swift-log) ## License From 6106fab5f922fb59ea6b60b3308626a8e95ed5f6 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Wed, 10 Dec 2025 16:54:57 +0100 Subject: [PATCH 11/11] add comment --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b6745906c..e6a967d269 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -369,6 +369,7 @@ jobs: device: "Apple TV" scheme: "Sentry" + # This will be replaced once #6945 is merged. swiftlog-integration-unit-tests: name: SentrySwiftLog Unit Tests if: needs.files-changed.outputs.run_unit_tests_for_prs == 'true'