Skip to content

Commit 186f06f

Browse files
authored
feat: Add support for tags and trace context fields for termination watch (#5561)
* Update changelog * feat: Add support for dist and environment fields for termination watch * feat: Add support for tags and context fields for termination watch * Fix renamed properties * Update changelog * fix rebase conflicts * Remove trace context calls * Fix tests after removing traceContext call * Separate extensions for encoding and decoding context, user, tags, and trace context in SentryScopePersistentStore * Remove TraceContext persistence * Remove unnecessary sanitization step * Pr suggestions
1 parent 134fbdf commit 186f06f

21 files changed

+510
-76
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
- Improve launch profile configuration management (#5318)
1616
- Record user for watchdog termination events (#5558)
1717
- Add support for dist and environment fields for termination watch (#5560)
18+
- Record user for watchdog termination events (#5558)
19+
- Add support for tags and context fields for termination watch (#5561)
1820

1921
## 8.53.1
2022

Sentry.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,9 @@
10201020
D8F8F5572B835BC600AC5465 /* SentryMsgPackSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D8F8F5562B835BC600AC5465 /* SentryMsgPackSerializerTests.m */; };
10211021
D8FC98AB2CD0DAB30009824C /* BreadcrumbExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FC98AA2CD0DAAC0009824C /* BreadcrumbExtension.swift */; };
10221022
D8FFE50C2703DBB400607131 /* SwizzlingCallTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FFE50B2703DAAE00607131 /* SwizzlingCallTests.swift */; };
1023+
F41362112E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */; };
1024+
F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */; };
1025+
F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */; };
10231026
F44858132E03579D0013E63B /* SentryCrashDynamicLinker+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */; };
10241027
F452437E2DE60B71003E8F50 /* SentryUseNSExceptionCallstackWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = F452437D2DE60B71003E8F50 /* SentryUseNSExceptionCallstackWrapper.m */; };
10251028
F45243882DE65968003E8F50 /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = F45243872DE65968003E8F50 /* ExceptionCatcher.m */; };
@@ -1028,6 +1031,7 @@
10281031
F452438C2DE65BC0003E8F50 /* SentryUseNSExceptionCallstackWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = F452438B2DE65BC0003E8F50 /* SentryUseNSExceptionCallstackWrapper.h */; };
10291032
F458D1132E180BB00028273E /* SentryFileManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F458D1122E180BB00028273E /* SentryFileManagerProtocol.swift */; };
10301033
F458D1152E1869AD0028273E /* SentryScopePersistentStore+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = F458D1142E1869AD0028273E /* SentryScopePersistentStore+String.swift */; };
1034+
F46DA6C32E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */; };
10311035
F49D41982DEA27AF00D9244E /* SentryUseNSExceptionCallstackWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49D41972DEA27AF00D9244E /* SentryUseNSExceptionCallstackWrapperTests.swift */; };
10321036
F49D419A2DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49D41992DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift */; };
10331037
F49D419C2DEA30C300D9244E /* SentryCrashExceptionApplicationHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = F49D419B2DEA30B800D9244E /* SentryCrashExceptionApplicationHelper.h */; };
@@ -2286,13 +2290,17 @@
22862290
D8F8F5562B835BC600AC5465 /* SentryMsgPackSerializerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryMsgPackSerializerTests.m; sourceTree = "<group>"; };
22872291
D8FC98AA2CD0DAAC0009824C /* BreadcrumbExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbExtension.swift; sourceTree = "<group>"; };
22882292
D8FFE50B2703DAAE00607131 /* SwizzlingCallTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwizzlingCallTests.swift; sourceTree = "<group>"; };
2293+
F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Tags.swift"; sourceTree = "<group>"; };
2294+
F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+User.swift"; sourceTree = "<group>"; };
2295+
F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Context.swift"; sourceTree = "<group>"; };
22892296
F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCrashDynamicLinker+Test.h"; sourceTree = "<group>"; };
22902297
F452437D2DE60B71003E8F50 /* SentryUseNSExceptionCallstackWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryUseNSExceptionCallstackWrapper.m; sourceTree = "<group>"; };
22912298
F45243862DE65968003E8F50 /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = "<group>"; };
22922299
F45243872DE65968003E8F50 /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = "<group>"; };
22932300
F452438B2DE65BC0003E8F50 /* SentryUseNSExceptionCallstackWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUseNSExceptionCallstackWrapper.h; path = include/SentryUseNSExceptionCallstackWrapper.h; sourceTree = "<group>"; };
22942301
F458D1122E180BB00028273E /* SentryFileManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFileManagerProtocol.swift; sourceTree = "<group>"; };
22952302
F458D1142E1869AD0028273E /* SentryScopePersistentStore+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+String.swift"; sourceTree = "<group>"; };
2303+
F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Helper.swift"; sourceTree = "<group>"; };
22962304
F49D41972DEA27AF00D9244E /* SentryUseNSExceptionCallstackWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUseNSExceptionCallstackWrapperTests.swift; sourceTree = "<group>"; };
22972305
F49D41992DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashExceptionApplicationTests.swift; sourceTree = "<group>"; };
22982306
F49D419B2DEA30B800D9244E /* SentryCrashExceptionApplicationHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryCrashExceptionApplicationHelper.h; path = include/SentryCrashExceptionApplicationHelper.h; sourceTree = "<group>"; };
@@ -4044,7 +4052,11 @@
40444052
isa = PBXGroup;
40454053
children = (
40464054
F4E3DCCA2E1579240093CB80 /* SentryScopePersistentStore.swift */,
4055+
F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */,
40474056
F458D1142E1869AD0028273E /* SentryScopePersistentStore+String.swift */,
4057+
F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */,
4058+
F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */,
4059+
F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */,
40484060
);
40494061
path = Persistence;
40504062
sourceTree = "<group>";
@@ -5323,6 +5335,7 @@
53235335
84302A812B5767A50027A629 /* SentryLaunchProfiling.m in Sources */,
53245336
63AA76A31EB9CBAA00D153DE /* SentryDsn.m in Sources */,
53255337
84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */,
5338+
F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */,
53265339
63B818FA1EC34639002FDF4C /* SentryDebugMeta.m in Sources */,
53275340
7B98D7D325FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m in Sources */,
53285341
8E564AE8267AF22600FE117D /* SentryNetworkTrackingIntegration.m in Sources */,
@@ -5610,6 +5623,8 @@
56105623
D40604472DD2471600C40DC0 /* SentryNSApplication.m in Sources */,
56115624
7DAC589123D8B2E0001CF26B /* SentryGlobalEventProcessor.m in Sources */,
56125625
7BBD189E244EC8D200427C76 /* SentryRetryAfterHeaderParser.m in Sources */,
5626+
F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */,
5627+
F46DA6C32E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift in Sources */,
56135628
63FE711920DA4C1000CDBAE8 /* SentryCrashMachineContext.c in Sources */,
56145629
63FE711B20DA4C1000CDBAE8 /* SentryCrashString.c in Sources */,
56155630
7B14089824878F950035403D /* SentryCrashStackEntryMapper.m in Sources */,
@@ -5643,6 +5658,7 @@
56435658
03F84D3327DD4191008FE43F /* SentryMachLogging.cpp in Sources */,
56445659
D85852BA27EDDC5900C6D8AE /* SentryUIApplication.m in Sources */,
56455660
FAB3599A2E05D8080083D5E3 /* SentryEventSwiftHelper.m in Sources */,
5661+
F41362112E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift in Sources */,
56465662
7B4E375F258231FC00059C93 /* SentryAttachment.m in Sources */,
56475663
636085141ED47BE600E8599E /* SentryFileManager.m in Sources */,
56485664
63FE710B20DA4C1000CDBAE8 /* SentryCrashMach.c in Sources */,

Sources/Sentry/SentryScope.m

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@
1919

2020
@interface SentryScope ()
2121

22-
/**
23-
* Set global tags -> these will be sent with every event
24-
*/
25-
@property (atomic, strong) NSMutableDictionary<NSString *, NSString *> *tagDictionary;
26-
2722
/**
2823
* Set global extra -> these will be sent with every event
2924
*/

Sources/Sentry/SentryWatchdogTerminationScopeObserver.m

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ - (void)setLevel:(enum SentryLevel)level
7979

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

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

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

9597
@end

Sources/Sentry/SentryWatchdogTerminationTracker.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ - (void)start
6060
event.user = [self.scopePersistentStore readPreviousUserFromDisk];
6161
event.dist = [self.scopePersistentStore readPreviousDistFromDisk];
6262
event.environment = [self.scopePersistentStore readPreviousEnvironmentFromDisk];
63+
event.tags = [self.scopePersistentStore readPreviousTagsFromDisk];
6364

6465
SentryException *exception =
6566
[[SentryException alloc] initWithValue:SentryWatchdogTerminationExceptionValue

Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# import <SentryHub.h>
1313
# import <SentryNSProcessInfoWrapper.h>
1414
# import <SentryOptions+Private.h>
15+
# import <SentryPropagationContext.h>
1516
# import <SentrySDK+Private.h>
1617
# import <SentrySwift.h>
1718
# import <SentryWatchdogTerminationBreadcrumbProcessor.h>
@@ -99,6 +100,9 @@ - (BOOL)installWithOptions:(SentryOptions *)options
99100
[scopeObserver setUser:outerScope.userObject];
100101
[scopeObserver setEnvironment:outerScope.environmentString];
101102
[scopeObserver setDist:outerScope.distString];
103+
[scopeObserver setTags:outerScope.tags];
104+
// We intentionally skip calling `setTraceContext:` since traces are not stored for watchdog
105+
// termination events
102106
}];
103107

104108
return YES;

Sources/Sentry/include/SentryScope+Private.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ NS_ASSUME_NONNULL_BEGIN
4040
@property (atomic, strong)
4141
NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *contextDictionary;
4242

43+
/**
44+
* Set global tags -> these will be sent with every event
45+
*/
46+
@property (atomic, strong) NSMutableDictionary<NSString *, NSString *> *tagDictionary;
47+
4348
- (void)addObserver:(id<SentryScopeObserver>)observer;
4449

4550
- (nullable SentryEvent *)applyToEvent:(SentryEvent *)event

Sources/Swift/Integrations/WatchdogTerminations/Processors/SentryWatchdogTerminationAttributesProcessor.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ import Foundation
4848
}
4949
}
5050

51+
public func setTags(_ tags: [String: String]?) {
52+
setData(data: tags, field: .tags) { [weak self] data in
53+
self?.scopePersistentStore.writeTagsToDisk(tags: data)
54+
}
55+
}
56+
5157
// MARK: - Private
5258
private func setData<T>(data: T?, field: SentryScopeField, save: @escaping (T) -> Void) {
5359
SentrySDKLog.debug("Setting \(field.name) in background queue: \(String(describing: data))")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@_implementationOnly import _SentryPrivate
2+
3+
extension SentryScopePersistentStore {
4+
func encode(context: [String: [String: Any]]) -> Data? {
5+
return encode(context, "context", true)
6+
}
7+
8+
func decodeContext(from data: Data) -> [String: [String: Any]]? {
9+
return decode(from: data, "context")
10+
}
11+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
@_implementationOnly import _SentryPrivate
2+
3+
extension SentryScopePersistentStore {
4+
func encode<T>(_ genericModel: T, _ name: String, _ sanitize: Bool = false) -> Data? {
5+
var model: Any = genericModel
6+
if sanitize {
7+
// We need to check if the context is a valid JSON object before encoding it.
8+
// Otherwise it will throw an unhandled `NSInvalidArgumentException` exception.
9+
// The error handler is required but will never be executed due to Swift type safety.
10+
guard let wrapped = genericModel as? [AnyHashable: Any],
11+
let sanitizedModel = sentry_sanitize(wrapped) else {
12+
SentrySDKLog.error("Failed to sanitize \(name), reason: \(name) is not valid json: \(genericModel)")
13+
return nil
14+
}
15+
model = sanitizedModel
16+
}
17+
18+
guard let data = SentrySerialization.data(withJSONObject: model) else {
19+
SentrySDKLog.error("Failed to serialize \(name), reason: \(name) is not valid json: \(genericModel)")
20+
return nil
21+
}
22+
23+
return data
24+
}
25+
26+
func decode<T>(from data: Data, _ name: String) -> [String: T]? {
27+
guard let deserialized = SentrySerialization.deserializeDictionary(fromJsonData: data) else {
28+
SentrySDKLog.error("Failed to deserialize \(name), reason: data is not valid json")
29+
return nil
30+
}
31+
32+
// `SentrySerialization` is a wrapper around `NSJSONSerialization` which returns any type of data (`id`).
33+
// It is the casted to a `NSDictionary`, which is then casted to a `[AnyHashable: Any]` in Swift.
34+
//
35+
// The responsibility of validating and casting the deserialized data from any data to a dictionary is delegated
36+
// to the `SentrySerialization` class.
37+
//
38+
// As this decode Tags method specifically returns a dictionary of strings, we need to ensure that
39+
// each value is a string.
40+
//
41+
// If the deserialized value is not a string, something clearly went wrong and we should discard the data.
42+
43+
// Iterate through the deserialized dictionary and check if the type is a dictionary.
44+
// When all values are strings, we can safely cast it to `T` without allocating
45+
// additional memory (like when mapping values).
46+
for (key, value) in deserialized {
47+
guard value is T else {
48+
SentrySDKLog.error("Failed to deserialize \(name), reason: value for key \(key) is not a valid string")
49+
return nil
50+
}
51+
}
52+
53+
return deserialized as? [String: T]
54+
}
55+
}

0 commit comments

Comments
 (0)