From e6657b9c235587f8c501d3cff9635f0341a3ac6b Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Mon, 2 Feb 2026 13:53:23 +0200 Subject: [PATCH 1/5] Expose metrics API to Objective-C --- Sources/Swift/Helper/SentrySDK.swift | 6 +- .../Integrations/Metrics/SentryMetrics.swift | 106 ++++++++++++++++++ 2 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 Sources/Swift/Integrations/Metrics/SentryMetrics.swift diff --git a/Sources/Swift/Helper/SentrySDK.swift b/Sources/Swift/Helper/SentrySDK.swift index 5091bd788f9..cc7c3077128 100644 --- a/Sources/Swift/Helper/SentrySDK.swift +++ b/Sources/Swift/Helper/SentrySDK.swift @@ -80,12 +80,8 @@ import Foundation /// /// - Note: This feature is currently in open beta. /// - /// - Important: The Metrics API has been designed and optimized for Swift. Objective-C support is not - /// currently available. If you need Objective-C support, please open an issue at - /// https://github.com/getsentry/sentry-cocoa/issues to show demand for this feature. - /// /// - SeeAlso: For complete documentation, visit https://docs.sentry.io/platforms/apple/metrics/ - public static var metrics: SentryMetricsApiProtocol = SentryMetricsApi(dependencies: SentryDependencyContainer.sharedInstance()) + @objc public static var metrics: SentryMetrics = SentryMetrics(api: SentryMetricsApi(dependencies: SentryDependencyContainer.sharedInstance())) /// Inits and configures Sentry (`SentryHub`, `SentryClient`) and sets up all integrations. Make sure to /// set a valid DSN. diff --git a/Sources/Swift/Integrations/Metrics/SentryMetrics.swift b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift new file mode 100644 index 00000000000..e26f616bd8a --- /dev/null +++ b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift @@ -0,0 +1,106 @@ +import Foundation + +/// Objective-C compatible wrapper for the Sentry Metrics API. +/// +/// This class provides an Objective-C accessible interface to the underlying Swift metrics API. +/// It accepts `String` for units (matching the raw value of ``SentryUnit``) and `[String: Any]` +/// for attributes, converting them to the Swift-native types internally. +/// +/// For Swift code, prefer using ``SentrySDK/metrics`` with the ``SentryMetricsApiProtocol`` +/// directly, which provides a more idiomatic Swift API with type-safe ``SentryUnit`` and +/// ``SentryAttributeValue`` protocol. +@objc +public final class SentryMetrics: NSObject { + private let api: SentryMetricsApiProtocol + + init(api: SentryMetricsApiProtocol) { + self.api = api + super.init() + } + + // MARK: - Count + + /// Records a count metric with the specified key and value. + @objc(countWithKey:value:) + public func count(key: String, value: UInt) { + api.count(key: key, value: value) + } + + /// Records a count metric with the specified key, value, and unit. + @objc(countWithKey:value:unit:) + public func count(key: String, value: UInt, unit: String?) { + api.count(key: key, value: value, unit: sentryUnit(from: unit)) + } + + /// Records a count metric with the specified key, value, unit, and attributes. + @objc(countWithKey:value:unit:attributes:) + public func count(key: String, value: UInt, unit: String?, attributes: [String: Any]) { + api.count(key: key, value: value, unit: sentryUnit(from: unit), attributes: convertAttributes(attributes)) + } + + // MARK: - Distribution + + /// Records a distribution metric with the specified key and value. + @objc(distributionWithKey:value:) + public func distribution(key: String, value: Double) { + api.distribution(key: key, value: value) + } + + /// Records a distribution metric with the specified key, value, and unit. + @objc(distributionWithKey:value:unit:) + public func distribution(key: String, value: Double, unit: String?) { + api.distribution(key: key, value: value, unit: sentryUnit(from: unit)) + } + + /// Records a distribution metric with the specified key, value, unit, and attributes. + @objc(distributionWithKey:value:unit:attributes:) + public func distribution(key: String, value: Double, unit: String?, attributes: [String: Any]) { + api.distribution(key: key, value: value, unit: sentryUnit(from: unit), attributes: convertAttributes(attributes)) + } + + // MARK: - Gauge + + /// Records a gauge metric with the specified key and value. + @objc(gaugeWithKey:value:) + public func gauge(key: String, value: Double) { + api.gauge(key: key, value: value) + } + + /// Records a gauge metric with the specified key, value, and unit. + @objc(gaugeWithKey:value:unit:) + public func gauge(key: String, value: Double, unit: String?) { + api.gauge(key: key, value: value, unit: sentryUnit(from: unit)) + } + + /// Records a gauge metric with the specified key, value, unit, and attributes. + @objc(gaugeWithKey:value:unit:attributes:) + public func gauge(key: String, value: Double, unit: String?, attributes: [String: Any]) { + api.gauge(key: key, value: value, unit: sentryUnit(from: unit), attributes: convertAttributes(attributes)) + } + + // MARK: - Private + + private func sentryUnit(from string: String?) -> SentryUnit? { + guard let string, !string.isEmpty else { return nil } + return SentryUnit(rawValue: string) + } + + private func convertAttributes(_ attrs: [String: Any]) -> [String: SentryAttributeValue] { + attrs.mapValues { SentryAttribute(value: $0) } + } +} + +// Conformance needed so SentryMetrics can also be used through the protocol-based Swift API. +extension SentryMetrics: SentryMetricsApiProtocol { + public func count(key: String, value: UInt, unit: SentryUnit?, attributes: [String: SentryAttributeValue]) { + api.count(key: key, value: value, unit: unit, attributes: attributes) + } + + public func distribution(key: String, value: Double, unit: SentryUnit?, attributes: [String: SentryAttributeValue]) { + api.distribution(key: key, value: value, unit: unit, attributes: attributes) + } + + public func gauge(key: String, value: Double, unit: SentryUnit?, attributes: [String: SentryAttributeValue]) { + api.gauge(key: key, value: value, unit: unit, attributes: attributes) + } +} From 6f6333a48022cc46e5627512f0eaeadf2b5ef5db Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Mon, 2 Feb 2026 14:34:24 +0200 Subject: [PATCH 2/5] Update comments --- Sources/Swift/Helper/SentrySDK.swift | 10 ++++------ Sources/Swift/Integrations/Metrics/SentryMetrics.swift | 9 ++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/Swift/Helper/SentrySDK.swift b/Sources/Swift/Helper/SentrySDK.swift index cc7c3077128..0729e827ceb 100644 --- a/Sources/Swift/Helper/SentrySDK.swift +++ b/Sources/Swift/Helper/SentrySDK.swift @@ -45,11 +45,11 @@ import Foundation /// using their individual attributes. /// /// The `metrics` namespace exposes three methods to capture different types of metric information: - /// - ``SentryMetricsApiProtocol/count(key:value:unit:attributes:)``: Track discrete occurrence counts + /// - ``SentryMetrics/count(key:value:unit:attributes:)``: Track discrete occurrence counts /// (e.g., button clicks, API requests, errors). - /// - ``SentryMetricsApiProtocol/gauge(key:value:unit:attributes:)``: Track values that can go up and down + /// - ``SentryMetrics/gauge(key:value:unit:attributes:)``: Track values that can go up and down /// (e.g., memory usage, queue depth, active connections). - /// - ``SentryMetricsApiProtocol/distribution(key:value:unit:attributes:)``: Track the distribution of a value + /// - ``SentryMetrics/distribution(key:value:unit:attributes:)``: Track the distribution of a value /// over time for statistical analysis like percentiles (e.g., response times, request durations). /// /// Each method supports optional units (via ``SentryUnit``) and attributes for filtering and grouping. @@ -72,9 +72,7 @@ import Foundation /// ## Requirements /// /// Metrics are enabled by default even though it is an experimental feature, because you must still - /// manually call the API methods (``SentryMetricsApiProtocol/count(key:value:unit:attributes:)``, - /// ``SentryMetricsApiProtocol/gauge(key:value:unit:attributes:)``, or - /// ``SentryMetricsApiProtocol/distribution(key:value:unit:attributes:)``) to use it. + /// manually call the API methods to use it. /// /// To disable metrics, set ``Options/experimental`` ``SentryExperimentalOptions/enableMetrics`` to `false`. /// diff --git a/Sources/Swift/Integrations/Metrics/SentryMetrics.swift b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift index e26f616bd8a..0bd961ee5bf 100644 --- a/Sources/Swift/Integrations/Metrics/SentryMetrics.swift +++ b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift @@ -6,9 +6,12 @@ import Foundation /// It accepts `String` for units (matching the raw value of ``SentryUnit``) and `[String: Any]` /// for attributes, converting them to the Swift-native types internally. /// -/// For Swift code, prefer using ``SentrySDK/metrics`` with the ``SentryMetricsApiProtocol`` -/// directly, which provides a more idiomatic Swift API with type-safe ``SentryUnit`` and -/// ``SentryAttributeValue`` protocol. +/// Swift callers can use the type-safe ``SentryUnit`` and ``SentryAttributeValue`` parameters +/// directly through the ``SentryMetricsApiProtocol`` conformance on this class. +/// +/// - Important: From Objective-C, pass unit names as plain strings (e.g., `@"millisecond"`, +/// `@"byte"`) and attributes as `NSDictionary`. The wrapper converts these +/// to the Swift-native types internally. @objc public final class SentryMetrics: NSObject { private let api: SentryMetricsApiProtocol From 608fe29c71af2c94a54cd62a97ba8f6ba4408a3f Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Mon, 2 Feb 2026 14:34:33 +0200 Subject: [PATCH 3/5] Add counter increment --- Sources/Swift/Integrations/Metrics/SentryMetrics.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Swift/Integrations/Metrics/SentryMetrics.swift b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift index 0bd961ee5bf..a1465603624 100644 --- a/Sources/Swift/Integrations/Metrics/SentryMetrics.swift +++ b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift @@ -23,6 +23,12 @@ public final class SentryMetrics: NSObject { // MARK: - Count + /// Records a count metric, incrementing the specified key by 1. + @objc(countWithKey:) + public func count(key: String) { + api.count(key: key, value: 1) + } + /// Records a count metric with the specified key and value. @objc(countWithKey:value:) public func count(key: String, value: UInt) { From 35d7f806904d5e988be2526c4d2ca39322f48e7e Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Mon, 2 Feb 2026 15:59:49 +0200 Subject: [PATCH 4/5] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6023a6637c8..4a0d38f43ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Expose metrics API to Objective-C ([#7339](https://github.com/getsentry/sentry-cocoa/pull/7339)) + ### Fixes - Fix mismatch of `in_foreground` app context (#7188) The app context `in_foreground` for handled and unhandled events was sometimes different. This is fixed now by aligning the implementation and adding a new `is_active` app context field. From 083ff1b81e7fa96b5fadf0aa83a4002e89ae345a Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Mon, 2 Feb 2026 16:18:29 +0200 Subject: [PATCH 5/5] Fix missing docs --- Sources/Swift/Integrations/Metrics/SentryMetrics.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/Swift/Integrations/Metrics/SentryMetrics.swift b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift index a1465603624..6c2b5cb6455 100644 --- a/Sources/Swift/Integrations/Metrics/SentryMetrics.swift +++ b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift @@ -101,14 +101,17 @@ public final class SentryMetrics: NSObject { // Conformance needed so SentryMetrics can also be used through the protocol-based Swift API. extension SentryMetrics: SentryMetricsApiProtocol { + /// Records a count metric with type-safe Swift parameters. public func count(key: String, value: UInt, unit: SentryUnit?, attributes: [String: SentryAttributeValue]) { api.count(key: key, value: value, unit: unit, attributes: attributes) } + /// Records a distribution metric with type-safe Swift parameters. public func distribution(key: String, value: Double, unit: SentryUnit?, attributes: [String: SentryAttributeValue]) { api.distribution(key: key, value: value, unit: unit, attributes: attributes) } + /// Records a gauge metric with type-safe Swift parameters. public func gauge(key: String, value: Double, unit: SentryUnit?, attributes: [String: SentryAttributeValue]) { api.gauge(key: key, value: value, unit: unit, attributes: attributes) }