Skip to content

Commit 99104c9

Browse files
authored
Structured Logs: Add default attributes (#5593)
1 parent f8029e2 commit 99104c9

File tree

12 files changed

+192
-16
lines changed

12 files changed

+192
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
### Features
66

7-
- Add experimental support for capturing structured logs via `SentrySDK.logger` (#5532)
7+
- Add experimental support for capturing structured logs via `SentrySDK.logger` (#5532, #5593)
88

99
### Improvements
1010

Sentry.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@
795795
925824C22CB5897700C9B20B /* SentrySessionReplayIntegration-Hybrid.h in Headers */ = {isa = PBXBuildFile; fileRef = D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */; settings = {ATTRIBUTES = (Private, ); }; };
796796
92672BB629C9A2A9006B021C /* SentryBreadcrumb+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
797797
927A5CC42DD7626B00B82404 /* SentryEnvelopeItemHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927A5CC32DD7626400B82404 /* SentryEnvelopeItemHeaderTests.swift */; };
798+
928207C42E251B8F009285A4 /* SentryScope+PrivateSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 928207C32E251B8F009285A4 /* SentryScope+PrivateSwift.h */; };
798799
9286059529A5096600F96038 /* SentryGeo.h in Headers */ = {isa = PBXBuildFile; fileRef = 9286059429A5096600F96038 /* SentryGeo.h */; settings = {ATTRIBUTES = (Public, ); }; };
799800
9286059729A5098900F96038 /* SentryGeo.m in Sources */ = {isa = PBXBuildFile; fileRef = 9286059629A5098900F96038 /* SentryGeo.m */; };
800801
9286059929A50BAB00F96038 /* SentryGeoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9286059829A50BAA00F96038 /* SentryGeoTests.swift */; };
@@ -1964,9 +1965,9 @@
19641965
84A789092C0E9F5800FF0803 /* SentrySpan+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySpan+Private.h"; path = "include/SentrySpan+Private.h"; sourceTree = "<group>"; };
19651966
84A8891A28DBD28900C51DFD /* SentryDevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDevice.h; path = include/SentryDevice.h; sourceTree = "<group>"; };
19661967
84A8891B28DBD28900C51DFD /* SentryDevice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDevice.m; sourceTree = "<group>"; };
1968+
84A8892028DBD8D600C51DFD /* SentryDeviceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDeviceTests.m; sourceTree = "<group>"; };
19671969
84A898522E163072009A551E /* SentryProfileConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryProfileConfiguration.h; path = ../include/SentryProfileConfiguration.h; sourceTree = "<group>"; };
19681970
84A898532E163072009A551E /* SentryProfileConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryProfileConfiguration.m; sourceTree = "<group>"; };
1969-
84A8892028DBD8D600C51DFD /* SentryDeviceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDeviceTests.m; sourceTree = "<group>"; };
19701971
84A898CD2E1DBDD1009A551E /* SentryAppStartProfilingConfigurationChangeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryAppStartProfilingConfigurationChangeTests.swift; sourceTree = "<group>"; };
19711972
84A899342E218C5F009A551E /* CLAUDE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CLAUDE.md; sourceTree = "<group>"; };
19721973
84A903702D39F66F00690CE4 /* SentryUserFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedbackFormViewModel.swift; sourceTree = "<group>"; };
@@ -2060,6 +2061,7 @@
20602061
92235CAF2E155B2600865983 /* SentryLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLoggerTests.swift; sourceTree = "<group>"; };
20612062
92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryBreadcrumb+Private.h"; path = "include/HybridPublic/SentryBreadcrumb+Private.h"; sourceTree = "<group>"; };
20622063
927A5CC32DD7626400B82404 /* SentryEnvelopeItemHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelopeItemHeaderTests.swift; sourceTree = "<group>"; };
2064+
928207C32E251B8F009285A4 /* SentryScope+PrivateSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryScope+PrivateSwift.h"; path = "include/SentryScope+PrivateSwift.h"; sourceTree = "<group>"; };
20632065
9286059429A5096600F96038 /* SentryGeo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryGeo.h; path = Public/SentryGeo.h; sourceTree = "<group>"; };
20642066
9286059629A5098900F96038 /* SentryGeo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryGeo.m; sourceTree = "<group>"; };
20652067
9286059829A50BAA00F96038 /* SentryGeoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryGeoTests.swift; sourceTree = "<group>"; };
@@ -2782,6 +2784,7 @@
27822784
7D0637022382B34300B30749 /* SentryScope.h */,
27832785
7D65260B237F649E00113EA2 /* SentryScope.m */,
27842786
632331F7240506DF008D91D6 /* SentryScope+Private.h */,
2787+
928207C32E251B8F009285A4 /* SentryScope+PrivateSwift.h */,
27852788
7BCFBD662681C95000BC27D8 /* SentryScopeObserver.h */,
27862789
15360CEF2433A16D00112302 /* SentryInstallation.h */,
27872790
15360CEC2433A15500112302 /* SentryInstallation.m */,
@@ -4848,6 +4851,7 @@
48484851
63FE710320DA4C1000CDBAE8 /* SentryCrashMachineContext_Apple.h in Headers */,
48494852
844EDC6F294143B900C86F34 /* SentryNSProcessInfoWrapper.h in Headers */,
48504853
D8479328278873A100BE8E99 /* SentryByteCountFormatter.h in Headers */,
4854+
928207C42E251B8F009285A4 /* SentryScope+PrivateSwift.h in Headers */,
48514855
63AA76981EB9C1C200D153DE /* SentryClient.h in Headers */,
48524856
0A9E917128DC7E7000FB4182 /* SentryInternalCDefines.h in Headers */,
48534857
63FE711F20DA4C1000CDBAE8 /* SentryCrashObjC.h in Headers */,

Sources/Sentry/SentryScope.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,11 @@ - (NSDictionary *)buildTraceContext:(nullable id<SentrySpan>)span
624624
}
625625
}
626626

627+
- (NSString *)propagationContextTraceIdString
628+
{
629+
return [self.propagationContext.traceId sentryIdString];
630+
}
631+
627632
@end
628633

629634
NS_ASSUME_NONNULL_END

Sources/Sentry/include/SentryPrivate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#import "SentryNSDictionarySanitize.h"
2525
#import "SentryProfiler+Private.h"
2626
#import "SentryRandom.h"
27+
#import "SentryScope+PrivateSwift.h"
2728
#import "SentrySdkInfo.h"
2829
#import "SentrySerialization.h"
2930
#import "SentrySession.h"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#import "SentryScope.h"
2+
3+
NS_ASSUME_NONNULL_BEGIN
4+
5+
// Added to only expose a limited sub-set of internal API needed in the Swift layer.
6+
@interface SentryScope ()
7+
8+
// This is a workaround to make the traceId available in the Swift layer.
9+
// Can't expose the SentryId directly for some reason.
10+
@property (nonatomic, readonly) NSString *propagationContextTraceIdString;
11+
12+
@end
13+
14+
NS_ASSUME_NONNULL_END

Sources/Swift/Protocol/SentryLog.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
struct SentryLog: Codable {
22
let timestamp: Date
3-
var traceId: SentryId
3+
let traceId: SentryId
44
let level: SentryLog.Level
55
let body: String
66
let attributes: [String: SentryLog.Attribute]
@@ -19,18 +19,18 @@ struct SentryLog: Codable {
1919
/// by the time processing completes, it is guaranteed to be a valid non-empty trace id.
2020
init(
2121
timestamp: Date,
22-
traceId: SentryId? = nil,
22+
traceId: SentryId,
2323
level: SentryLog.Level,
2424
body: String,
2525
attributes: [String: SentryLog.Attribute],
2626
severityNumber: Int? = nil
2727
) {
2828
self.timestamp = timestamp
29-
self.traceId = traceId ?? SentryId.empty
29+
self.traceId = traceId
3030
self.level = level
3131
self.body = body
3232
self.attributes = attributes
33-
self.severityNumber = severityNumber
33+
self.severityNumber = severityNumber ?? level.toSeverityNumber()
3434
}
3535

3636
init(from decoder: any Decoder) throws {

Sources/Swift/Tools/SentryLogBatcher.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import Foundation
77

88
private let client: SentryClient
99

10+
let options: Options
11+
1012
@_spi(Private) public init(client: SentryClient) {
1113
self.client = client
14+
self.options = client.options
1215
super.init()
1316
}
1417

Sources/Swift/Tools/SentryLogger.swift

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@_implementationOnly import _SentryPrivate
2+
13
import Foundation
24

35
/// **EXPERIMENTAL** - A structured logging API for Sentry.
@@ -101,13 +103,36 @@ public final class SentryLogger: NSObject {
101103
guard let batcher else {
102104
return
103105
}
104-
let logAttributes = attributes.mapValues { SentryLog.Attribute(value: $0) }
105-
let log = SentryLog(
106-
timestamp: dateProvider.date(),
107-
level: level,
108-
body: body,
109-
attributes: logAttributes
106+
107+
var logAttributes = attributes.mapValues { SentryLog.Attribute(value: $0) }
108+
addDefaultAttributes(to: &logAttributes)
109+
110+
let propagationContextTraceIdString = hub.scope.propagationContextTraceIdString
111+
let propagationContextTraceId = SentryId(uuidString: propagationContextTraceIdString)
112+
113+
batcher.add(
114+
SentryLog(
115+
timestamp: dateProvider.date(),
116+
traceId: propagationContextTraceId,
117+
level: level,
118+
body: body,
119+
attributes: logAttributes
120+
)
110121
)
111-
batcher.add(log)
122+
}
123+
124+
private func addDefaultAttributes(to attributes: inout [String: SentryLog.Attribute]) {
125+
guard let batcher else {
126+
return
127+
}
128+
attributes["sentry.sdk.name"] = .string(SentryMeta.sdkName)
129+
attributes["sentry.sdk.version"] = .string(SentryMeta.versionString)
130+
attributes["sentry.environment"] = .string(batcher.options.environment)
131+
if let releaseName = batcher.options.releaseName {
132+
attributes["sentry.release"] = .string(releaseName)
133+
}
134+
if let span = hub.scope.span {
135+
attributes["sentry.trace.parent_span_id"] = .string(span.spanId.sentrySpanIdString)
136+
}
112137
}
113138
}

Tests/Perf/metrics-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ startupTimeTest:
1111

1212
binarySizeTest:
1313
diffMin: 200 KiB
14-
diffMax: 870 KiB
14+
diffMax: 875 KiB

Tests/SentryTests/Protocol/SentryLogTests.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,75 @@ final class SentryLogTests: XCTestCase {
3939
}
4040
""".utf8)
4141

42+
// MARK: - Severity Number Fallback Tests
43+
44+
func testConstructorWithExplicitSeverityNumber() throws {
45+
let log = SentryLog(
46+
timestamp: Date(),
47+
traceId: SentryId(),
48+
level: .info,
49+
body: "Test message",
50+
attributes: [:],
51+
severityNumber: 99 // Explicit value different from level's default
52+
)
53+
54+
XCTAssertEqual(log.severityNumber, 99, "Should use explicitly provided severity number")
55+
XCTAssertEqual(log.level, .info)
56+
}
57+
58+
func testConstructorWithoutSeverityNumberFallsBackToLevel() throws {
59+
let log = SentryLog(
60+
timestamp: Date(),
61+
traceId: SentryId(),
62+
level: .info,
63+
body: "Test message",
64+
attributes: [:]
65+
// severityNumber not provided - should default to nil and fallback to level
66+
)
67+
68+
XCTAssertEqual(log.severityNumber, 9, "Should derive severity number from info level")
69+
XCTAssertEqual(log.level, .info)
70+
}
71+
72+
func testConstructorWithNilSeverityNumberFallsBackToLevel() throws {
73+
let log = SentryLog(
74+
timestamp: Date(),
75+
traceId: SentryId(),
76+
level: .error,
77+
body: "Error message",
78+
attributes: [:],
79+
severityNumber: nil // Explicitly nil
80+
)
81+
82+
XCTAssertEqual(log.severityNumber, 17, "Should derive severity number from error level")
83+
XCTAssertEqual(log.level, .error)
84+
}
85+
86+
func testSeverityNumberFallbackForAllLevels() throws {
87+
let testCases: [(SentryLog.Level, Int)] = [
88+
(.trace, 1),
89+
(.debug, 5),
90+
(.info, 9),
91+
(.warn, 13),
92+
(.error, 17),
93+
(.fatal, 21)
94+
]
95+
96+
for (level, expectedSeverity) in testCases {
97+
let log = SentryLog(
98+
timestamp: Date(),
99+
traceId: SentryId(),
100+
level: level,
101+
body: "Test message",
102+
attributes: [:]
103+
// severityNumber not provided
104+
)
105+
106+
XCTAssertEqual(log.severityNumber, expectedSeverity,
107+
"Level \(level) should derive severity number \(expectedSeverity)")
108+
}
109+
}
110+
42111
func testEncode() throws {
43112
let data = try encodeToJSONData(data: log)
44113
let json = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])

0 commit comments

Comments
 (0)