diff --git a/CHANGELOG.md b/CHANGELOG.md
index 338dbe88e97..725f7231122 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## Unreleased
+
+### Features
+
+- Add attributes data to `SentryScope` (#6830)
+
## 9.0.0
This changelog lists every breaking change. For a high-level overview and upgrade guidance, see the [migration guide](https://docs.sentry.io/platforms/apple/migration/).
diff --git a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift
index 0316c08831c..9846e1ba4a1 100644
--- a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift
+++ b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift
@@ -196,6 +196,10 @@ public struct SentrySDKWrapper {
}
let data = Data("hello".utf8)
scope.addAttachment(Attachment(data: data, filename: "log.txt"))
+
+ scope.setAttribute(value: "\(Bundle.main.bundleIdentifier ?? "")-custom-attribute", key: "custom-attribute-text")
+ scope.setAttribute(value: Date().timeIntervalSince1970, key: "custom-attribute-numeric")
+ scope.setAttribute(value: true, key: "custom-attribute-boolean")
return scope
}
diff --git a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard
index 884e2330df8..e123514827f 100644
--- a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard
+++ b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard
@@ -1256,6 +1256,16 @@
+
@@ -1830,6 +1840,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/iOS-Swift/iOS-Swift/ViewControllers/ScopeViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewControllers/ScopeViewController.swift
new file mode 100644
index 00000000000..721c78831c1
--- /dev/null
+++ b/Samples/iOS-Swift/iOS-Swift/ViewControllers/ScopeViewController.swift
@@ -0,0 +1,61 @@
+import Sentry
+import UIKit
+
+class ScopeViewController: UIViewController {
+
+ @IBOutlet var attributesTextView: UITextView!
+ @IBOutlet var attributeNameField: UITextField!
+ @IBOutlet var attributeValueField: UITextField!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ updateAttributesTextView()
+ }
+
+ @IBAction func setAttribute(_ sender: Any?) {
+ guard let attributeName = attributeNameField.text, let attributeValue = attributeValueField.text else {
+ return
+ }
+
+ SentrySDK.configureScope { scope in
+ scope.setAttribute(value: attributeValue, key: attributeName)
+ }
+
+ updateAttributesTextView()
+ }
+
+ @IBAction func removeAttribute(_ sender: Any?) {
+ guard let attributeName = attributeNameField.text else {
+ return
+ }
+
+ SentrySDK.configureScope { scope in
+ scope.removeAttribute(key: attributeName)
+ }
+
+ updateAttributesTextView()
+ }
+
+ @IBAction func updateAttributesTextView(_ sender: Any?) {
+ updateAttributesTextView()
+ }
+
+ private func updateAttributesTextView() {
+ SentrySDK.configureScope { [weak self] scope in
+ guard let self else { return }
+
+ guard let jsonData = try? JSONSerialization.data(withJSONObject: scope.attributes, options: [.prettyPrinted]) else {
+ self.attributesTextView.text = "Error serializing attributes to JSON"
+ return
+ }
+
+ guard let jsonString = String(data: jsonData, encoding: .utf8) else {
+ self.attributesTextView.text = "Error converting data to JSON text"
+ return
+ }
+
+ self.attributesTextView.text = jsonString
+ }
+ }
+}
diff --git a/Sources/Sentry/Public/SentryScope.h b/Sources/Sentry/Public/SentryScope.h
index 6017ee5b1ee..7e304ab478f 100644
--- a/Sources/Sentry/Public/SentryScope.h
+++ b/Sources/Sentry/Public/SentryScope.h
@@ -41,6 +41,11 @@ NS_SWIFT_NAME(Scope)
*/
@property (nonatomic, readonly, copy) NSDictionary *tags;
+/**
+ * Gets the dictionary of currently set attributes.
+ */
+@property (nonatomic, readonly, copy) NSDictionary *attributes;
+
- (instancetype)initWithMaxBreadcrumbs:(NSInteger)maxBreadcrumbs NS_DESIGNATED_INITIALIZER;
- (instancetype)init;
- (instancetype)initWithScope:(SentryScope *)scope;
@@ -137,6 +142,24 @@ NS_SWIFT_NAME(Scope)
*/
- (void)addAttachment:(SentryAttachment *)attachment NS_SWIFT_NAME(addAttachment(_:));
+/**
+ * Set global attributes. Attributes are searchable key/value string pairs attached to every log
+ * message.
+ * @note The SDK only applies attributes to Logs. The SDK doesn't apply the attributes to
+ * Events, Transactions, Spans, Profiles, Session Replay.
+ * @param value Supported values are string, integers, boolean and double
+ * @param key The key to store, cannot be an empty string
+ */
+- (void)setAttributeValue:(id)value forKey:(NSString *)key NS_SWIFT_NAME(setAttribute(value:key:));
+
+/**
+ * Remove the attribute for the specified key.
+ * @note The SDK only applies attributes to Logs. The SDK doesn't apply the attributes to
+ * Events, Transactions, Spans, Profiles, Session Replay.
+ * @param key The key to remove
+ */
+- (void)removeAttributeForKey:(NSString *)key NS_SWIFT_NAME(removeAttribute(key:));
+
/**
* Clears all attachments in the scope.
*/
diff --git a/Sources/Sentry/SentryCrashIntegration.m b/Sources/Sentry/SentryCrashIntegration.m
index b58332ecd56..5c8288a18db 100644
--- a/Sources/Sentry/SentryCrashIntegration.m
+++ b/Sources/Sentry/SentryCrashIntegration.m
@@ -239,6 +239,10 @@ - (void)configureScope
userInfo[@"release"] = self.options.releaseName;
userInfo[@"dist"] = self.options.dist;
+ // Crashes don't use the attributes field, we remove them to avoid uploading them
+ // unnecessarily.
+ [userInfo removeObjectForKey:@"attributes"];
+
[SentryDependencyContainer.sharedInstance.crashReporter setUserInfo:userInfo];
[outerScope addObserver:self.scopeObserver];
diff --git a/Sources/Sentry/SentryCrashScopeObserver.m b/Sources/Sentry/SentryCrashScopeObserver.m
index b226b044763..6363aad9862 100644
--- a/Sources/Sentry/SentryCrashScopeObserver.m
+++ b/Sources/Sentry/SentryCrashScopeObserver.m
@@ -91,6 +91,11 @@ - (void)setLevel:(enum SentryLevel)level
sentrycrash_scopesync_setLevel([json bytes]);
}
+- (void)setAttributes:(nullable NSDictionary *)attributes
+{
+ // Nothing to do here, crash events don't support attributes
+}
+
- (void)addSerializedBreadcrumb:(NSDictionary *)crumb
{
NSData *json = [self toJSONEncodedCString:crumb];
diff --git a/Sources/Sentry/SentryScope.m b/Sources/Sentry/SentryScope.m
index e1bc15dbdb3..1afaa67e7c8 100644
--- a/Sources/Sentry/SentryScope.m
+++ b/Sources/Sentry/SentryScope.m
@@ -29,6 +29,8 @@ @interface SentryScope ()
@property (atomic, strong) NSMutableArray *breadcrumbArray;
+@property (atomic, strong) NSMutableDictionary *attributesDictionary;
+
@end
@implementation SentryScope {
@@ -50,6 +52,7 @@ - (instancetype)initWithMaxBreadcrumbs:(NSInteger)maxBreadcrumbs
self.contextDictionary = [NSMutableDictionary new];
self.attachmentArray = [NSMutableArray new];
self.fingerprintArray = [NSMutableArray new];
+ self.attributesDictionary = [NSMutableDictionary new];
_spanLock = [[NSObject alloc] init];
self.observers = [NSMutableArray new];
self.propagationContext = [[SentryPropagationContext alloc] init];
@@ -74,6 +77,7 @@ - (instancetype)initWithScope:(SentryScope *)scope
[_breadcrumbArray addObjectsFromArray:crumbs];
[_fingerprintArray addObjectsFromArray:[scope fingerprints]];
[_attachmentArray addObjectsFromArray:[scope attachments]];
+ [_attributesDictionary addEntriesFromDictionary:[scope attributes]];
self.propagationContext = scope.propagationContext;
self.maxBreadcrumbs = scope.maxBreadcrumbs;
@@ -191,6 +195,9 @@ - (void)clear
@synchronized(_spanLock) {
_span = nil;
}
+ @synchronized(_attributesDictionary) {
+ [_attributesDictionary removeAllObjects];
+ }
self.userObject = nil;
self.distString = nil;
@@ -467,6 +474,40 @@ - (void)clearAttachments
}
}
+- (NSDictionary *)attributes
+{
+ @synchronized(_attributesDictionary) {
+ return _attributesDictionary.copy;
+ }
+}
+
+- (void)setAttributeValue:(id)value forKey:(NSString *)key
+{
+ if (key == nil || key.length == 0) {
+ SENTRY_LOG_ERROR(@"Attribute's key cannot be nil nor empty");
+ return;
+ }
+
+ @synchronized(_attributesDictionary) {
+ _attributesDictionary[key] = value;
+
+ for (id observer in self.observers) {
+ [observer setAttributes:_attributesDictionary];
+ }
+ }
+}
+
+- (void)removeAttributeForKey:(NSString *)key
+{
+ @synchronized(_attributesDictionary) {
+ [_attributesDictionary removeObjectForKey:key];
+
+ for (id observer in self.observers) {
+ [observer setAttributes:_attributesDictionary];
+ }
+ }
+}
+
- (NSDictionary *)serialize
{
NSMutableDictionary *serializedData = [NSMutableDictionary new];
@@ -509,6 +550,9 @@ - (void)clearAttachments
if (crumbs.count > 0) {
[serializedData setValue:crumbs forKey:@"breadcrumbs"];
}
+ if (self.attributes.count > 0) {
+ [serializedData setValue:[self attributes] forKey:@"attributes"];
+ }
return serializedData;
}
diff --git a/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationScopeObserver.swift b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationScopeObserver.swift
index 349516abfdf..8a97dd31f76 100644
--- a/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationScopeObserver.swift
+++ b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationScopeObserver.swift
@@ -54,6 +54,10 @@ class SentryWatchdogTerminationScopeObserver: NSObject, SentryScopeObserver {
// Nothing to do here, watchdog termination events are always Fatal
}
+ func setAttributes(_ attributes: [String: Any]?) {
+ // Nothing to do here, watchdog termination events don't support attributes
+ }
+
func addSerializedBreadcrumb(_ serializedBreadcrumb: [String: Any]) {
breadcrumbProcessor.addSerializedBreadcrumb(serializedBreadcrumb)
}
diff --git a/Sources/Swift/State/SentryScopeObserver.swift b/Sources/Swift/State/SentryScopeObserver.swift
index 83fae2e3d0e..afa77ac9885 100644
--- a/Sources/Swift/State/SentryScopeObserver.swift
+++ b/Sources/Swift/State/SentryScopeObserver.swift
@@ -8,6 +8,7 @@
func setEnvironment(_ environment: String?)
func setFingerprint(_ fingerprint: [String]?)
func setLevel(_ level: SentryLevel)
+ func setAttributes(_ attributes: [String: Any]?)
func addSerializedBreadcrumb(_ serializedBreadcrumb: [String: Any])
func clearBreadcrumbs()
func clear()
diff --git a/Tests/SentryTests/Integrations/SentryCrash/SentryCrashIntegrationTests.swift b/Tests/SentryTests/Integrations/SentryCrash/SentryCrashIntegrationTests.swift
index 30fbf657c75..f0619b7e7d2 100644
--- a/Tests/SentryTests/Integrations/SentryCrash/SentryCrashIntegrationTests.swift
+++ b/Tests/SentryTests/Integrations/SentryCrash/SentryCrashIntegrationTests.swift
@@ -624,6 +624,32 @@ class SentryCrashIntegrationTests: NotificationCenterTestCase {
XCTAssertEqual("transaction", envelope.items.first?.header.type)
}
+ func testAttributesAreNotPassedToSentryCrash() throws {
+ // Start the SDK without any integration
+ SentrySDK.start { options in
+ options.dsn = SentryCrashIntegrationTests.dsnAsString
+ options.removeAllIntegrations()
+ }
+
+ // Configure some attributes
+ SentrySDK.configureScope { scope in
+ scope.setAttribute(value: "value", key: "key")
+ scope.setEnvironment("test-attributes")
+ }
+
+ // UserInfo is set when installing the integration, so let's manually install it again to use the new scope values
+ let sentryCrash = fixture.sentryCrash
+ let sut = SentryCrashIntegration(crashAdapter: sentryCrash, andDispatchQueueWrapper: fixture.dispatchQueueWrapper)
+
+ sut.install(with: Options())
+
+ let userInfo = try XCTUnwrap(SentryDependencyContainer.sharedInstance().crashReporter.userInfo)
+ // Double check the environment just set is in the user info
+ assertUserInfoField(userInfo: userInfo, key: "environment", expected: "test-attributes")
+ // Validate there is no attributes in the user info
+ XCTAssertNil(userInfo["attributes"])
+ }
+
private func givenCurrentSession() -> SentrySession {
// serialize sets the timestamp
let session = SentrySession(jsonObject: fixture.session.serialize())!
diff --git a/Tests/SentryTests/SentryScope+Equality.m b/Tests/SentryTests/SentryScope+Equality.m
index bd2c40f9122..eb1ef9eb7e6 100644
--- a/Tests/SentryTests/SentryScope+Equality.m
+++ b/Tests/SentryTests/SentryScope+Equality.m
@@ -49,6 +49,9 @@ - (BOOL)isEqualToScope:(SentryScope *)scope
if (self.attachmentArray != scope.attachmentArray
&& ![self.attachmentArray isEqualToArray:scope.attachmentArray])
return NO;
+ if (self.attributes != scope.attributes
+ && ![self.attributes isEqualToDictionary:scope.attributes])
+ return NO;
return YES;
}
@@ -65,6 +68,7 @@ - (NSUInteger)hash
hash = hash * 23 + (NSUInteger)self.levelEnum;
hash = hash * 23 + self.maxBreadcrumbs;
hash = hash * 23 + [self.attachmentArray hash];
+ hash = hash * 23 + [self.attributes hash];
return hash;
}
diff --git a/Tests/SentryTests/SentryScopeSwiftTests.swift b/Tests/SentryTests/SentryScopeSwiftTests.swift
index 6e07570fa4f..e595b2cd7c7 100644
--- a/Tests/SentryTests/SentryScopeSwiftTests.swift
+++ b/Tests/SentryTests/SentryScopeSwiftTests.swift
@@ -1,3 +1,4 @@
+// swiftlint:disable file_length
@_spi(Private) import Sentry
import SentryTestUtils
import XCTest
@@ -63,6 +64,8 @@ class SentryScopeSwiftTests: XCTestCase {
scope.addAttachment(TestData.fileAttachment)
+ scope.setAttribute(value: "my-value", key: "my-attribute-key")
+
event = Event()
event.message = SentryMessage(formatted: "message")
@@ -131,6 +134,7 @@ class SentryScopeSwiftTests: XCTestCase {
XCTAssertEqual(try XCTUnwrap(cloned.serialize() as? [String: AnyHashable]), snapshot)
XCTAssertEqual(scope.propagationContext.spanId, cloned.propagationContext.spanId)
XCTAssertEqual(scope.propagationContext.traceId, cloned.propagationContext.traceId)
+ XCTAssertEqual(scope.attributes as NSDictionary, cloned.attributes as NSDictionary)
let (event1, event2) = (Event(), Event())
(event1.timestamp, event2.timestamp) = (fixture.date, fixture.date)
@@ -358,6 +362,7 @@ class SentryScopeSwiftTests: XCTestCase {
let expected = Scope(maxBreadcrumbs: fixture.maxBreadcrumbs)
XCTAssertEqual(expected, scope)
XCTAssertEqual(0, scope.attachments.count)
+ XCTAssertEqual(0, scope.attributes.count)
}
func testAttachmentsIsACopy() {
@@ -562,6 +567,24 @@ class SentryScopeSwiftTests: XCTestCase {
XCTAssertEqual(level, observer.level)
}
+ func testScopeObserver_setAttributes() {
+ let sut = Scope()
+ let observer = fixture.observer
+ sut.add(observer)
+
+ sut.setAttribute(value: "my-attribute", key: "key-string")
+ sut.setAttribute(value: false, key: "key-bool")
+ sut.setAttribute(value: 1.5, key: "key-double")
+ sut.setAttribute(value: 4, key: "key-integer")
+
+ XCTAssertEqual([
+ "key-string": "my-attribute",
+ "key-bool": false,
+ "key-double": 1.5,
+ "key-integer": 4
+ ] as [String: AnyHashable], try XCTUnwrap(sut.attributes as? [String: AnyHashable]))
+ }
+
func testScopeObserver_addBreadcrumb() {
let sut = Scope()
let observer = fixture.observer
@@ -856,6 +879,71 @@ class SentryScopeSwiftTests: XCTestCase {
// -- Assert --
XCTAssertNil(actualSpan)
}
+
+ func testSetStringAttribute() {
+ let scope = Scope()
+
+ scope.setAttribute(value: "test-string", key: "a-string-key")
+
+ XCTAssertEqual(try XCTUnwrap(scope.attributes["a-string-key"] as? String), "test-string")
+ }
+
+ func testSetStringAttributeAgainChangesValue() {
+ let scope = Scope()
+
+ scope.setAttribute(value: "test-string", key: "a-string-key")
+
+ XCTAssertEqual(try XCTUnwrap(scope.attributes["a-string-key"] as? String), "test-string")
+
+ scope.setAttribute(value: "another-string", key: "a-string-key")
+
+ XCTAssertEqual(try XCTUnwrap(scope.attributes["a-string-key"] as? String), "another-string")
+ }
+
+ func testSetBoolAttribute() {
+ let scope = Scope()
+
+ scope.setAttribute(value: true, key: "a-bool-key")
+ scope.setAttribute(value: false, key: "a-bool-key-false")
+
+ XCTAssertEqual(try XCTUnwrap(scope.attributes["a-bool-key-false"] as? Bool), false)
+ XCTAssertEqual(try XCTUnwrap(scope.attributes["a-bool-key"] as? Bool), true)
+ }
+
+ func testSetDoubleAttribute() {
+ let scope = Scope()
+
+ scope.setAttribute(value: 1.4728, key: "a-double-key")
+
+ XCTAssertEqual(try XCTUnwrap(scope.attributes["a-double-key"] as? Double), 1.4728)
+ }
+
+ func testSetIntegerAttribute() {
+ let scope = Scope()
+
+ scope.setAttribute(value: 4, key: "an-integer-key")
+
+ XCTAssertEqual(try XCTUnwrap(scope.attributes["an-integer-key"] as? Int), 4)
+ }
+
+ func testRemoveAttribute() {
+ let scope = Scope()
+
+ scope.setAttribute(value: "test-string", key: "a-key")
+
+ scope.removeAttribute(key: "a-key")
+
+ XCTAssertNil(scope.attributes["a-key"])
+ }
+
+ func testRemoveNotExistingAttributeDoesNotCrash() {
+ let scope = Scope()
+
+ // This should not crash
+ scope.removeAttribute(key: "an-invalid-key")
+
+ XCTAssertTrue(scope.attributes.isEmpty)
+ }
private class TestScopeObserver: NSObject, SentryScopeObserver {
var tags: [String: String]?
@@ -920,6 +1008,11 @@ class SentryScopeSwiftTests: XCTestCase {
func setUser(_ user: User?) {
self.user = user
}
+
+ var attributes: [String: Any]?
+ func setAttributes(_ attributes: [String: Any]?) {
+ self.attributes = attributes
+ }
}
}
@@ -965,3 +1058,4 @@ private final class NotOfTypeSpan: NSObject, Span {
}
private final class SubClassOfSentrySpan: SentrySpan {}
+// swiftlint:enable file_length
diff --git a/sdk_api.json b/sdk_api.json
index 32385e9f07a..8bcb2cfc873 100644
--- a/sdk_api.json
+++ b/sdk_api.json
@@ -15140,6 +15140,80 @@
}
]
},
+ {
+ "kind": "Var",
+ "name": "attributes",
+ "printedName": "attributes",
+ "children": [
+ {
+ "kind": "TypeNominal",
+ "name": "Dictionary",
+ "printedName": "[Swift.String : Any]",
+ "children": [
+ {
+ "kind": "TypeNominal",
+ "name": "String",
+ "printedName": "Swift.String",
+ "usr": "s:SS"
+ },
+ {
+ "kind": "TypeNominal",
+ "name": "ProtocolComposition",
+ "printedName": "Any"
+ }
+ ],
+ "usr": "s:SD"
+ }
+ ],
+ "declKind": "Var",
+ "usr": "c:objc(cs)SentryScope(py)attributes",
+ "moduleName": "Sentry",
+ "isOpen": true,
+ "objc_name": "attributes",
+ "declAttributes": [
+ "ObjC",
+ "Dynamic"
+ ],
+ "accessors": [
+ {
+ "kind": "Accessor",
+ "name": "Get",
+ "printedName": "Get()",
+ "children": [
+ {
+ "kind": "TypeNominal",
+ "name": "Dictionary",
+ "printedName": "[Swift.String : Any]",
+ "children": [
+ {
+ "kind": "TypeNominal",
+ "name": "String",
+ "printedName": "Swift.String",
+ "usr": "s:SS"
+ },
+ {
+ "kind": "TypeNominal",
+ "name": "ProtocolComposition",
+ "printedName": "Any"
+ }
+ ],
+ "usr": "s:SD"
+ }
+ ],
+ "declKind": "Accessor",
+ "usr": "c:objc(cs)SentryScope(im)attributes",
+ "moduleName": "Sentry",
+ "isOpen": true,
+ "objc_name": "attributes",
+ "declAttributes": [
+ "DiscardableResult",
+ "ObjC",
+ "Dynamic"
+ ],
+ "accessorKind": "get"
+ }
+ ]
+ },
{
"kind": "Constructor",
"name": "init",
@@ -15933,6 +16007,81 @@
],
"funcSelfKind": "NonMutating"
},
+ {
+ "kind": "Function",
+ "name": "setAttribute",
+ "printedName": "setAttribute(value:key:)",
+ "children": [
+ {
+ "kind": "TypeNameAlias",
+ "name": "Void",
+ "printedName": "Swift.Void",
+ "children": [
+ {
+ "kind": "TypeNominal",
+ "name": "Void",
+ "printedName": "()"
+ }
+ ]
+ },
+ {
+ "kind": "TypeNominal",
+ "name": "ProtocolComposition",
+ "printedName": "Any"
+ },
+ {
+ "kind": "TypeNominal",
+ "name": "String",
+ "printedName": "Swift.String",
+ "usr": "s:SS"
+ }
+ ],
+ "declKind": "Func",
+ "usr": "c:objc(cs)SentryScope(im)setAttributeValue:forKey:",
+ "moduleName": "Sentry",
+ "isOpen": true,
+ "objc_name": "setAttributeValue:forKey:",
+ "declAttributes": [
+ "ObjC",
+ "Dynamic"
+ ],
+ "funcSelfKind": "NonMutating"
+ },
+ {
+ "kind": "Function",
+ "name": "removeAttribute",
+ "printedName": "removeAttribute(key:)",
+ "children": [
+ {
+ "kind": "TypeNameAlias",
+ "name": "Void",
+ "printedName": "Swift.Void",
+ "children": [
+ {
+ "kind": "TypeNominal",
+ "name": "Void",
+ "printedName": "()"
+ }
+ ]
+ },
+ {
+ "kind": "TypeNominal",
+ "name": "String",
+ "printedName": "Swift.String",
+ "usr": "s:SS"
+ }
+ ],
+ "declKind": "Func",
+ "usr": "c:objc(cs)SentryScope(im)removeAttributeForKey:",
+ "moduleName": "Sentry",
+ "isOpen": true,
+ "objc_name": "removeAttributeForKey:",
+ "declAttributes": [
+ "ObjC",
+ "Dynamic"
+ ],
+ "funcSelfKind": "NonMutating"
+ },
{
"kind": "Function",
"name": "clearAttachments",