Skip to content

Commit 84bc635

Browse files
authored
Metrics Part 1 - Add Counter Metric API (#1940)
Add Counter Metric API (part 1) (#1940) * added Counter Metric API * added Metric Aggregation, with hub/client integration and close * added metric tag normalization * added send of statsd envelope type Add other metric types and weight (part 2) (#1949) * added crc32_utils.dart, taken from archive library * added Gauge, Distribution and Set metrics * added weight to Metrics and auto flush when weight is too much Add timing metric and beforeMetric callback (part 3) (#1954) * added SentryOptions.beforeMetricCallback * added beforeMetricCallback logic in metrics_aggregator.dart * added timing metric api with span auto start * timing api uses span duration as value for the emitted metric if possible Add metrics span summary (part 4) (#1958) * added local_metrics_aggregator.dart to spans * metrics_aggregator.dart now adds to current span's localMetricsAggregator * added metric_summary.dart * added metricSummary to spans and transaction JSONs Add rate limit (part 5) (#1973) * added metric_bucket data category for rate limits * updated metric normalization rules * added rate limit for metrics
1 parent 61e71ec commit 84bc635

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3021
-81
lines changed

CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Experimental: Add support for Sentry Developer Metrics ([#1940](https://github.com/getsentry/sentry-dart/pull/1940), [#1949](https://github.com/getsentry/sentry-dart/pull/1949), [#1954](https://github.com/getsentry/sentry-dart/pull/1954), [#1958](https://github.com/getsentry/sentry-dart/pull/1958))
8+
Use the Metrics API to track processing time, download sizes, user signups, and conversion rates and correlate them back to tracing data in order to get deeper insights and solve issues faster. Our API supports counters, distributions, sets, gauges and timers, and it's easy to get started:
9+
```dart
10+
Sentry.metrics()
11+
.increment(
12+
'button_login_click', // key
13+
value: 1.0,
14+
unit: null,
15+
tags: {"provider": "e-mail"}
16+
);
17+
```
18+
To learn more about Sentry Developer Metrics, head over to our [Dart](https://docs.sentry.io/platforms/dart/metrics/) and [Flutter](https://docs.sentry.io/platforms/flutter/metrics/) docs page.
19+
520
### Dependencies
621

722
- Expand `package_info_plus` version range to `6.0.0` ([#1948](https://github.com/getsentry/sentry-dart/pull/1948))

dart/lib/src/hub.dart

+48
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import 'dart:async';
22
import 'dart:collection';
33

44
import 'package:meta/meta.dart';
5+
import 'metrics/metric.dart';
6+
import 'metrics/metrics_aggregator.dart';
7+
import 'metrics/metrics_api.dart';
58
import 'profiling.dart';
69
import 'propagation_context.dart';
710
import 'transport/data_category.dart';
@@ -38,6 +41,14 @@ class Hub {
3841

3942
late final _WeakMap _throwableToSpan;
4043

44+
late final MetricsApi _metricsApi;
45+
46+
@internal
47+
MetricsApi get metricsApi => _metricsApi;
48+
49+
@internal
50+
MetricsAggregator? get metricsAggregator => _peek().client.metricsAggregator;
51+
4152
factory Hub(SentryOptions options) {
4253
_validateOptions(options);
4354

@@ -49,6 +60,7 @@ class Hub {
4960
_stack.add(_StackItem(_getClient(_options), Scope(_options)));
5061
_isEnabled = true;
5162
_throwableToSpan = _WeakMap(_options);
63+
_metricsApi = MetricsApi(hub: this);
5264
}
5365

5466
static void _validateOptions(SentryOptions options) {
@@ -554,6 +566,42 @@ class Hub {
554566
return sentryId;
555567
}
556568

569+
@internal
570+
Future<SentryId> captureMetrics(
571+
Map<int, Iterable<Metric>> metricsBuckets) async {
572+
var sentryId = SentryId.empty();
573+
574+
if (!_isEnabled) {
575+
_options.logger(
576+
SentryLevel.warning,
577+
"Instance is disabled and this 'captureMetrics' call is a no-op.",
578+
);
579+
} else if (!_options.enableMetrics) {
580+
_options.logger(
581+
SentryLevel.info,
582+
"Metrics are disabled and this 'captureMetrics' call is a no-op.",
583+
);
584+
} else if (metricsBuckets.isEmpty) {
585+
_options.logger(
586+
SentryLevel.info,
587+
"Metrics are empty and this 'captureMetrics' call is a no-op.",
588+
);
589+
} else {
590+
final item = _peek();
591+
try {
592+
sentryId = await item.client.captureMetrics(metricsBuckets);
593+
} catch (exception, stackTrace) {
594+
_options.logger(
595+
SentryLevel.error,
596+
'Error while capturing metrics.',
597+
exception: exception,
598+
stackTrace: stackTrace,
599+
);
600+
}
601+
}
602+
return sentryId;
603+
}
604+
557605
@internal
558606
void setSpanContext(
559607
dynamic throwable,

dart/lib/src/hub_adapter.dart

+15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import 'package:meta/meta.dart';
44
import 'hint.dart';
55

66
import 'hub.dart';
7+
import 'metrics/metric.dart';
8+
import 'metrics/metrics_aggregator.dart';
9+
import 'metrics/metrics_api.dart';
710
import 'profiling.dart';
811
import 'protocol.dart';
912
import 'scope.dart';
@@ -23,6 +26,10 @@ class HubAdapter implements Hub {
2326
@internal
2427
SentryOptions get options => Sentry.currentHub.options;
2528

29+
@override
30+
@internal
31+
MetricsApi get metricsApi => Sentry.currentHub.metricsApi;
32+
2633
factory HubAdapter() {
2734
return _instance;
2835
}
@@ -181,4 +188,12 @@ class HubAdapter implements Hub {
181188

182189
@override
183190
Scope get scope => Sentry.currentHub.scope;
191+
192+
@override
193+
Future<SentryId> captureMetrics(Map<int, Iterable<Metric>> metricsBuckets) =>
194+
Sentry.currentHub.captureMetrics(metricsBuckets);
195+
196+
@override
197+
MetricsAggregator? get metricsAggregator =>
198+
Sentry.currentHub.metricsAggregator;
184199
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'dart:core';
2+
import 'package:meta/meta.dart';
3+
import '../protocol/metric_summary.dart';
4+
import 'metric.dart';
5+
6+
@internal
7+
class LocalMetricsAggregator {
8+
// format: <export key, <metric key, gauge>>
9+
final Map<String, Map<String, GaugeMetric>> _buckets = {};
10+
11+
void add(final Metric metric, final num value) {
12+
final bucket =
13+
_buckets.putIfAbsent(metric.getSpanAggregationKey(), () => {});
14+
15+
bucket.update(metric.getCompositeKey(), (m) => m..add(value),
16+
ifAbsent: () => Metric.fromType(
17+
type: MetricType.gauge,
18+
key: metric.key,
19+
value: value,
20+
unit: metric.unit,
21+
tags: metric.tags) as GaugeMetric);
22+
}
23+
24+
Map<String, List<MetricSummary>> getSummaries() {
25+
final Map<String, List<MetricSummary>> summaries = {};
26+
for (final entry in _buckets.entries) {
27+
final String exportKey = entry.key;
28+
29+
final metricSummaries = entry.value.values
30+
.map((gauge) => MetricSummary.fromGauge(gauge))
31+
.toList();
32+
33+
summaries[exportKey] = metricSummaries;
34+
}
35+
return summaries;
36+
}
37+
}

0 commit comments

Comments
 (0)