Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b491448
Add beforeSendLog with mutable log class
denrase Jul 22, 2025
4799ae8
add pr to changelog
denrase Jul 22, 2025
d0afa5a
remove xcode created folder recovered references
denrase Jul 22, 2025
0c6509a
Merge branch 'main' into feat/structured-logs-before-send
denrase Jul 22, 2025
c440e7f
make public api
denrase Jul 22, 2025
181cfb8
Merge branch 'main' into feat/structured-logs-before-send
denrase Jul 28, 2025
780341e
add docs
denrase Jul 28, 2025
f904584
Merge branch 'main' into feat/structured-logs-before-send
denrase Jul 29, 2025
58fe518
Merge branch 'main' into feat/structured-logs-before-send
denrase Jul 29, 2025
399fa55
Change `SentryLog` to NSObject, make public and remove the wrapper cl…
denrase Jul 29, 2025
1661c2f
make public api
denrase Jul 29, 2025
c9c5c71
don’t force unwrap value
denrase Jul 29, 2025
a5a9d98
Merge branch 'main' into feat/structured-logs-before-send
denrase Jul 30, 2025
08beca0
fix cl
denrase Jul 30, 2025
579aa99
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 4, 2025
43001c0
update changelog
denrase Aug 4, 2025
b034d88
fix tests, generate public api
denrase Aug 4, 2025
777d8bb
Fix SMP issue with dynamic fallback for SPM builds
denrase Aug 4, 2025
f6e9001
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 4, 2025
2844982
remove open
denrase Aug 4, 2025
4e22c52
make typealias public
denrase Aug 4, 2025
4aa1fa9
add docs for logs
denrase Aug 4, 2025
736ff34
recreate api
denrase Aug 4, 2025
0c82fca
male log final and codabple package private
denrase Aug 5, 2025
3beece2
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 5, 2025
ae88b04
bump diffMax
denrase Aug 6, 2025
c1db59e
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 6, 2025
354250e
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 7, 2025
6321376
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 7, 2025
e05bbc5
revert diffmax
denrase Aug 7, 2025
0a6a7b7
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 11, 2025
b3944d8
fix cl
denrase Aug 11, 2025
038ab02
fix swift dynamic call extension
denrase Aug 11, 2025
5a0f736
Merge branch 'main' into feat/structured-logs-before-send
denrase Aug 11, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Add experimental support for capturing structured logs via `SentrySDK.logger` (#5532, #5593, #5639, #5628, #5637, #5678)
- Add experimental support for capturing structured logs via `SentrySDK.logger` (#5532, #5593, #5639, #5628, #5637)
- The SDK will show a warning in the console if it detects it was loaded twice (#5298)

Expand Down
19 changes: 16 additions & 3 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -791,9 +791,12 @@
92235CAC2E15369900865983 /* SentryLogBatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAB2E15369900865983 /* SentryLogBatcher.swift */; };
92235CAE2E15549C00865983 /* SentryLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAD2E15549C00865983 /* SentryLogger.swift */; };
92235CB02E155B2600865983 /* SentryLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAF2E155B2600865983 /* SentryLoggerTests.swift */; };
9226E8092E2F8626003B4BEB /* MutableSentryLogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9226E8082E2F8620003B4BEB /* MutableSentryLogLevel.swift */; };
925824C22CB5897700C9B20B /* SentrySessionReplayIntegration-Hybrid.h in Headers */ = {isa = PBXBuildFile; fileRef = D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */; settings = {ATTRIBUTES = (Private, ); }; };
925C11172E2F81B30032D35A /* MutableSentryLogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 925C11162E2F81AD0032D35A /* MutableSentryLogLevel.swift */; };
925C11192E2F81F90032D35A /* MutableSentryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 925C11182E2F81F50032D35A /* MutableSentryLog.swift */; };
925C111B2E2F85110032D35A /* MutableSentryLogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 925C111A2E2F85110032D35A /* MutableSentryLogTests.swift */; };
92672BB629C9A2A9006B021C /* SentryBreadcrumb+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
926C89732E26A30C006F3154 /* SentryScope+PrivateSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 926C89722E26A30C006F3154 /* SentryScope+PrivateSwift.h */; };
927A5CC42DD7626B00B82404 /* SentryEnvelopeItemHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927A5CC32DD7626400B82404 /* SentryEnvelopeItemHeaderTests.swift */; };
928207C42E251B8F009285A4 /* SentryScope+PrivateSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 928207C32E251B8F009285A4 /* SentryScope+PrivateSwift.h */; };
9286059529A5096600F96038 /* SentryGeo.h in Headers */ = {isa = PBXBuildFile; fileRef = 9286059429A5096600F96038 /* SentryGeo.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -2108,8 +2111,11 @@
92235CAB2E15369900865983 /* SentryLogBatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogBatcher.swift; sourceTree = "<group>"; };
92235CAD2E15549C00865983 /* SentryLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogger.swift; sourceTree = "<group>"; };
92235CAF2E155B2600865983 /* SentryLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLoggerTests.swift; sourceTree = "<group>"; };
9226E8082E2F8620003B4BEB /* MutableSentryLogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableSentryLogLevel.swift; sourceTree = "<group>"; };
925C11162E2F81AD0032D35A /* MutableSentryLogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableSentryLogLevel.swift; sourceTree = "<group>"; };
925C11182E2F81F50032D35A /* MutableSentryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableSentryLog.swift; sourceTree = "<group>"; };
925C111A2E2F85110032D35A /* MutableSentryLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableSentryLogTests.swift; sourceTree = "<group>"; };
92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryBreadcrumb+Private.h"; path = "include/HybridPublic/SentryBreadcrumb+Private.h"; sourceTree = "<group>"; };
926C89722E26A30C006F3154 /* SentryScope+PrivateSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryScope+PrivateSwift.h"; path = "include/SentryScope+PrivateSwift.h"; sourceTree = "<group>"; };
927A5CC32DD7626400B82404 /* SentryEnvelopeItemHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelopeItemHeaderTests.swift; sourceTree = "<group>"; };
928207C32E251B8F009285A4 /* SentryScope+PrivateSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryScope+PrivateSwift.h"; path = "include/SentryScope+PrivateSwift.h"; sourceTree = "<group>"; };
9286059429A5096600F96038 /* SentryGeo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryGeo.h; path = Public/SentryGeo.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3373,6 +3379,8 @@
92B6BDAC2E05B9F700D538B3 /* SentryLogTests.swift */,
92ECD7472E05B5760063EC10 /* SentryLogAttributeTests.swift */,
92B6BDA82E05B8F000D538B3 /* SentryLogLevelTests.swift */,
925C111A2E2F85110032D35A /* MutableSentryLogTests.swift */,
9226E8082E2F8620003B4BEB /* MutableSentryLogLevel.swift */,
D46712612DCD059500D4074A /* SentryRedactDefaultOptionsTests.swift */,
620078762D3906AD0022CB67 /* Codable */,
7BC6EBF7255C05060059822A /* TestData.swift */,
Expand Down Expand Up @@ -4675,6 +4683,8 @@
D8F016B12B9622B7007B9AFB /* Protocol */ = {
isa = PBXGroup;
children = (
925C11182E2F81F50032D35A /* MutableSentryLog.swift */,
925C11162E2F81AD0032D35A /* MutableSentryLogLevel.swift */,
620078752D38F1110022CB67 /* Codable */,
92ECD73F2E05AD500063EC10 /* SentryLogAttribute.swift */,
92ECD73D2E05AD2B0063EC10 /* SentryLogLevel.swift */,
Expand Down Expand Up @@ -4919,7 +4929,6 @@
63FE713F20DA4C1100CDBAE8 /* SentryCrashStackCursor_SelfThread.h in Headers */,
639FCFA41EBC809A00778193 /* SentryStacktrace.h in Headers */,
620379DB2AFE1415005AC0C1 /* SentryBuildAppStartSpans.h in Headers */,
926C89732E26A30C006F3154 /* SentryScope+PrivateSwift.h in Headers */,
63FE716320DA4C1100CDBAE8 /* SentryCrashDynamicLinker.h in Headers */,
D4AF00232D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h in Headers */,
639FCF981EBC7B9700778193 /* SentryEvent.h in Headers */,
Expand Down Expand Up @@ -5583,6 +5592,7 @@
62212B872D520CB00062C2FA /* SentryEventCodable.swift in Sources */,
7B3B473825D6CC7E00D01640 /* SentryNSError.m in Sources */,
621AE74D2C626C510012E730 /* SentryANRTrackerV2.m in Sources */,
925C11172E2F81B30032D35A /* MutableSentryLogLevel.swift in Sources */,
84CFA4CA2C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift in Sources */,
FAEC270E2DF3526000878871 /* SentryUserFeedback.swift in Sources */,
F49D419E2DEA3D0600D9244E /* SentryCrashExceptionApplicationHelper.m in Sources */,
Expand Down Expand Up @@ -5851,6 +5861,7 @@
63FE713120DA4C1100CDBAE8 /* SentryCrashDynamicLinker.c in Sources */,
8E25C95325F836D000DC215B /* SentryRandom.m in Sources */,
03F84D3527DD4191008FE43F /* SentryThreadHandle.cpp in Sources */,
925C11192E2F81F90032D35A /* MutableSentryLog.swift in Sources */,
0A2D8DA9289BC905008720F6 /* SentryViewHierarchyProvider.m in Sources */,
D84D2CDD2C2BF7370011AF8A /* SentryReplayEvent.swift in Sources */,
D8BC28CC2BFF78220054DA4D /* SentryRRWebTouchEvent.swift in Sources */,
Expand Down Expand Up @@ -6031,6 +6042,7 @@
7BD337E424A356180050DB6E /* SentryCrashIntegrationTests.swift in Sources */,
62C3168B2B1F865A000D7031 /* SentryTimeSwiftTests.swift in Sources */,
D480F9D92DE47A50009A0594 /* TestSentryScopePersistentStore.swift in Sources */,
925C111B2E2F85110032D35A /* MutableSentryLogTests.swift in Sources */,
7BD4E8E827FD95900086C410 /* SentryMigrateSessionInitTests.m in Sources */,
7B6CC50224EE5A42001816D7 /* SentryHubTests.swift in Sources */,
7BF9EF882722D13000B5BBEF /* SentryTestObjCRuntimeWrapper.m in Sources */,
Expand Down Expand Up @@ -6197,6 +6209,7 @@
D884A20527C80F6300074664 /* SentryCoreDataTrackerTest.swift in Sources */,
8E70B10125CB8695002B3155 /* SentrySpanIdTests.swift in Sources */,
62E2119A2DAE99FC007D7262 /* SentryAsyncSafeLog.m in Sources */,
9226E8092E2F8626003B4BEB /* MutableSentryLogLevel.swift in Sources */,
84EB21962BF01CEA00EDDA28 /* SentryCrashInstallationTests.swift in Sources */,
7BFE7A0A27A1B6B000D2B66E /* SentryWatchdogTerminationTrackingIntegrationTests.swift in Sources */,
D8292D7D2A39A027009872F7 /* UrlSanitizedTests.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions Sources/Sentry/Public/SentryDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
@class SentryEvent;
@class SentrySamplingContext;
@class SentryUserFeedbackConfiguration;
@class MutableSentryLog;
@protocol SentrySpan;

/**
Expand Down Expand Up @@ -107,6 +108,12 @@ typedef SentryEvent *_Nullable (^SentryBeforeSendEventCallback)(SentryEvent *_No
*/
typedef id<SentrySpan> _Nullable (^SentryBeforeSendSpanCallback)(id<SentrySpan> _Nonnull span);

/**
* Use this block to drop or modify a log before the SDK sends it to Sentry. Return @c nil to drop
* the log.
*/
typedef MutableSentryLog *_Nullable (^SentryBeforeSendLogCallback)(MutableSentryLog *_Nonnull log);

/**
* Block can be used to decide if the SDK should capture a screenshot or not. Return @c true if the
* SDK should capture a screenshot, return @c false if not. This callback doesn't work for crashes.
Expand Down
6 changes: 6 additions & 0 deletions Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ NS_SWIFT_NAME(Options)
*/
@property (nullable, nonatomic, copy) SentryBeforeSendSpanCallback beforeSendSpan NS_SWIFT_SENDABLE;

/**
* Use this callback to drop or modify a log before the SDK sends it to Sentry. Return @c nil to
* drop the log.
*/
@property (nullable, nonatomic, copy) SentryBeforeSendLogCallback beforeSendLog NS_SWIFT_SENDABLE;

/**
* This block can be used to modify the event before it will be serialized and sent.
*/
Expand Down
44 changes: 44 additions & 0 deletions Sources/Swift/Protocol/MutableSentryLog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* A mutable wrapper for use in `beforeSendLog` callbacks.
*
* The internal log structure is immutable for thread safety and data integrity in the SDK pipeline.
* `MutableSentryLog` provides a mutable interface for user modifications in callbacks.
*/
@objcMembers
public class MutableSentryLog: NSObject {

public var timestamp: Date
public var traceId: SentryId
public var level: MutableSentryLogLevel {
didSet {
// Automatically update severity number when level changes
self.severityNumber = NSNumber(value: level.toSeverityNumber())
}
}
public var body: String
public var attributes: [String: Any]
public var severityNumber: NSNumber?

init(log: SentryLog) {
self.timestamp = log.timestamp
self.traceId = log.traceId
self.level = MutableSentryLogLevel.from(log.level)
self.body = log.body
self.attributes = log.attributes.mapValues { $0.value }
self.severityNumber = log.severityNumber.map { NSNumber(value: $0) }
super.init()
}

func toSentryLog() -> SentryLog {
let sentryAttributes = attributes.mapValues { SentryLog.Attribute(value: $0) }

return SentryLog(
timestamp: timestamp,
traceId: traceId,
level: level.toLevel(),
body: body,
attributes: sentryAttributes,
severityNumber: severityNumber?.intValue
)
}
}
50 changes: 50 additions & 0 deletions Sources/Swift/Protocol/MutableSentryLogLevel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@_implementationOnly import _SentryPrivate
import Foundation

@objc
public enum MutableSentryLogLevel: Int {
case trace
case debug
case info
case warn
case error
case fatal

// Convert from Swift SentryLog.Level
internal static func from(_ level: SentryLog.Level) -> MutableSentryLogLevel {
switch level {
case .trace: return .trace
case .debug: return .debug
case .info: return .info
case .warn: return .warn
case .error: return .error
case .fatal: return .fatal
}
}

// Convert to Swift SentryLog.Level
internal func toLevel() -> SentryLog.Level {
switch self {
case .trace: return .trace
case .debug: return .debug
case .info: return .info
case .warn: return .warn
case .error: return .error
case .fatal: return .fatal
@unknown default: return .error
}
}

// Docs: https://develop.sentry.dev/sdk/telemetry/logs/#log-severity-number
internal func toSeverityNumber() -> Int {
switch self {
case .trace: return 1
case .debug: return 5
case .info: return 9
case .warn: return 13
case .error: return 17
case .fatal: return 21
@unknown default: return 17 // Default to error level
}
}
}
24 changes: 16 additions & 8 deletions Sources/Swift/Tools/SentryLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,23 @@ public final class SentryLogger: NSObject {
let propagationContextTraceIdString = hub.scope.propagationContextTraceIdString
let propagationContextTraceId = SentryId(uuidString: propagationContextTraceIdString)

batcher.add(
SentryLog(
timestamp: dateProvider.date(),
traceId: propagationContextTraceId,
level: level,
body: body,
attributes: logAttributes
)
let log = SentryLog(
timestamp: dateProvider.date(),
traceId: propagationContextTraceId,
level: level,
body: body,
attributes: logAttributes
)

var processedLog: SentryLog? = log
if let beforeSendLog = batcher.options.beforeSendLog {
let mutableSentryLog = MutableSentryLog(log: log)
processedLog = beforeSendLog(mutableSentryLog)?.toSentryLog()
}

if let processedLog {
batcher.add(processedLog)
}
}

private func addDefaultAttributes(to attributes: inout [String: SentryLog.Attribute]) {
Expand Down
42 changes: 42 additions & 0 deletions Tests/SentryTests/Protocol/MutableSentryLogLevel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@_spi(Private) @testable import Sentry
@_spi(Private) import SentryTestUtils
import XCTest

final class MutableSentryLogLevelTests: XCTestCase {

// MARK: - MutableSentryLogLevel Tests

func testMutableSentryLogLevel_FromLevel_AllCases() {
let testCases: [(SentryLog.Level, MutableSentryLogLevel)] = [
(.trace, .trace),
(.debug, .debug),
(.info, .info),
(.warn, .warn),
(.error, .error),
(.fatal, .fatal)
]

for (swiftLevel, expectedMutableLevel) in testCases {
let actualMutableLevel = MutableSentryLogLevel.from(swiftLevel)
XCTAssertEqual(actualMutableLevel, expectedMutableLevel,
"Converting \(swiftLevel) should result in \(expectedMutableLevel)")
}
}

func testMutableSentryLogLevel_ToLevel_AllCases() {
let testCases: [(MutableSentryLogLevel, SentryLog.Level)] = [
(.trace, .trace),
(.debug, .debug),
(.info, .info),
(.warn, .warn),
(.error, .error),
(.fatal, .fatal)
]

for (mutableLevel, expectedSwiftLevel) in testCases {
let actualSwiftLevel = mutableLevel.toLevel()
XCTAssertEqual(actualSwiftLevel, expectedSwiftLevel,
"Converting \(mutableLevel) should result in \(expectedSwiftLevel)")
}
}
}
Loading
Loading