diff --git a/.github/workflows/test-cross-platform.yml b/.github/workflows/test-cross-platform.yml index a72b598d77..267c957eb8 100644 --- a/.github/workflows/test-cross-platform.yml +++ b/.github/workflows/test-cross-platform.yml @@ -55,7 +55,7 @@ jobs: with: repository: getsentry/sentry-react-native path: sentry-react-native - ref: removeEnableTracing + ref: denrase/options-enable-logs - name: Enable Corepack working-directory: sentry-react-native diff --git a/CHANGELOG.md b/CHANGELOG.md index cfec2220d0..54e8655ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Removes deprecated user feedback API, this is replaced with the new feedback API (#5591) - Removes `enablePerformanceV2` option and makes this the default. The app start duration will now finish when the first frame is drawn instead of when the OS posts the UIWindowDidBecomeVisibleNotification. (#6008) - Removes enableTracing property from SentryOptions (#5694) +- Structured Logs: Move options out of experimental (#6359) ### Features diff --git a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift index 528d2cf7de..27f63dd10f 100644 --- a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift +++ b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift @@ -161,10 +161,11 @@ public struct SentrySDKWrapper { } #endif // !os(macOS) && !os(tvOS) && !os(watchOS) && !os(visionOS) + options.enableLogs = true + // Experimental features options.experimental.enableFileManagerSwizzling = !SentrySDKOverrides.Other.disableFileManagerSwizzling.boolValue options.experimental.enableUnhandledCPPExceptionsV2 = true - options.experimental.enableLogs = true } func configureInitialScope(scope: Scope, options: Options) -> Scope { diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index 7a335d3f5b..c35b78676b 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -157,6 +157,13 @@ NS_SWIFT_NAME(Options) */ @property (nullable, nonatomic, copy) SentryBeforeSendSpanCallback beforeSendSpan NS_SWIFT_SENDABLE; +/** + * When enabled, the SDK sends logs to Sentry. Logs can be captured using the @c SentrySDK.logger + * API, which provides structured logging with attributes. + * @note Default value is @c NO . + */ +@property (nonatomic, assign) BOOL enableLogs; + #if !SWIFT_PACKAGE /** * Use this callback to drop or modify a log before the SDK sends it to Sentry. Return @c nil to diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 575f757cdb..e57f301797 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -117,6 +117,7 @@ - (instancetype)init self.enableNetworkTracking = YES; self.enableFileIOTracing = YES; self.enableNetworkBreadcrumbs = YES; + self.enableLogs = NO; self.tracesSampleRate = nil; #if SENTRY_TARGET_PROFILING_SUPPORTED # if !SDK_V9 diff --git a/Sources/Sentry/SentyOptionsInternal.m b/Sources/Sentry/SentyOptionsInternal.m index b214fb969a..63315f6cfd 100644 --- a/Sources/Sentry/SentyOptionsInternal.m +++ b/Sources/Sentry/SentyOptionsInternal.m @@ -155,6 +155,8 @@ + (BOOL)validateOptions:(NSDictionary *)options sentryOptions.maxBreadcrumbs = [options[@"maxBreadcrumbs"] unsignedIntValue]; } + [self setBool:options[@"enableLogs"] block:^(BOOL value) { sentryOptions.enableLogs = value; }]; + [self setBool:options[@"enableNetworkBreadcrumbs"] block:^(BOOL value) { sentryOptions.enableNetworkBreadcrumbs = value; }]; @@ -172,6 +174,12 @@ + (BOOL)validateOptions:(NSDictionary *)options sentryOptions.beforeSend = options[@"beforeSend"]; } +#if !SWIFT_PACKAGE + if ([self isBlock:options[@"beforeSendLog"]]) { + sentryOptions.beforeSendLog = options[@"beforeSendLog"]; + } +#endif // !SWIFT_PACKAGE + if ([self isBlock:options[@"beforeSendSpan"]]) { sentryOptions.beforeSendSpan = options[@"beforeSendSpan"]; } diff --git a/Sources/Swift/Helper/SentrySDK.swift b/Sources/Swift/Helper/SentrySDK.swift index 8c068a5c6e..293b63efa7 100644 --- a/Sources/Swift/Helper/SentrySDK.swift +++ b/Sources/Swift/Helper/SentrySDK.swift @@ -37,7 +37,7 @@ import Foundation } let hub = SentryDependencyContainerSwiftHelper.currentHub() var batcher: SentryLogBatcher? - if let client = hub.getClient(), client.options.experimental.enableLogs { + if let client = hub.getClient(), client.options.enableLogs { batcher = SentryLogBatcher(client: client, dispatchQueue: Dependencies.dispatchQueueWrapper) } let logger = SentryLogger( diff --git a/Sources/Swift/SentryExperimentalOptions.swift b/Sources/Swift/SentryExperimentalOptions.swift index 5f5a1a5dce..dccb6879c0 100644 --- a/Sources/Swift/SentryExperimentalOptions.swift +++ b/Sources/Swift/SentryExperimentalOptions.swift @@ -30,7 +30,7 @@ public final class SentryExperimentalOptions: NSObject { * - Experiment: This is an experimental feature and is therefore disabled by default. We'll enable it by default in a future major release. */ public var enableUnhandledCPPExceptionsV2 = false - + /** * Forces enabling of session replay in unreliable environments. * @@ -48,11 +48,6 @@ public final class SentryExperimentalOptions: NSObject { */ public var enableSessionReplayInUnreliableEnvironment = false - /** - * Logs are considered beta. - */ - public var enableLogs = false - @_spi(Private) public func validateOptions(_ options: [String: Any]?) { } } diff --git a/Tests/SentryTests/SentryLogBatcherTests.swift b/Tests/SentryTests/SentryLogBatcherTests.swift index ec1ad446bd..99856566e7 100644 --- a/Tests/SentryTests/SentryLogBatcherTests.swift +++ b/Tests/SentryTests/SentryLogBatcherTests.swift @@ -14,7 +14,7 @@ final class SentryLogBatcherTests: XCTestCase { super.setUp() options = Options() - options.experimental.enableLogs = true + options.enableLogs = true testClient = TestClient(options: options) testDispatchQueue = TestSentryDispatchQueueWrapper() diff --git a/Tests/SentryTests/SentryLoggerTests.swift b/Tests/SentryTests/SentryLoggerTests.swift index 1539dd3246..3a298b0f18 100644 --- a/Tests/SentryTests/SentryLoggerTests.swift +++ b/Tests/SentryTests/SentryLoggerTests.swift @@ -311,7 +311,7 @@ final class SentryLoggerTests: XCTestCase { } func testCaptureLog_SetsTraceIdFromPropagationContext() { - fixture.options.experimental.enableLogs = true + fixture.options.enableLogs = true let expectedTraceId = SentryId() let propagationContext = SentryPropagationContext(trace: expectedTraceId, spanId: SpanId()) diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index 142987b676..42db4dc1f6 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -202,6 +202,11 @@ - (void)testEnableNetworkBreadcrumbs [self testBooleanField:@"enableNetworkBreadcrumbs"]; } +- (void)testEnableLogs +{ + [self testBooleanField:@"enableLogs" defaultValue:NO]; +} + - (void)testEnableAutoBreadcrumbTracking { [self testBooleanField:@"enableAutoBreadcrumbTracking"]; @@ -306,6 +311,30 @@ - (void)testNSNullBeforeSend_ReturnsNil XCTAssertFalse([options.beforeSend isEqual:[NSNull null]]); } +#if !SWIFT_PACKAGE +- (void)testBeforeSendLog +{ + SentryBeforeSendLogCallback callback = ^(SentryLog *log) { return log; }; + SentryOptions *options = [self getValidOptions:@{ @"beforeSendLog" : callback }]; + + XCTAssertEqual(callback, options.beforeSendLog); +} + +- (void)testDefaultBeforeSendLog +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertNil(options.beforeSendLog); +} + +- (void)testGarbageBeforeSendLog_ReturnsNil +{ + SentryOptions *options = [self getValidOptions:@{ @"beforeSendLog" : @"fault" }]; + + XCTAssertNil(options.beforeSendLog); +} +#endif // !SWIFT_PACKAGE + - (void)testBeforeSendSpan { SentryBeforeSendSpanCallback callback = ^(id span) { return span; }; @@ -615,6 +644,9 @@ - (void)testNSNull_SetsDefaultValue @"maxCacheItems" : [NSNull null], @"cacheDirectoryPath" : [NSNull null], @"beforeSend" : [NSNull null], +#if !SWIFT_PACKAGE + @"beforeSendLog" : [NSNull null], +#endif @"beforeBreadcrumb" : [NSNull null], @"onCrashedLastRun" : [NSNull null], @"integrations" : [NSNull null], diff --git a/Tests/SentryTests/SentrySDKInternalTests.swift b/Tests/SentryTests/SentrySDKInternalTests.swift index 8605637d54..17e4357a50 100644 --- a/Tests/SentryTests/SentrySDKInternalTests.swift +++ b/Tests/SentryTests/SentrySDKInternalTests.swift @@ -701,7 +701,7 @@ class SentrySDKInternalTests: XCTestCase { @available(*, deprecated, message: "This is deprecated because SentryOptions integrations is deprecated") func testLogger_WithLogsEnabled_CapturesLog() { - fixture.client.options.experimental.enableLogs = true + fixture.client.options.enableLogs = true givenSdkWithHub() SentrySDK.logger.error(String(repeating: "S", count: 1_024 * 1_024)) @@ -716,7 +716,7 @@ class SentrySDKInternalTests: XCTestCase { } func testLogger_WithNoClient_DoesNotCaptureLog() { - fixture.client.options.experimental.enableLogs = true + fixture.client.options.enableLogs = true let hubWithoutClient = SentryHub(client: nil, andScope: nil) SentrySDKInternal.setCurrentHub(hubWithoutClient) @@ -733,7 +733,7 @@ class SentrySDKInternalTests: XCTestCase { @available(*, deprecated, message: "This is deprecated because SentryOptions integrations is deprecated") func testLogger_WithLogsDisabled_DoesNotCaptureLog() { - fixture.client.options.experimental.enableLogs = false + fixture.client.options.enableLogs = false givenSdkWithHub() SentrySDK.logger.error("foo") diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index 5ed6d5dc35..17c6d12808 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -444,7 +444,7 @@ class SentrySDKTests: XCTestCase { // MARK: - Logger Flush Tests func testFlush_CallsLoggerCaptureLogs() { - fixture.client.options.experimental.enableLogs = true + fixture.client.options.enableLogs = true SentrySDKInternal.setCurrentHub(fixture.hub) SentrySDKInternal.setStart(with: fixture.client.options) @@ -462,7 +462,7 @@ class SentrySDKTests: XCTestCase { } func testClose_CallsLoggerCaptureLogs() { - fixture.client.options.experimental.enableLogs = true + fixture.client.options.enableLogs = true SentrySDKInternal.setCurrentHub(fixture.hub) SentrySDKInternal.setStart(with: fixture.client.options) @@ -484,7 +484,7 @@ class SentrySDKTests: XCTestCase { let loggerBeforeStart = SentrySDK.logger // Now properly start the SDK using internal APIs - fixture.client.options.experimental.enableLogs = true + fixture.client.options.enableLogs = true SentrySDKInternal.setCurrentHub(fixture.hub) SentrySDKInternal.setStart(with: fixture.client.options) @@ -506,7 +506,7 @@ class SentrySDKTests: XCTestCase { func testLogger_WhenLogsDisabled() { // Start SDK with logs disabled - fixture.client.options.experimental.enableLogs = false + fixture.client.options.enableLogs = false SentrySDKInternal.setCurrentHub(fixture.hub) SentrySDKInternal.setStart(with: fixture.client.options) diff --git a/sdk_api.json b/sdk_api.json index d7ace6e60a..2fd11b5fda 100644 --- a/sdk_api.json +++ b/sdk_api.json @@ -16136,6 +16136,89 @@ } ] }, + { + "kind": "Var", + "name": "enableLogs", + "printedName": "enableLogs", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryOptions(py)enableLogs", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "enableLogs", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryOptions(im)enableLogs", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "enableLogs", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryOptions(im)setEnableLogs:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setEnableLogs:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, { "kind": "Var", "name": "beforeSendLog", @@ -53434,82 +53517,6 @@ } ] }, - { - "kind": "Var", - "name": "enableLogs", - "printedName": "enableLogs", - "children": [ - { - "kind": "TypeNominal", - "name": "Bool", - "printedName": "Swift.Bool", - "usr": "s:Sb" - } - ], - "declKind": "Var", - "usr": "c:@M@Sentry@objc(cs)SentryExperimentalOptions(py)enableLogs", - "mangledName": "$s6Sentry0A19ExperimentalOptionsC10enableLogsSbvp", - "moduleName": "Sentry", - "declAttributes": [ - "Final", - "ObjC", - "HasStorage" - ], - "hasStorage": true, - "accessors": [ - { - "kind": "Accessor", - "name": "Get", - "printedName": "Get()", - "children": [ - { - "kind": "TypeNominal", - "name": "Bool", - "printedName": "Swift.Bool", - "usr": "s:Sb" - } - ], - "declKind": "Accessor", - "usr": "c:@M@Sentry@objc(cs)SentryExperimentalOptions(im)enableLogs", - "mangledName": "$s6Sentry0A19ExperimentalOptionsC10enableLogsSbvg", - "moduleName": "Sentry", - "implicit": true, - "declAttributes": [ - "Final", - "ObjC" - ], - "accessorKind": "get" - }, - { - "kind": "Accessor", - "name": "Set", - "printedName": "Set()", - "children": [ - { - "kind": "TypeNominal", - "name": "Void", - "printedName": "()" - }, - { - "kind": "TypeNominal", - "name": "Bool", - "printedName": "Swift.Bool", - "usr": "s:Sb" - } - ], - "declKind": "Accessor", - "usr": "c:@M@Sentry@objc(cs)SentryExperimentalOptions(im)setEnableLogs:", - "mangledName": "$s6Sentry0A19ExperimentalOptionsC10enableLogsSbvs", - "moduleName": "Sentry", - "implicit": true, - "declAttributes": [ - "Final", - "ObjC" - ], - "accessorKind": "set" - } - ] - }, { "kind": "Constructor", "name": "init",