Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,8 @@
D468C0622D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */; };
D46B041D2EDF168400AF4A0A /* MetricsIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B041C2EDF167D00AF4A0A /* MetricsIntegration.swift */; };
D46B04202EDF175C00AF4A0A /* MetricsIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B041F2EDF175600AF4A0A /* MetricsIntegrationTests.swift */; };
D46B04482EDF25E100AF4A0A /* SentryMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B04472EDF25E100AF4A0A /* SentryMetric.swift */; };
D46B044F2EDF260A00AF4A0A /* SentryMetricBatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46B044E2EDF260A00AF4A0A /* SentryMetricBatcher.swift */; };
D473ACD72D8090FC000F1CC6 /* FileManager+SentryTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D473ACD62D8090FC000F1CC6 /* FileManager+SentryTracing.swift */; };
D480F9D92DE47A50009A0594 /* TestSentryScopePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D480F9D82DE47A48009A0594 /* TestSentryScopePersistentStore.swift */; };
D480F9DB2DE47AF2009A0594 /* SentryScopePersistentStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D480F9DA2DE47AEB009A0594 /* SentryScopePersistentStoreTests.swift */; };
Expand Down Expand Up @@ -2147,6 +2149,8 @@
D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryFileIOTracker+SwiftHelpers.swift"; sourceTree = "<group>"; };
D46B041C2EDF167D00AF4A0A /* MetricsIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsIntegration.swift; sourceTree = "<group>"; };
D46B041F2EDF175600AF4A0A /* MetricsIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsIntegrationTests.swift; sourceTree = "<group>"; };
D46B04472EDF25E100AF4A0A /* SentryMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetric.swift; sourceTree = "<group>"; };
D46B044E2EDF260A00AF4A0A /* SentryMetricBatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetricBatcher.swift; sourceTree = "<group>"; };
D46D45E12D5F3FD600A1CB35 /* Sentry_Base.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Sentry_Base.xctestplan; sourceTree = "<group>"; };
D46D45E92D5F411700A1CB35 /* SentrySwiftUI_Base.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = SentrySwiftUI_Base.xctestplan; path = Plans/SentrySwiftUI_Base.xctestplan; sourceTree = SOURCE_ROOT; };
D473ACD62D8090FC000F1CC6 /* FileManager+SentryTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+SentryTracing.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4645,6 +4649,7 @@
FA01BCB12E69352A00968DFA /* SentryDiscardedEvent.swift */,
92235CAD2E15549C00865983 /* SentryLogger.swift */,
92235CAB2E15369900865983 /* SentryLogBatcher.swift */,
D46B044E2EDF260A00AF4A0A /* SentryMetricBatcher.swift */,
F451FAA52E0B304E0050ACF2 /* LoadValidator.swift */,
FA34C1A22E692A5000BC52AA /* SentryEnvelopeItem.swift */,
FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */,
Expand Down Expand Up @@ -4870,6 +4875,7 @@
92ECD73F2E05AD500063EC10 /* SentryLogAttribute.swift */,
92ECD73D2E05AD2B0063EC10 /* SentryLogLevel.swift */,
9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */,
D46B04472EDF25E100AF4A0A /* SentryMetric.swift */,
F458D1122E180BB00028273E /* SentryFileManagerProtocol.swift */,
);
path = Protocol;
Expand Down Expand Up @@ -5803,8 +5809,10 @@
D8ACE3C82762187200F5A213 /* SentryFileIOTrackerHelper.m in Sources */,
D8B088B729C9E3FF00213258 /* SentryTracerConfiguration.m in Sources */,
FA7206E12E0B37C80072FDD4 /* SentryProfileCollector.mm in Sources */,
D46B044F2EDF260A00AF4A0A /* SentryMetricBatcher.swift in Sources */,
9264E1EB2E2E385E00B077CF /* SentryLogMessage.swift in Sources */,
8ECC674A25C23A20000E2BF6 /* SentryTransactionContext.m in Sources */,
D46B04482EDF25E100AF4A0A /* SentryMetric.swift in Sources */,
03BCC38C27E1C01A003232C7 /* SentryTime.mm in Sources */,
A8F17B342902870300990B25 /* SentryHttpStatusCodeRange.m in Sources */,
62C97D3A2CC64E6B00DDA204 /* SentryUncaughtNSExceptions.m in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions SentryTestUtils/Sources/TestClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,10 @@ public class TestClient: SentryClientInternal {
captureLogInvocations.record((castLog, scope))
}
}

@_spi(Private) public var captureMetricsDataInvocations = Invocations<(data: NSData, count: NSNumber)>()
@_spi(Private) public override func captureMetricsData(_ data: NSData, with itemCount: NSNumber) {
captureMetricsDataInvocations.record((data, itemCount))
super.captureMetricsData(data, with: itemCount)
}
}
12 changes: 12 additions & 0 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,18 @@ - (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount
[self captureEnvelope:envelope];
}

- (void)captureMetricsData:(NSData *)data with:(NSNumber *)itemCount
{
SentryEnvelopeItem *envelopeItem =
[[SentryEnvelopeItem alloc] initWithType:SentryEnvelopeItemTypes.traceMetric
data:data
contentType:@"application/vnd.sentry.items.trace-metric+json"
itemCount:itemCount];
SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:[SentryEnvelopeHeader empty]
singleItem:envelopeItem];
[self captureEnvelope:envelope];
}

@end

NS_ASSUME_NONNULL_END
9 changes: 9 additions & 0 deletions Sources/Sentry/SentryDataCategoryMapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
NSString *const kSentryDataCategoryNameSpan = @"span";
NSString *const kSentryDataCategoryNameFeedback = @"feedback";
NSString *const kSentryDataCategoryNameLogItem = @"log_item";
NSString *const kSentryDataCategoryNameTraceMetric = @"trace_metric";
NSString *const kSentryDataCategoryNameUnknown = @"unknown";

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -57,6 +58,9 @@
if ([itemType isEqualToString:SentryEnvelopeItemTypes.log]) {
return kSentryDataCategoryLogItem;
}
if ([itemType isEqualToString:SentryEnvelopeItemTypes.traceMetric]) {
return kSentryDataCategoryTraceMetric;
}

return kSentryDataCategoryDefault;
}
Expand Down Expand Up @@ -113,6 +117,9 @@
if ([value isEqualToString:kSentryDataCategoryNameLogItem]) {
return kSentryDataCategoryLogItem;
}
if ([value isEqualToString:kSentryDataCategoryNameTraceMetric]) {
return kSentryDataCategoryTraceMetric;
}

return kSentryDataCategoryUnknown;
}
Expand Down Expand Up @@ -148,6 +155,8 @@
return kSentryDataCategoryNameFeedback;
case kSentryDataCategoryLogItem:
return kSentryDataCategoryNameLogItem;
case kSentryDataCategoryTraceMetric:
return kSentryDataCategoryNameTraceMetric;

default: // !!!: fall-through!
case kSentryDataCategoryUnknown:
Expand Down
4 changes: 4 additions & 0 deletions Sources/Sentry/SentryOptionsInternal.m
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ + (BOOL)validateOptions:(NSDictionary<NSString *, id> *)options
sentryOptions.beforeSendLog = options[@"beforeSendLog"];
}

if ([self isBlock:options[@"beforeSendMetric"]]) {
sentryOptions.beforeSendMetric = options[@"beforeSendMetric"];
}

if ([self isBlock:options[@"beforeSendSpan"]]) {
sentryOptions.beforeSendSpan = options[@"beforeSendSpan"];
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Sentry/include/SentryClient+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ NS_ASSUME_NONNULL_BEGIN

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

- (void)captureMetricsData:(NSData *)data with:(NSNumber *)itemCount;

@end

NS_ASSUME_NONNULL_END
3 changes: 2 additions & 1 deletion Sources/Sentry/include/SentryDataCategory.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ typedef NS_ENUM(NSUInteger, SentryDataCategory) {
kSentryDataCategorySpan = 11,
kSentryDataCategoryFeedback = 12,
kSentryDataCategoryLogItem = 13,
kSentryDataCategoryUnknown = 14,
kSentryDataCategoryTraceMetric = 14,
kSentryDataCategoryUnknown = 15,
};
1 change: 1 addition & 0 deletions Sources/Swift/Helper/SentryEnvelopeItemType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
public static let statsd = "statsd"
public static let profileChunk = "profile_chunk"
public static let log = "log"
public static let traceMetric = "trace_metric"
}
5 changes: 4 additions & 1 deletion Sources/Swift/Helper/SentrySDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@
return SentryLogger(dateProvider: SentryDependencyContainer.sharedInstance().dateProvider)
}
}


/// API to collect metrics
@objc public static var metrics = MetricsApi()

Check failure on line 42 in Sources/Swift/Helper/SentrySDK.swift

View workflow job for this annotation

GitHub Actions / Fast Unit Tests (iOS 18) / Unit iOS 18 Sentry

cannot find 'MetricsApi' in scope

Check failure on line 42 in Sources/Swift/Helper/SentrySDK.swift

View workflow job for this annotation

GitHub Actions / Check API Stability ()

cannot find 'MetricsApi' in scope

Check failure on line 42 in Sources/Swift/Helper/SentrySDK.swift

View workflow job for this annotation

GitHub Actions / Fast XCFramework Slice (Sentry Dynamic, iOS arm64) / iphoneos

cannot find 'MetricsApi' in scope

Check failure on line 42 in Sources/Swift/Helper/SentrySDK.swift

View workflow job for this annotation

GitHub Actions / Lint

cannot find 'MetricsApi' in scope

/// Inits and configures Sentry (`SentryHub`, `SentryClient`) and sets up all integrations. Make sure to
/// set a valid DSN.
/// - note: Call this method on the main thread. When calling it from a background thread, the
Expand Down
49 changes: 46 additions & 3 deletions Sources/Swift/Integrations/Metrics/MetricsIntegration.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,56 @@
final class MetricsIntegration<Dependencies>: NSObject, SwiftIntegration {
@_implementationOnly import _SentryPrivate

protocol DispatchQueueWrapperProvider {
var dispatchQueueWrapper: SentryDispatchQueueWrapper { get }
}

final class MetricsIntegration<Dependencies: DispatchQueueWrapperProvider>: NSObject, SwiftIntegration, SentryMetricBatcherDelegate {
private let options: Options
private var metricBatcher: SentryMetricBatcher!
private let dispatchQueue: SentryDispatchQueueWrapper

init?(with options: Options, dependencies: Dependencies) {
guard options.enableMetrics else { return nil }

SentrySDKLog.debug("Integration initialized")
self.options = options
self.dispatchQueue = dependencies.dispatchQueueWrapper
self.metricBatcher = SentryMetricBatcher(
options: options,
dispatchQueue: dispatchQueue
)

super.init()

self.metricBatcher.delegate = self
}

func uninstall() {}
func uninstall() {
// Flush any pending metrics before uninstalling
metricBatcher.captureMetrics()
}

static var name: String {
"SentryMetricsIntegration"
}

// MARK: - Public API for MetricsApi

func addMetric(_ metric: SentryMetric, scope: Scope) {
metricBatcher.addMetric(metric, scope: scope)
}

// MARK: - SentryMetricBatcherDelegate

@objc(captureMetricsData:with:)
func capture(metricsData: NSData, count: NSNumber) {
// Get the client from the current hub
let hub = SentrySDKInternal.currentHub()
guard let client = hub.getClient() else {
SentrySDKLog.debug("MetricsIntegration: No client available, dropping metrics")
return
}

// Call the client's captureMetricsData method
client.captureMetricsData(metricsData as Data, with: count)
}
}
4 changes: 4 additions & 0 deletions Sources/Swift/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@
/// drop the log.
@objc public var beforeSendLog: ((SentryLog) -> SentryLog?)?

/// Use this callback to drop or modify a metric before the SDK sends it to Sentry. Return nil to
/// drop the metric.
@objc public var beforeSendMetric: ((SentryMetric) -> SentryMetric?)?

/// This block can be used to modify the breadcrumb before it will be serialized and sent.
@objc public var beforeBreadcrumb: SentryBeforeBreadcrumbCallback?

Expand Down
Loading
Loading