Skip to content

Commit 303d533

Browse files
committed
feat(metrics): Add implementation for metrics envelope item
1 parent 1210307 commit 303d533

File tree

18 files changed

+2079
-8
lines changed

18 files changed

+2079
-8
lines changed

Sentry.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,8 @@
784784
D468C0622D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */; };
785785
D46B041D2EDF168400AF4A0A /* MetricsIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B041C2EDF167D00AF4A0A /* MetricsIntegration.swift */; };
786786
D46B04202EDF175C00AF4A0A /* MetricsIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B041F2EDF175600AF4A0A /* MetricsIntegrationTests.swift */; };
787+
D46B04482EDF25E100AF4A0A /* SentryMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B04472EDF25E100AF4A0A /* SentryMetric.swift */; };
788+
D46B044F2EDF260A00AF4A0A /* SentryMetricBatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B044E2EDF260A00AF4A0A /* SentryMetricBatcher.swift */; };
787789
D473ACD72D8090FC000F1CC6 /* FileManager+SentryTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D473ACD62D8090FC000F1CC6 /* FileManager+SentryTracing.swift */; };
788790
D480F9D92DE47A50009A0594 /* TestSentryScopePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D480F9D82DE47A48009A0594 /* TestSentryScopePersistentStore.swift */; };
789791
D480F9DB2DE47AF2009A0594 /* SentryScopePersistentStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D480F9DA2DE47AEB009A0594 /* SentryScopePersistentStoreTests.swift */; };
@@ -2147,6 +2149,8 @@
21472149
D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryFileIOTracker+SwiftHelpers.swift"; sourceTree = "<group>"; };
21482150
D46B041C2EDF167D00AF4A0A /* MetricsIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsIntegration.swift; sourceTree = "<group>"; };
21492151
D46B041F2EDF175600AF4A0A /* MetricsIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsIntegrationTests.swift; sourceTree = "<group>"; };
2152+
D46B04472EDF25E100AF4A0A /* SentryMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetric.swift; sourceTree = "<group>"; };
2153+
D46B044E2EDF260A00AF4A0A /* SentryMetricBatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetricBatcher.swift; sourceTree = "<group>"; };
21502154
D46D45E12D5F3FD600A1CB35 /* Sentry_Base.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Sentry_Base.xctestplan; sourceTree = "<group>"; };
21512155
D46D45E92D5F411700A1CB35 /* SentrySwiftUI_Base.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = SentrySwiftUI_Base.xctestplan; path = Plans/SentrySwiftUI_Base.xctestplan; sourceTree = SOURCE_ROOT; };
21522156
D473ACD62D8090FC000F1CC6 /* FileManager+SentryTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+SentryTracing.swift"; sourceTree = "<group>"; };
@@ -4645,6 +4649,7 @@
46454649
FA01BCB12E69352A00968DFA /* SentryDiscardedEvent.swift */,
46464650
92235CAD2E15549C00865983 /* SentryLogger.swift */,
46474651
92235CAB2E15369900865983 /* SentryLogBatcher.swift */,
4652+
D46B044E2EDF260A00AF4A0A /* SentryMetricBatcher.swift */,
46484653
F451FAA52E0B304E0050ACF2 /* LoadValidator.swift */,
46494654
FA34C1A22E692A5000BC52AA /* SentryEnvelopeItem.swift */,
46504655
FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */,
@@ -4870,6 +4875,7 @@
48704875
92ECD73F2E05AD500063EC10 /* SentryLogAttribute.swift */,
48714876
92ECD73D2E05AD2B0063EC10 /* SentryLogLevel.swift */,
48724877
9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */,
4878+
D46B04472EDF25E100AF4A0A /* SentryMetric.swift */,
48734879
F458D1122E180BB00028273E /* SentryFileManagerProtocol.swift */,
48744880
);
48754881
path = Protocol;
@@ -5803,8 +5809,10 @@
58035809
D8ACE3C82762187200F5A213 /* SentryFileIOTrackerHelper.m in Sources */,
58045810
D8B088B729C9E3FF00213258 /* SentryTracerConfiguration.m in Sources */,
58055811
FA7206E12E0B37C80072FDD4 /* SentryProfileCollector.mm in Sources */,
5812+
D46B044F2EDF260A00AF4A0A /* SentryMetricBatcher.swift in Sources */,
58065813
9264E1EB2E2E385E00B077CF /* SentryLogMessage.swift in Sources */,
58075814
8ECC674A25C23A20000E2BF6 /* SentryTransactionContext.m in Sources */,
5815+
D46B04482EDF25E100AF4A0A /* SentryMetric.swift in Sources */,
58085816
03BCC38C27E1C01A003232C7 /* SentryTime.mm in Sources */,
58095817
A8F17B342902870300990B25 /* SentryHttpStatusCodeRange.m in Sources */,
58105818
62C97D3A2CC64E6B00DDA204 /* SentryUncaughtNSExceptions.m in Sources */,

SentryTestUtils/Sources/TestClient.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,10 @@ public class TestClient: SentryClientInternal {
167167
captureLogInvocations.record((castLog, scope))
168168
}
169169
}
170+
171+
@_spi(Private) public var captureMetricsDataInvocations = Invocations<(data: NSData, count: NSNumber)>()
172+
@_spi(Private) public override func captureMetricsData(_ data: NSData, with itemCount: NSNumber) {
173+
captureMetricsDataInvocations.record((data, itemCount))
174+
super.captureMetricsData(data, with: itemCount)
175+
}
170176
}

Sources/Sentry/SentryClient.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,18 @@ - (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount
11171117
[self captureEnvelope:envelope];
11181118
}
11191119

1120+
- (void)captureMetricsData:(NSData *)data with:(NSNumber *)itemCount
1121+
{
1122+
SentryEnvelopeItem *envelopeItem =
1123+
[[SentryEnvelopeItem alloc] initWithType:SentryEnvelopeItemTypes.traceMetric
1124+
data:data
1125+
contentType:@"application/vnd.sentry.items.trace-metric+json"
1126+
itemCount:itemCount];
1127+
SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:[SentryEnvelopeHeader empty]
1128+
singleItem:envelopeItem];
1129+
[self captureEnvelope:envelope];
1130+
}
1131+
11201132
@end
11211133

11221134
NS_ASSUME_NONNULL_END

Sources/Sentry/SentryDataCategoryMapper.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
NSString *const kSentryDataCategoryNameSpan = @"span";
1919
NSString *const kSentryDataCategoryNameFeedback = @"feedback";
2020
NSString *const kSentryDataCategoryNameLogItem = @"log_item";
21+
NSString *const kSentryDataCategoryNameTraceMetric = @"trace_metric";
2122
NSString *const kSentryDataCategoryNameUnknown = @"unknown";
2223

2324
NS_ASSUME_NONNULL_BEGIN
@@ -57,6 +58,9 @@
5758
if ([itemType isEqualToString:SentryEnvelopeItemTypes.log]) {
5859
return kSentryDataCategoryLogItem;
5960
}
61+
if ([itemType isEqualToString:SentryEnvelopeItemTypes.traceMetric]) {
62+
return kSentryDataCategoryTraceMetric;
63+
}
6064

6165
return kSentryDataCategoryDefault;
6266
}
@@ -113,6 +117,9 @@
113117
if ([value isEqualToString:kSentryDataCategoryNameLogItem]) {
114118
return kSentryDataCategoryLogItem;
115119
}
120+
if ([value isEqualToString:kSentryDataCategoryNameTraceMetric]) {
121+
return kSentryDataCategoryTraceMetric;
122+
}
116123

117124
return kSentryDataCategoryUnknown;
118125
}
@@ -148,6 +155,8 @@
148155
return kSentryDataCategoryNameFeedback;
149156
case kSentryDataCategoryLogItem:
150157
return kSentryDataCategoryNameLogItem;
158+
case kSentryDataCategoryTraceMetric:
159+
return kSentryDataCategoryNameTraceMetric;
151160

152161
default: // !!!: fall-through!
153162
case kSentryDataCategoryUnknown:

Sources/Sentry/SentryOptionsInternal.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ + (BOOL)validateOptions:(NSDictionary<NSString *, id> *)options
120120
sentryOptions.beforeSendLog = options[@"beforeSendLog"];
121121
}
122122

123+
if ([self isBlock:options[@"beforeSendMetric"]]) {
124+
sentryOptions.beforeSendMetric = options[@"beforeSendMetric"];
125+
}
126+
123127
if ([self isBlock:options[@"beforeSendSpan"]]) {
124128
sentryOptions.beforeSendSpan = options[@"beforeSendSpan"];
125129
}

Sources/Sentry/include/SentryClient+Private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ NS_ASSUME_NONNULL_BEGIN
8282

8383
- (void)_swiftCaptureLog:(NSObject *)log withScope:(SentryScope *)scope;
8484

85+
- (void)captureMetricsData:(NSData *)data with:(NSNumber *)itemCount;
86+
8587
@end
8688

8789
NS_ASSUME_NONNULL_END

Sources/Sentry/include/SentryDataCategory.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ typedef NS_ENUM(NSUInteger, SentryDataCategory) {
2020
kSentryDataCategorySpan = 11,
2121
kSentryDataCategoryFeedback = 12,
2222
kSentryDataCategoryLogItem = 13,
23-
kSentryDataCategoryUnknown = 14,
23+
kSentryDataCategoryTraceMetric = 14,
24+
kSentryDataCategoryUnknown = 15,
2425
};

Sources/Swift/Helper/SentryEnvelopeItemType.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414
public static let statsd = "statsd"
1515
public static let profileChunk = "profile_chunk"
1616
public static let log = "log"
17+
public static let traceMetric = "trace_metric"
1718
}

Sources/Swift/Helper/SentrySDK.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ import Foundation
3737
return SentryLogger(dateProvider: SentryDependencyContainer.sharedInstance().dateProvider)
3838
}
3939
}
40-
40+
41+
/// API to collect metrics
42+
@objc public static var metrics = MetricsApi()
43+
4144
/// Inits and configures Sentry (`SentryHub`, `SentryClient`) and sets up all integrations. Make sure to
4245
/// set a valid DSN.
4346
/// - note: Call this method on the main thread. When calling it from a background thread, the
Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,58 @@
1-
final class MetricsIntegration<Dependencies>: NSObject, SwiftIntegration {
1+
final class MetricsIntegration<Dependencies>: NSObject, SwiftIntegration, SentryMetricBatcherDelegate {
2+
private let options: Options
3+
private let metricBatcher: SentryMetricBatcher
4+
private let dispatchQueue: SentryDispatchQueueWrapper
5+
26
init?(with options: Options, dependencies: Dependencies) {
37
guard options.enableMetrics else { return nil }
4-
5-
SentrySDKLog.debug("Integration initialized")
8+
9+
guard let dispatchQueueWrapper = dependencies.dispatchQueueWrapper else {
10+
SentrySDKLog.error("MetricsIntegration: dispatchQueueWrapper not available in dependencies")
11+
return nil
12+
}
13+
14+
self.options = options
15+
self.dispatchQueue = dispatchQueueWrapper
16+
17+
super.init()
18+
19+
// Create the batcher with self as delegate after initialization
20+
self.metricBatcher = SentryMetricBatcher(
21+
options: options,
22+
dispatchQueue: dispatchQueue,
23+
delegate: self
24+
)
25+
26+
SentrySDKLog.debug("MetricsIntegration initialized")
627
}
728

8-
func uninstall() {}
29+
func uninstall() {
30+
// Flush any pending metrics before uninstalling
31+
metricBatcher.captureMetrics()
32+
}
933

1034
static var name: String {
1135
"SentryMetricsIntegration"
1236
}
37+
38+
// MARK: - Public API for MetricsApi
39+
40+
func addMetric(_ metric: SentryMetric, scope: Scope) {
41+
metricBatcher.addMetric(metric, scope: scope)
42+
}
43+
44+
// MARK: - SentryMetricBatcherDelegate
45+
46+
@objc(captureMetricsData:with:)
47+
func capture(metricsData: NSData, count: NSNumber) {
48+
// Get the client from the current hub
49+
let hub = SentrySDKInternal.currentHub()
50+
guard let client = hub.getClient() else {
51+
SentrySDKLog.warn("MetricsIntegration: No client available, dropping metrics")
52+
return
53+
}
54+
55+
// Call the client's captureMetricsData method
56+
client.captureMetricsData(metricsData, with: count)
57+
}
1358
}

0 commit comments

Comments
 (0)