-
-
Notifications
You must be signed in to change notification settings - Fork 371
Structured Logs: Add log APIs to Hub and Client
#6518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
455e640
fe496c6
79f488f
0a4031e
7a38ee2
6f84e05
010294f
f376254
f1e0dc3
e9d6a1e
b8be304
e060a58
ca30a2d
585893a
4d72a5d
4533b76
4446d9f
f913b46
4bd9c2a
5220b71
2eafc00
4197873
b42d510
f00c069
dee5276
11031c7
ebaac6b
0239c61
130e34d
dd00bcb
ca58453
df33907
11f2254
ddc8ad7
1ee870f
1fe6bb7
d7e46ca
33d4912
0dd1af0
0d24147
75d3996
c662522
a8766e9
c6c9bb6
26d91ce
d5e2104
c811a52
7510ff6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -44,13 +44,14 @@ | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| NS_ASSUME_NONNULL_BEGIN | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @interface SentryClientInternal () | ||||||||||||||||||||||
| @interface SentryClientInternal () <SentryLogBatcherDelegate> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @property (nonatomic, strong) SentryTransportAdapter *transportAdapter; | ||||||||||||||||||||||
| @property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider; | ||||||||||||||||||||||
| @property (nonatomic, strong) id<SentryRandomProtocol> random; | ||||||||||||||||||||||
| @property (nonatomic, strong) NSLocale *locale; | ||||||||||||||||||||||
| @property (nonatomic, strong) NSTimeZone *timezone; | ||||||||||||||||||||||
| @property (nonatomic, strong) SentryLogBatcher *logBatcher; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -113,6 +114,10 @@ - (instancetype)initWithOptions:(SentryOptions *)options | |||||||||||||||||||||
| self.locale = locale; | ||||||||||||||||||||||
| self.timezone = timezone; | ||||||||||||||||||||||
| self.attachmentProcessors = [[NSMutableArray alloc] init]; | ||||||||||||||||||||||
| self.logBatcher = [[SentryLogBatcher alloc] | ||||||||||||||||||||||
| initWithOptions:options | ||||||||||||||||||||||
| dispatchQueue:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper | ||||||||||||||||||||||
| delegate:self]; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // The SDK stores the installationID in a file. The first call requires file IO. To avoid | ||||||||||||||||||||||
| // executing this on the main thread, we cache the installationID async here. | ||||||||||||||||||||||
|
|
@@ -618,7 +623,11 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| - (void)flush:(NSTimeInterval)timeout | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| [self.transportAdapter flush:timeout]; | ||||||||||||||||||||||
| NSTimeInterval captureLogsDuration = [self.logBatcher captureLogs]; | ||||||||||||||||||||||
| // Capturing batched logs should never take long, but we need to fall back to a sane value. | ||||||||||||||||||||||
| // This is a workaround for in-memory logs, until we'll write batched logs to disk, | ||||||||||||||||||||||
| // to avoid data loss due to crashes. This is a trade-off until then. | ||||||||||||||||||||||
| [self.transportAdapter flush:fmax(timeout / 2, timeout - captureLogsDuration)]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - (void)close | ||||||||||||||||||||||
|
|
@@ -1088,7 +1097,14 @@ - (void)removeAttachmentProcessor:(id<SentryClientAttachmentProcessor>)attachmen | |||||||||||||||||||||
| return processedAttachments; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount; | ||||||||||||||||||||||
| - (void)_swiftCaptureLog:(NSObject *)log withScope:(SentryScope *)scope | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if ([log isKindOfClass:[SentryLog class]]) { | ||||||||||||||||||||||
| [self.logBatcher addLog:(SentryLog *)log scope:scope]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+1100
to
+1105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above, cause this is a Swift Type used in ObjC, once we call the internal hub from Swift (when using SPM), it's not visible. |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| SentryEnvelopeItemHeader *header = | ||||||||||||||||||||||
| [[SentryEnvelopeItemHeader alloc] initWithType:SentryEnvelopeItemTypes.log | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,7 @@ | |
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @interface SentryHubInternal () | ||
| @interface SentryHubInternal () <SentryLoggerDelegate> | ||
|
|
||
| @property (nullable, atomic, strong) SentryClientInternal *client; | ||
| @property (nullable, nonatomic, strong) SentryScope *scope; | ||
|
|
@@ -73,6 +73,10 @@ - (instancetype)initWithClient:(nullable SentryClientInternal *)client | |
| if (_scope) { | ||
| [_crashWrapper enrichScope:SENTRY_UNWRAP_NULLABLE(SentryScope, _scope)]; | ||
| } | ||
|
|
||
| __swiftLogger = [[SentryLogger alloc] | ||
| initWithDelegate:self | ||
| dateProvider:SentryDependencyContainer.sharedInstance.dateProvider]; | ||
| } | ||
|
|
||
| return self; | ||
|
|
@@ -833,6 +837,34 @@ - (void)unregisterSessionListener:(id<SentrySessionListener>)listener | |
| } | ||
| } | ||
|
|
||
| // SentryLoggerDelegate | ||
|
|
||
| - (void)captureLog:(SentryLog *)log | ||
| { | ||
| SentryClientInternal *client = self.client; | ||
| if (client != nil) { | ||
| #if SENTRY_TARGET_REPLAY_SUPPORTED | ||
| NSString *scopeReplayId = self.scope.replayId; | ||
| if (scopeReplayId != nil) { | ||
| // Session mode: use scope replay ID | ||
| [log setAttribute:[[SentryStructuredLogAttribute alloc] initWithString:scopeReplayId] | ||
| forKey:@"sentry.replay_id"]; | ||
| } else { | ||
| // Buffer mode: check if hub has a session replay ID | ||
| NSString *sessionReplayId = [self getSessionReplayId]; | ||
| if (sessionReplayId != nil) { | ||
| [log setAttribute:[[SentryStructuredLogAttribute alloc] | ||
| initWithString:sessionReplayId] | ||
| forKey:@"sentry.replay_id"]; | ||
| [log setAttribute:[[SentryStructuredLogAttribute alloc] initWithBoolean:YES] | ||
| forKey:@"sentry._internal.replay_is_buffering"]; | ||
| } | ||
| } | ||
| #endif | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Swift-Objective-C mismatch crashes from log attributesThe |
||
| [client _swiftCaptureLog:log withScope:self.scope]; | ||
| } | ||
| } | ||
|
|
||
| #pragma mark - Protected | ||
|
|
||
| - (NSArray<NSString *> *)trimmedInstalledIntegrationNames | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| @_implementationOnly import _SentryPrivate | ||
| import Foundation | ||
|
|
||
| // Swift extensions to provide properly typed log-related APIs for SPM builds. | ||
| // In SPM builds, SentryLog is only forward declared in the Objective-C headers, | ||
| // which causes Swift-to-Objective-C bridging issues. These extensions work around | ||
| // that limitation by providing Swift-native methods and properties that use dynamic | ||
| // dispatch internally. | ||
|
|
||
| #if SWIFT_PACKAGE | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @noahsmartin, can you please double-check if this approach works? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is existing code, I just moved it to its own file so it stays on our radar. Circular dependency issue when installing through SPM, before that, the SPM target on CI would not compile. |
||
|
|
||
| /** | ||
| * Use this callback to drop or modify a log before the SDK sends it to Sentry. Return `nil` to | ||
| * drop the log. | ||
| */ | ||
| public typealias SentryBeforeSendLogCallback = (SentryLog) -> SentryLog? | ||
|
|
||
| @objc | ||
| public extension Options { | ||
| /** | ||
| * Use this callback to drop or modify a log before the SDK sends it to Sentry. Return `nil` to | ||
| * drop the log. | ||
| */ | ||
| @objc | ||
| var beforeSendLog: SentryBeforeSendLogCallback? { | ||
| get { return value(forKey: "beforeSendLogDynamic") as? SentryBeforeSendLogCallback } | ||
| set { setValue(newValue, forKey: "beforeSendLogDynamic") } | ||
| } | ||
| } | ||
|
|
||
| #endif // SWIFT_PACKAGE | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,28 +27,13 @@ import Foundation | |
|
|
||
| /// API to access Sentry logs | ||
| @objc public static var logger: SentryLogger { | ||
| return _loggerLock.synchronized { | ||
| let sdkEnabled = SentrySDKInternal.isEnabled | ||
| if !sdkEnabled { | ||
| SentrySDKLog.fatal("Logs called before SentrySDK.start() will be dropped.") | ||
| } | ||
| if let _logger, _loggerConfigured { | ||
| return _logger | ||
| } | ||
| let hub = SentrySDKInternal.currentHub() | ||
| var batcher: SentryLogBatcher? | ||
| if let client = hub.getClient(), client.options.enableLogs { | ||
| batcher = SentryLogBatcher(client: client, dispatchQueue: Dependencies.dispatchQueueWrapper) | ||
| } | ||
| let logger = SentryLogger( | ||
| hub: hub, | ||
| dateProvider: Dependencies.dateProvider, | ||
| batcher: batcher | ||
| ) | ||
| _logger = logger | ||
| _loggerConfigured = sdkEnabled | ||
| return logger | ||
| if !SentrySDKInternal.isEnabled { | ||
| SentrySDKLog.fatal("Logs called before SentrySDK.start() will be dropped.") | ||
| } | ||
| // We know the type so it's fine to force cast. | ||
| // swiftlint:disable force_cast | ||
| return SentrySDKInternal.currentHub()._swiftLogger as! SentryLogger | ||
| // swiftlint:enable force_cast | ||
|
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same issue as above, when using SPM, logger would not be visible here due tu circular dependency between Swift/Objc parts of the SDK. |
||
| } | ||
|
|
||
| /// Inits and configures Sentry (`SentryHub`, `SentryClient`) and sets up all integrations. Make sure to | ||
|
|
@@ -360,18 +345,12 @@ import Foundation | |
| /// - note: This might take slightly longer than the specified timeout if there are many batched logs to capture. | ||
| @objc(flush:) | ||
| public static func flush(timeout: TimeInterval) { | ||
| let captureLogsDuration = captureLogs() | ||
| // Capturing batched logs should never take long, but we need to fall back to a sane value. | ||
| // This is a workaround for experimental logs, until we'll write batched logs to disk, | ||
| // to avoid data loss due to crashes. This is a trade-off until then. | ||
| SentrySDKInternal.flush(timeout: max(timeout / 2, timeout - captureLogsDuration)) | ||
| SentrySDKInternal.flush(timeout: timeout) | ||
| } | ||
|
|
||
| /// Closes the SDK, uninstalls all the integrations, and calls `flush` with | ||
| /// `SentryOptions.shutdownTimeInterval`. | ||
| @objc public static func close() { | ||
| // Capturing batched logs should never take long, ignore the duration here. | ||
| _ = captureLogs() | ||
| SentrySDKInternal.close() | ||
| } | ||
|
|
||
|
|
@@ -412,32 +391,6 @@ import Foundation | |
| SentrySDKInternal.stopProfiler() | ||
| } | ||
| #endif | ||
|
|
||
| // MARK: Internal | ||
|
|
||
| /// - note: Conceptually internal but needs to be marked public with SPI for ObjC visibility | ||
| @objc @_spi(Private) public static func clearLogger() { | ||
| _loggerLock.synchronized { | ||
| _logger = nil | ||
| _loggerConfigured = false | ||
| } | ||
| } | ||
|
|
||
| // MARK: Private | ||
|
|
||
| private static var _loggerLock = NSLock() | ||
| private static var _logger: SentryLogger? | ||
| // Flag to re-create instance if accessed before SDK init. | ||
| private static var _loggerConfigured = false | ||
|
|
||
| @discardableResult | ||
| private static func captureLogs() -> TimeInterval { | ||
| var duration: TimeInterval = 0.0 | ||
| _loggerLock.synchronized { | ||
| duration = _logger?.captureLogs() ?? 0.0 | ||
| } | ||
| return duration | ||
| } | ||
| } | ||
|
|
||
| // swiftlint:enable file_length | ||
Uh oh!
There was an error while loading. Please reload this page.