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. diff --git a/Sources/Swift/Helper/SentrySDK.swift b/Sources/Swift/Helper/SentrySDK.swift index 5091bd788f9..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,20 +72,14 @@ 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`. /// /// - 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..6c2b5cb6455 --- /dev/null +++ b/Sources/Swift/Integrations/Metrics/SentryMetrics.swift @@ -0,0 +1,118 @@ +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. +/// +/// 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 + + init(api: SentryMetricsApiProtocol) { + self.api = api + super.init() + } + + // 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) { + 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 { + /// 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) + } +}