Skip to content

Commit 324157f

Browse files
authored
Structured Logs: Add log APIs to Hub and Client (#6518)
1 parent 3a3d93b commit 324157f

25 files changed

+1381
-938
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
- Move `enableDataSwizzling` from experimental options to top-level options (#6592). This option remains enabled by default.
6565
- Add `sentry.replay_id` attribute to logs ([#6515](https://github.com/getsentry/sentry-cocoa/pull/6515))
6666
- Structured Logs: Add `SentrySwiftLog` Integration (#6286)
67+
- Structured Logs: Add log APIs to `Hub` and `Client` (#6518)
6768

6869
## 8.57.2
6970

Sentry.xcodeproj/project.pbxproj

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,8 @@
725725
9241AC442EBA39A500E611ED /* SentrySwiftLog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9241AC132EBA38CB00E611ED /* SentrySwiftLog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
726726
9241AC4F2EBA3A0C00E611ED /* SentryLogHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9241AC4D2EBA3A0C00E611ED /* SentryLogHandlerTests.swift */; };
727727
925824C22CB5897700C9B20B /* SentrySessionReplayIntegration-Hybrid.h in Headers */ = {isa = PBXBuildFile; fileRef = D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */; settings = {ATTRIBUTES = (Private, ); }; };
728+
92622E092EABB71000ABE7FF /* SentryLogSPMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92622E082EABB71000ABE7FF /* SentryLogSPMTests.swift */; };
729+
92622E142EABBDA900ABE7FF /* SentryLog+SPM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92622E132EABBDA900ABE7FF /* SentryLog+SPM.swift */; };
728730
9264E1EB2E2E385E00B077CF /* SentryLogMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */; };
729731
9264E1ED2E2E397C00B077CF /* SentryLogMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EC2E2E397400B077CF /* SentryLogMessageTests.swift */; };
730732
92672BB629C9A2A9006B021C /* SentryBreadcrumb+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -739,7 +741,6 @@
739741
92D957732E05A44600E20E66 /* SentryAsyncLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 92D957722E05A44600E20E66 /* SentryAsyncLog.m */; };
740742
92D957772E05A4F300E20E66 /* SentryAsyncLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 92D957762E05A4F300E20E66 /* SentryAsyncLog.h */; };
741743
92E5F3D62CDBB3BF00B7AD98 /* SentrySampling.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8C57A525EEFC42001CEEFA /* SentrySampling.h */; };
742-
92EC54CE2E1EB54B00A10AC2 /* SentryClient+Logs.h in Headers */ = {isa = PBXBuildFile; fileRef = 92EC54CD2E1EB54B00A10AC2 /* SentryClient+Logs.h */; };
743744
92ECD7202E05A7DF0063EC10 /* SentryLogC.h in Headers */ = {isa = PBXBuildFile; fileRef = D8AE48B12C5786AA0092A2A6 /* SentryLogC.h */; settings = {ATTRIBUTES = (Private, ); }; };
744745
92ECD73C2E05ACE00063EC10 /* SentryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92ECD73B2E05ACDE0063EC10 /* SentryLog.swift */; };
745746
92ECD73E2E05AD320063EC10 /* SentryLogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92ECD73D2E05AD2B0063EC10 /* SentryLogLevel.swift */; };
@@ -2128,6 +2129,8 @@
21282129
92235CAB2E15369900865983 /* SentryLogBatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogBatcher.swift; sourceTree = "<group>"; };
21292130
92235CAD2E15549C00865983 /* SentryLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogger.swift; sourceTree = "<group>"; };
21302131
92235CAF2E155B2600865983 /* SentryLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLoggerTests.swift; sourceTree = "<group>"; };
2132+
92622E082EABB71000ABE7FF /* SentryLogSPMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogSPMTests.swift; sourceTree = "<group>"; };
2133+
92622E132EABBDA900ABE7FF /* SentryLog+SPM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryLog+SPM.swift"; sourceTree = "<group>"; };
21312134
9241AC132EBA38CB00E611ED /* SentrySwiftLog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SentrySwiftLog.framework; sourceTree = BUILT_PRODUCTS_DIR; };
21322135
9241AC1B2EBA38CC00E611ED /* SentrySwiftLogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SentrySwiftLogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
21332136
9241AC4A2EBA39EE00E611ED /* SentryLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogHandler.swift; sourceTree = "<group>"; };
@@ -2145,7 +2148,6 @@
21452148
92B6BDAC2E05B9F700D538B3 /* SentryLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogTests.swift; sourceTree = "<group>"; };
21462149
92D957722E05A44600E20E66 /* SentryAsyncLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryAsyncLog.m; sourceTree = "<group>"; };
21472150
92D957762E05A4F300E20E66 /* SentryAsyncLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryAsyncLog.h; path = include/SentryAsyncLog.h; sourceTree = "<group>"; };
2148-
92EC54CD2E1EB54B00A10AC2 /* SentryClient+Logs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryClient+Logs.h"; path = "include/SentryClient+Logs.h"; sourceTree = "<group>"; };
21492151
92ECD73B2E05ACDE0063EC10 /* SentryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLog.swift; sourceTree = "<group>"; };
21502152
92ECD73D2E05AD2B0063EC10 /* SentryLogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogLevel.swift; sourceTree = "<group>"; };
21512153
92ECD73F2E05AD500063EC10 /* SentryLogAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogAttribute.swift; sourceTree = "<group>"; };
@@ -2764,6 +2766,7 @@
27642766
84B0E0062CD963F9007FB332 /* SentryIconography.swift */,
27652767
621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */,
27662768
F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */,
2769+
92622E132EABBDA900ABE7FF /* SentryLog+SPM.swift */,
27672770
F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */,
27682771
);
27692772
path = Helper;
@@ -3689,6 +3692,7 @@
36893692
F4A930242E661856006DA6EF /* SentryMobileProvisionParserTests.swift */,
36903693
D4F7BD7C2E4373BB004A2D77 /* SentryLevelMapperTests.swift */,
36913694
D8AE48BE2C578D540092A2A6 /* SentrySDKLog.swift */,
3695+
92622E082EABB71000ABE7FF /* SentryLogSPMTests.swift */,
36923696
849AC3FF29E0C1FF00889C16 /* SentryFormatterTests.swift */,
36933697
7B88F30324BC8E6500ADF90A /* SentrySerializationTests.swift */,
36943698
62F4DDA02C04CB9700588890 /* SentryBaggageSerializationTests.swift */,
@@ -5317,7 +5321,6 @@
53175321
8E7C98312693E1CC00E6336C /* SentryTraceHeader.h in Headers */,
53185322
62C316812B1F2E93000D7031 /* SentryDelayedFramesTracker.h in Headers */,
53195323
92D957772E05A4F300E20E66 /* SentryAsyncLog.h in Headers */,
5320-
92EC54CE2E1EB54B00A10AC2 /* SentryClient+Logs.h in Headers */,
53215324
7B8713AE26415ADF006D6004 /* SentryAppStartTrackingIntegration.h in Headers */,
53225325
7B7D873224864BB900D2ECFF /* SentryCrashMachineContextWrapper.h in Headers */,
53235326
861265F92404EC1500C4AFDE /* SentryArray.h in Headers */,
@@ -6269,6 +6272,7 @@
62696272
7B14089824878F950035403D /* SentryCrashStackEntryMapper.m in Sources */,
62706273
D8BC28C82BFF5EBB0054DA4D /* SentryTouchTracker.swift in Sources */,
62716274
63FE711720DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.c in Sources */,
6275+
92622E142EABBDA900ABE7FF /* SentryLog+SPM.swift in Sources */,
62726276
FA3A42722E1C5F9B00A08C39 /* SentryNSNotificationCenterWrapper.swift in Sources */,
62736277
63FE70CB20DA4C1000CDBAE8 /* SentryCrashReportFixer.c in Sources */,
62746278
F4A930232E65FDBF006DA6EF /* SentryMobileProvisionParser.swift in Sources */,
@@ -6378,6 +6382,7 @@
63786382
7BE3C78724472E9800A38442 /* TestRequestManager.swift in Sources */,
63796383
63FE722220DA66EC00CDBAE8 /* SentryCrashJSONCodec_Tests.m in Sources */,
63806384
7B0A5452252311CE00A71716 /* SentryBreadcrumbTests.swift in Sources */,
6385+
92622E092EABB71000ABE7FF /* SentryLogSPMTests.swift in Sources */,
63816386
7BE3C7752445C82300A38442 /* SentryCurrentDateTests.swift in Sources */,
63826387
7B3398672459C4AE00BD9C96 /* SentryEnvelopeRateLimitTests.swift in Sources */,
63836388
8EA9AF492665AC48002771B4 /* SentryPerformanceTrackerTests.swift in Sources */,

SentryTestUtils/Sources/TestClient.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,10 @@ public class TestClient: SentryClientInternal {
161161
flushInvocations.record(timeout)
162162
}
163163

164-
public var captureLogsDataInvocations = Invocations<(data: Data, count: NSNumber)>()
165-
public override func captureLogsData(_ data: Data, with count: NSNumber) {
166-
captureLogsDataInvocations.record((data, count))
164+
public var captureLogInvocations = Invocations<(log: SentryLog, scope: Scope)>()
165+
public override func _swiftCaptureLog(_ log: NSObject, with scope: Scope) {
166+
if let castLog = log as? SentryLog {
167+
captureLogInvocations.record((castLog, scope))
168+
}
167169
}
168170
}

Sources/Sentry/SentryClient.m

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,14 @@
4545

4646
NS_ASSUME_NONNULL_BEGIN
4747

48-
@interface SentryClientInternal ()
48+
@interface SentryClientInternal () <SentryLogBatcherDelegate>
4949

5050
@property (nonatomic, strong) SentryTransportAdapter *transportAdapter;
5151
@property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider;
5252
@property (nonatomic, strong) id<SentryRandomProtocol> random;
5353
@property (nonatomic, strong) NSLocale *locale;
5454
@property (nonatomic, strong) NSTimeZone *timezone;
55+
@property (nonatomic, strong) SentryLogBatcher *logBatcher;
5556

5657
@end
5758

@@ -114,6 +115,10 @@ - (instancetype)initWithOptions:(SentryOptions *)options
114115
self.locale = locale;
115116
self.timezone = timezone;
116117
self.attachmentProcessors = [[NSMutableArray alloc] init];
118+
self.logBatcher = [[SentryLogBatcher alloc]
119+
initWithOptions:options
120+
dispatchQueue:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper
121+
delegate:self];
117122

118123
// The SDK stores the installationID in a file. The first call requires file IO. To avoid
119124
// executing this on the main thread, we cache the installationID async here.
@@ -619,7 +624,11 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event
619624

620625
- (void)flush:(NSTimeInterval)timeout
621626
{
622-
[self.transportAdapter flush:timeout];
627+
NSTimeInterval captureLogsDuration = [self.logBatcher captureLogs];
628+
// Capturing batched logs should never take long, but we need to fall back to a sane value.
629+
// This is a workaround for in-memory logs, until we'll write batched logs to disk,
630+
// to avoid data loss due to crashes. This is a trade-off until then.
631+
[self.transportAdapter flush:fmax(timeout / 2, timeout - captureLogsDuration)];
623632
}
624633

625634
- (void)close
@@ -1089,7 +1098,14 @@ - (void)removeAttachmentProcessor:(id<SentryClientAttachmentProcessor>)attachmen
10891098
return processedAttachments;
10901099
}
10911100

1092-
- (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount;
1101+
- (void)_swiftCaptureLog:(NSObject *)log withScope:(SentryScope *)scope
1102+
{
1103+
if ([log isKindOfClass:[SentryLog class]]) {
1104+
[self.logBatcher addLog:(SentryLog *)log scope:scope];
1105+
}
1106+
}
1107+
1108+
- (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount
10931109
{
10941110
SentryEnvelopeItemHeader *header =
10951111
[[SentryEnvelopeItemHeader alloc] initWithType:SentryEnvelopeItemTypes.log

Sources/Sentry/SentryHub.m

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
NS_ASSUME_NONNULL_BEGIN
2828

29-
@interface SentryHubInternal ()
29+
@interface SentryHubInternal () <SentryLoggerDelegate>
3030

3131
@property (nullable, atomic, strong) SentryClientInternal *client;
3232
@property (nullable, nonatomic, strong) SentryScope *scope;
@@ -73,6 +73,10 @@ - (instancetype)initWithClient:(nullable SentryClientInternal *)client
7373
if (_scope) {
7474
[_crashWrapper enrichScope:SENTRY_UNWRAP_NULLABLE(SentryScope, _scope)];
7575
}
76+
77+
__swiftLogger = [[SentryLogger alloc]
78+
initWithDelegate:self
79+
dateProvider:SentryDependencyContainer.sharedInstance.dateProvider];
7680
}
7781

7882
return self;
@@ -833,6 +837,35 @@ - (void)unregisterSessionListener:(id<SentrySessionListener>)listener
833837
}
834838
}
835839

840+
// SentryLoggerDelegate
841+
842+
- (void)captureLog:(SentryLog *)log
843+
{
844+
SentryClientInternal *client = self.client;
845+
if (client == nil) {
846+
SENTRY_LOG_WARN(@"No client configured. Dropping log.");
847+
return;
848+
}
849+
#if SENTRY_TARGET_REPLAY_SUPPORTED
850+
NSString *scopeReplayId = self.scope.replayId;
851+
if (scopeReplayId != nil) {
852+
// Session mode: use scope replay ID
853+
[log setAttribute:[[SentryLogAttribute alloc] initWithString:scopeReplayId]
854+
forKey:@"sentry.replay_id"];
855+
} else {
856+
// Buffer mode: check if hub has a session replay ID
857+
NSString *sessionReplayId = [self getSessionReplayId];
858+
if (sessionReplayId != nil) {
859+
[log setAttribute:[[SentryLogAttribute alloc] initWithString:sessionReplayId]
860+
forKey:@"sentry.replay_id"];
861+
[log setAttribute:[[SentryLogAttribute alloc] initWithBoolean:YES]
862+
forKey:@"sentry._internal.replay_is_buffering"];
863+
}
864+
}
865+
#endif
866+
[client _swiftCaptureLog:log withScope:self.scope];
867+
}
868+
836869
#pragma mark - Protected
837870

838871
- (NSArray<NSString *> *)trimmedInstalledIntegrationNames

Sources/Sentry/SentrySDKInternal.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,8 +667,6 @@ + (void)close
667667

668668
[SentrySDKInternal setCurrentHub:nil];
669669

670-
[SentrySDK clearLogger];
671-
672670
[SentryDependencyContainer.sharedInstance.crashWrapper stopBinaryImageCache];
673671
[SentryDependencyContainer.sharedInstance.binaryImageCache stop];
674672

Sources/Sentry/include/SentryClient+Private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ NS_ASSUME_NONNULL_BEGIN
7878
- (void)addAttachmentProcessor:(id<SentryClientAttachmentProcessor>)attachmentProcessor;
7979
- (void)removeAttachmentProcessor:(id<SentryClientAttachmentProcessor>)attachmentProcessor;
8080

81+
- (void)_swiftCaptureLog:(NSObject *)log withScope:(SentryScope *)scope;
82+
8183
@end
8284

8385
NS_ASSUME_NONNULL_END

Sources/Sentry/include/SentryHub+Private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ NS_ASSUME_NONNULL_BEGIN
2929

3030
@property (nonatomic, strong) NSMutableArray<id<SentryIntegrationProtocol>> *installedIntegrations;
3131

32+
@property (nonatomic, readonly, strong) NSObject *_swiftLogger;
33+
3234
/**
3335
* Every integration starts with "Sentry" and ends with "Integration". To keep the payload of the
3436
* event small we remove both.

Sources/Sentry/include/SentryPrivate.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#import "SentryANRTrackerV1.h"
2929
#import "SentryANRTrackerV2.h"
3030
#import "SentryAsyncLog.h"
31-
#import "SentryClient+Logs.h"
3231
#import "SentryContinuousProfiler.h"
3332
#import "SentryCrash.h"
3433
#import "SentryCrashDebug.h"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@_implementationOnly import _SentryPrivate
2+
import Foundation
3+
4+
// Swift extensions to provide properly typed log-related APIs for SPM builds.
5+
// In SPM builds, SentryLog is only forward declared in the Objective-C headers,
6+
// which causes Swift-to-Objective-C bridging issues. These extensions work around
7+
// that limitation by providing Swift-native methods and properties that use dynamic
8+
// dispatch internally.
9+
10+
#if SWIFT_PACKAGE
11+
12+
/**
13+
* Use this callback to drop or modify a log before the SDK sends it to Sentry. Return `nil` to
14+
* drop the log.
15+
*/
16+
public typealias SentryBeforeSendLogCallback = (SentryLog) -> SentryLog?
17+
18+
@objc
19+
public extension Options {
20+
/**
21+
* Use this callback to drop or modify a log before the SDK sends it to Sentry. Return `nil` to
22+
* drop the log.
23+
*/
24+
@objc
25+
var beforeSendLog: SentryBeforeSendLogCallback? {
26+
get { return value(forKey: "beforeSendLogDynamic") as? SentryBeforeSendLogCallback }
27+
set { setValue(newValue, forKey: "beforeSendLogDynamic") }
28+
}
29+
}
30+
31+
#endif // SWIFT_PACKAGE

0 commit comments

Comments
 (0)