Skip to content
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- Improve launch profile configuration management (#5318)
- Record user for watchdog termination events (#5558)
- Add support for dist and environment fields for termination watch (#5560)
- Record user for watchdog termination events (#5558)
- Add support for tags and context fields for termination watch (#5561)

## 8.53.1

Expand Down
16 changes: 16 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,9 @@
D8F8F5572B835BC600AC5465 /* SentryMsgPackSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D8F8F5562B835BC600AC5465 /* SentryMsgPackSerializerTests.m */; };
D8FC98AB2CD0DAB30009824C /* BreadcrumbExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FC98AA2CD0DAAC0009824C /* BreadcrumbExtension.swift */; };
D8FFE50C2703DBB400607131 /* SwizzlingCallTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FFE50B2703DAAE00607131 /* SwizzlingCallTests.swift */; };
F41362112E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */; };
F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */; };
F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */; };
F44858132E03579D0013E63B /* SentryCrashDynamicLinker+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */; };
F452437E2DE60B71003E8F50 /* SentryUseNSExceptionCallstackWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = F452437D2DE60B71003E8F50 /* SentryUseNSExceptionCallstackWrapper.m */; };
F45243882DE65968003E8F50 /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = F45243872DE65968003E8F50 /* ExceptionCatcher.m */; };
Expand All @@ -1028,6 +1031,7 @@
F452438C2DE65BC0003E8F50 /* SentryUseNSExceptionCallstackWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = F452438B2DE65BC0003E8F50 /* SentryUseNSExceptionCallstackWrapper.h */; };
F458D1132E180BB00028273E /* SentryFileManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F458D1122E180BB00028273E /* SentryFileManagerProtocol.swift */; };
F458D1152E1869AD0028273E /* SentryScopePersistentStore+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = F458D1142E1869AD0028273E /* SentryScopePersistentStore+String.swift */; };
F46DA6C32E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */; };
F49D41982DEA27AF00D9244E /* SentryUseNSExceptionCallstackWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49D41972DEA27AF00D9244E /* SentryUseNSExceptionCallstackWrapperTests.swift */; };
F49D419A2DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49D41992DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift */; };
F49D419C2DEA30C300D9244E /* SentryCrashExceptionApplicationHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = F49D419B2DEA30B800D9244E /* SentryCrashExceptionApplicationHelper.h */; };
Expand Down Expand Up @@ -2285,13 +2289,17 @@
D8F8F5562B835BC600AC5465 /* SentryMsgPackSerializerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryMsgPackSerializerTests.m; sourceTree = "<group>"; };
D8FC98AA2CD0DAAC0009824C /* BreadcrumbExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbExtension.swift; sourceTree = "<group>"; };
D8FFE50B2703DAAE00607131 /* SwizzlingCallTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwizzlingCallTests.swift; sourceTree = "<group>"; };
F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Tags.swift"; sourceTree = "<group>"; };
F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+User.swift"; sourceTree = "<group>"; };
F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Context.swift"; sourceTree = "<group>"; };
F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCrashDynamicLinker+Test.h"; sourceTree = "<group>"; };
F452437D2DE60B71003E8F50 /* SentryUseNSExceptionCallstackWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryUseNSExceptionCallstackWrapper.m; sourceTree = "<group>"; };
F45243862DE65968003E8F50 /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = "<group>"; };
F45243872DE65968003E8F50 /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = "<group>"; };
F452438B2DE65BC0003E8F50 /* SentryUseNSExceptionCallstackWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUseNSExceptionCallstackWrapper.h; path = include/SentryUseNSExceptionCallstackWrapper.h; sourceTree = "<group>"; };
F458D1122E180BB00028273E /* SentryFileManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFileManagerProtocol.swift; sourceTree = "<group>"; };
F458D1142E1869AD0028273E /* SentryScopePersistentStore+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+String.swift"; sourceTree = "<group>"; };
F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Helper.swift"; sourceTree = "<group>"; };
F49D41972DEA27AF00D9244E /* SentryUseNSExceptionCallstackWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUseNSExceptionCallstackWrapperTests.swift; sourceTree = "<group>"; };
F49D41992DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashExceptionApplicationTests.swift; sourceTree = "<group>"; };
F49D419B2DEA30B800D9244E /* SentryCrashExceptionApplicationHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryCrashExceptionApplicationHelper.h; path = include/SentryCrashExceptionApplicationHelper.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4041,7 +4049,11 @@
isa = PBXGroup;
children = (
F4E3DCCA2E1579240093CB80 /* SentryScopePersistentStore.swift */,
F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */,
F458D1142E1869AD0028273E /* SentryScopePersistentStore+String.swift */,
F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */,
F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */,
F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */,
);
path = Persistence;
sourceTree = "<group>";
Expand Down Expand Up @@ -5320,6 +5332,7 @@
84302A812B5767A50027A629 /* SentryLaunchProfiling.m in Sources */,
63AA76A31EB9CBAA00D153DE /* SentryDsn.m in Sources */,
84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */,
F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */,
63B818FA1EC34639002FDF4C /* SentryDebugMeta.m in Sources */,
7B98D7D325FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m in Sources */,
8E564AE8267AF22600FE117D /* SentryNetworkTrackingIntegration.m in Sources */,
Expand Down Expand Up @@ -5607,6 +5620,8 @@
D40604472DD2471600C40DC0 /* SentryNSApplication.m in Sources */,
7DAC589123D8B2E0001CF26B /* SentryGlobalEventProcessor.m in Sources */,
7BBD189E244EC8D200427C76 /* SentryRetryAfterHeaderParser.m in Sources */,
F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */,
F46DA6C32E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift in Sources */,
63FE711920DA4C1000CDBAE8 /* SentryCrashMachineContext.c in Sources */,
63FE711B20DA4C1000CDBAE8 /* SentryCrashString.c in Sources */,
7B14089824878F950035403D /* SentryCrashStackEntryMapper.m in Sources */,
Expand Down Expand Up @@ -5640,6 +5655,7 @@
03F84D3327DD4191008FE43F /* SentryMachLogging.cpp in Sources */,
D85852BA27EDDC5900C6D8AE /* SentryUIApplication.m in Sources */,
FAB3599A2E05D8080083D5E3 /* SentryEventSwiftHelper.m in Sources */,
F41362112E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift in Sources */,
7B4E375F258231FC00059C93 /* SentryAttachment.m in Sources */,
636085141ED47BE600E8599E /* SentryFileManager.m in Sources */,
63FE710B20DA4C1000CDBAE8 /* SentryCrashMach.c in Sources */,
Expand Down
5 changes: 0 additions & 5 deletions Sources/Sentry/SentryScope.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@

@interface SentryScope ()

/**
* Set global tags -> these will be sent with every event
*/
@property (atomic, strong) NSMutableDictionary<NSString *, NSString *> *tagDictionary;

/**
* Set global extra -> these will be sent with every event
*/
Expand Down
6 changes: 4 additions & 2 deletions Sources/Sentry/SentryWatchdogTerminationScopeObserver.m
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ - (void)setLevel:(enum SentryLevel)level

- (void)setTags:(nullable NSDictionary<NSString *, NSString *> *)tags
{
// Left blank on purpose
[self.attributesProcessor setTags:tags];
}

- (void)setUser:(nullable SentryUser *)user
Expand All @@ -89,7 +89,9 @@ - (void)setUser:(nullable SentryUser *)user

- (void)setTraceContext:(nullable NSDictionary<NSString *, id> *)traceContext
{
// Left blank on purpose
// Nothing to do here, Trace Context is not persisted for watchdog termination events
// On regular events, we have the current trace in memory, but there isn't time to persist one
// in watchdog termination events
}

@end
Expand Down
1 change: 1 addition & 0 deletions Sources/Sentry/SentryWatchdogTerminationTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ - (void)start
event.user = [self.scopePersistentStore readPreviousUserFromDisk];
event.dist = [self.scopePersistentStore readPreviousDistFromDisk];
event.environment = [self.scopePersistentStore readPreviousEnvironmentFromDisk];
event.tags = [self.scopePersistentStore readPreviousTagsFromDisk];

SentryException *exception =
[[SentryException alloc] initWithValue:SentryWatchdogTerminationExceptionValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# import <SentryHub.h>
# import <SentryNSProcessInfoWrapper.h>
# import <SentryOptions+Private.h>
# import <SentryPropagationContext.h>
# import <SentrySDK+Private.h>
# import <SentrySwift.h>
# import <SentryWatchdogTerminationBreadcrumbProcessor.h>
Expand Down Expand Up @@ -99,6 +100,9 @@ - (BOOL)installWithOptions:(SentryOptions *)options
[scopeObserver setUser:outerScope.userObject];
[scopeObserver setEnvironment:outerScope.environmentString];
[scopeObserver setDist:outerScope.distString];
[scopeObserver setTags:outerScope.tags];
// We intentionally skip calling `setTraceContext:` since traces are not stored for watchdog
// termination events
}];

return YES;
Expand Down
5 changes: 5 additions & 0 deletions Sources/Sentry/include/SentryScope+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ NS_ASSUME_NONNULL_BEGIN
@property (atomic, strong)
NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *contextDictionary;

/**
* Set global tags -> these will be sent with every event
*/
@property (atomic, strong) NSMutableDictionary<NSString *, NSString *> *tagDictionary;

- (void)addObserver:(id<SentryScopeObserver>)observer;

- (nullable SentryEvent *)applyToEvent:(SentryEvent *)event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ import Foundation
}
}

public func setTags(_ tags: [String: String]?) {
setData(data: tags, field: .tags) { [weak self] data in
self?.scopePersistentStore.writeTagsToDisk(tags: data)
}
}

// MARK: - Private
private func setData<T>(data: T?, field: SentryScopeField, save: @escaping (T) -> Void) {
SentrySDKLog.debug("Setting \(field.name) in background queue: \(String(describing: data))")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@_implementationOnly import _SentryPrivate

extension SentryScopePersistentStore {
func encode(context: [String: [String: Any]]) -> Data? {
return encode(context, "context", true)
}

func decodeContext(from data: Data) -> [String: [String: Any]]? {
return decode(from: data, "context")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@_implementationOnly import _SentryPrivate

extension SentryScopePersistentStore {
func encode<T>(_ genericModel: T, _ name: String, _ sanitize: Bool = false) -> Data? {
var model: Any = genericModel
if sanitize {
// We need to check if the context is a valid JSON object before encoding it.
// Otherwise it will throw an unhandled `NSInvalidArgumentException` exception.
// The error handler is required but will never be executed due to Swift type safety.
guard let wrapped = genericModel as? [AnyHashable: Any],
let sanitizedModel = sentry_sanitize(wrapped) else {
SentrySDKLog.error("Failed to sanitize \(name), reason: \(name) is not valid json: \(genericModel)")
return nil

Check warning on line 13 in Sources/Swift/Persistence/SentryScopePersistentStore+Helper.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Persistence/SentryScopePersistentStore+Helper.swift#L12-L13

Added lines #L12 - L13 were not covered by tests
}
model = sanitizedModel
}

guard let data = SentrySerialization.data(withJSONObject: model) else {
SentrySDKLog.error("Failed to serialize \(name), reason: \(name) is not valid json: \(genericModel)")
return nil
}

return data
}

func decode<T>(from data: Data, _ name: String) -> [String: T]? {
guard let deserialized = SentrySerialization.deserializeDictionary(fromJsonData: data) else {
SentrySDKLog.error("Failed to deserialize \(name), reason: data is not valid json")
return nil
}

// `SentrySerialization` is a wrapper around `NSJSONSerialization` which returns any type of data (`id`).
// It is the casted to a `NSDictionary`, which is then casted to a `[AnyHashable: Any]` in Swift.
//
// The responsibility of validating and casting the deserialized data from any data to a dictionary is delegated
// to the `SentrySerialization` class.
//
// As this decode Tags method specifically returns a dictionary of strings, we need to ensure that
// each value is a string.
//
// If the deserialized value is not a string, something clearly went wrong and we should discard the data.

// Iterate through the deserialized dictionary and check if the type is a dictionary.
// When all values are strings, we can safely cast it to `T` without allocating
// additional memory (like when mapping values).
for (key, value) in deserialized {
guard value is T else {
SentrySDKLog.error("Failed to deserialize \(name), reason: value for key \(key) is not a valid string")
return nil
}
}

return deserialized as? [String: T]
}
}
11 changes: 11 additions & 0 deletions Sources/Swift/Persistence/SentryScopePersistentStore+Tags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@_implementationOnly import _SentryPrivate

extension SentryScopePersistentStore {
func encode(tags: [String: String]) -> Data? {
return encode(tags, "tags", false)
}

func decodeTags(from data: Data) -> [String: String]? {
return decode(from: data, "tags")
}
}
20 changes: 20 additions & 0 deletions Sources/Swift/Persistence/SentryScopePersistentStore+User.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@_implementationOnly import _SentryPrivate

extension SentryScopePersistentStore {
func encode(user: User) -> Data? {
guard let data = SentrySerialization.data(withJSONObject: user.serialize()) else {
SentrySDKLog.error("Failed to serialize user, reason: user is not valid json: \(user)")
return nil
}
return data
}

func decodeUser(from data: Data) -> User? {
return decoderUserHelper(data)
}

// Swift compiler can't infer T, even if I try to cast it
private func decoderUserHelper(_ data: Data) -> UserDecodable? {
return decodeFromJSONData(jsonData: data)
}
}
Loading
Loading