Skip to content
Merged
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
7 changes: 5 additions & 2 deletions .github/workflows/dart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ jobs:
- name: Run tests with coverage
run: dart pub global run coverage:test_with_coverage
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
file: coverage/lcov.info
# Tokenless uploads are rejected on protected branches
# ("Token required because branch is protected").
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/lcov.info
fail_ci_if_error: false

pana:
Expand Down
36 changes: 35 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,41 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0-beta.11-wip]
## [1.0.0-rc.1-wip]

### Removed
- **Breaking: the deprecated vendor/RUM enums are removed** (deprecated
with notice in 1.0.0-beta.10): `AppLifecycleStates`,
`AppLifecycleSemantics`, `AppStartType`, `AppInfoSemantics`,
`DeviceSemantics`, `BatterySemantics`, `NavigationSemantics`,
`InteractionType`, `InteractionSemantics`, `PerformanceSemantics`,
`ErrorSemantics`, `NetworkSemantics`, `RumSessionView`,
`NavigationAction`, and `LifecycleState` (`semantics/rum.dart` is
gone). They are not OpenTelemetry semantic conventions and so do not
belong in this package; `flutterrific_opentelemetry` defines its own
Flutter conventions for what the registry does not yet cover. The API
package now contains only registry conventions.

### Added
- **Semantic-conventions versioning policy** in VERSIONING.md: within a
major version, registry regenerations are additive and deprecating
only; identifier- or wire-affecting registry changes batch into the
next major; spec-fidelity string corrections are bug fixes with a
CHANGELOG wire-format table.

### Changed
- **Breaking: `APIObservableResult` is now an abstract interface.** The
API-side implementation was unconstructible (private constructor, no
factory) so nothing could have used it; SDKs implement the interface,
as `dartastic_opentelemetry` already does.

### Fixed
- `Attributes.of` no longer throws a `TypeError` when a map value is an
untyped list (`List<Object>` / `List<dynamic>`). Lists are now
element-checked like `Attributes.fromJson`: homogeneous
string/bool/int/double lists convert, mixed numeric lists promote to
`List<double>`, and unsupported element types are warned and ignored
per the OTel specification.

## [1.0.0-beta.10] - 2026-07-18

Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Thank you for your interest in contributing to the OpenTelemetry API for Dart! This document provides guidelines and instructions for contributing to this project.

Contributors should join the `#otel-dart` channel on the [CNCF Slack](https://slack.cncf.io/) — it's where Dart OpenTelemetry development is discussed and the fastest way to reach the maintainers.

## Code of Conduct

This project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the project maintainers.
Expand Down Expand Up @@ -189,6 +191,6 @@ By contributing to this project, you agree that your contributions will be licen

## Questions?

If you have any questions about contributing, please open an issue or contact the project maintainers directly.
If you have any questions about contributing, ask in the `#otel-dart` channel on the [CNCF Slack](https://slack.cncf.io/), open an issue, or contact the project maintainers directly.

Thank you for contributing to the OpenTelemetry API for Dart!
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ void main() {
tracer.withSpan(childSpan, () {
childSpan.setIntAttribute(ExampleAttribute.operationValue.key, 42);
});
childSpan.setStatus(SpanStatusCode.Ok);
// No setStatus(SpanStatusCode.Ok) here: Ok is the default, so
// only Error needs to be set explicitly.
} catch (e, stackTrace) {
// Per the OTel spec: recordException first, then setStatus(Error).
childSpan.recordException(e, stackTrace: stackTrace);
Expand All @@ -209,7 +210,6 @@ void main() {
childSpan.end();
}
});
rootSpan.setStatus(SpanStatusCode.Ok);
} catch (e, stackTrace) {
rootSpan.recordException(e, stackTrace: stackTrace);
rootSpan.setStatus(SpanStatusCode.Error, e.toString());
Expand Down
28 changes: 28 additions & 0 deletions VERSIONING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,34 @@ This package aims to align with the OpenTelemetry specification:
- We track the specification version we implement in our documentation
- Critical specification changes may necessitate breaking changes

## Semantic Conventions Versioning

The semantic-convention enums under `lib/src/api/semantics/semconv/` are
generated from a pinned OpenTelemetry semantic-conventions registry with
OTel Weaver (`tool/semconv/generate.sh`; verify freshness with
`--check`). The pinned registry version and commit are recorded in
`SemconvRegistry` and in every generated file header, and every registry
bump is documented in the CHANGELOG.

Within a major version, registry regenerations are **additive and
deprecating only**:

- New namespaces, attributes, enum members, metrics, events, and
entities may be added (a MINOR change).
- Attributes the registry deprecates gain `@Deprecated` but remain.
- Registry changes that would rename or remove generated Dart
identifiers, or change emitted key/value strings, are **held back and
batched into the next MAJOR release**.
- Exception: spec-fidelity corrections — where a generated member
emitted a string that did not match the registry — are bug fixes and
may land in a MINOR release with a prominent wire-format table in the
CHANGELOG (precedent: 1.0.0-beta.10).

Only registry conventions (plus their spec-mandated deprecation
history) appear in this package. Conventions the registry does not yet
cover — such as Flutter RUM semantics — are defined by downstream
packages (e.g. `flutterrific_opentelemetry`).

## Long-Term Support (LTS)

- No formal LTS versions currently exist for this pre-1.0 package
Expand Down
4 changes: 2 additions & 2 deletions example/dartastic_opentelemetry_api_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ void main() {
[OTelAPI.attributeString(ExampleAttribute.eventFoo.key, 'bar')]),
);
});
// Capitalized Ok to match the OTel spec.
span.setStatus(SpanStatusCode.Ok);
// No setStatus(SpanStatusCode.Ok) here: Ok is the default, so only
// Error needs to be set explicitly.
} catch (e, stackTrace) {
span.recordException(e, stackTrace: stackTrace);
span.setStatus(SpanStatusCode.Error, e.toString());
Expand Down
1 change: 0 additions & 1 deletion lib/dartastic_opentelemetry_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export 'src/api/metrics/up_down_counter.dart' hide UpDownCounterCreate;
export 'src/api/otel_api.dart';
// Semantics
export 'src/api/semantics/http_header_attribute.dart';
export 'src/api/semantics/rum.dart';
export 'src/api/semantics/semantics_base.dart';
export 'src/api/semantics/semconv/semconv.dart';
// Trace
Expand Down
6 changes: 3 additions & 3 deletions lib/src/api/common/attribute.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class Attribute<T extends Object> {
if (valueList.isEmpty) {
throw ArgumentError('Attribute _value list must not be empty');
}
if (valueList.contains(null)) {
throw ArgumentError('null is not allowed in List attribute values.');
}
// No null-element guard: attributes are only created with
// non-nullable element types (List<String>, List<bool>, List<int>,
// List<double>), so sound null safety already rules nulls out.
}
}

Expand Down
29 changes: 20 additions & 9 deletions lib/src/api/factory/otel_api_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import 'dart:typed_data';

import '../../factory/otel_factory.dart';
import '../../util/otel_log.dart';
import '../baggage/baggage.dart';
import '../baggage/baggage_entry.dart';
import '../common/attribute.dart';
Expand Down Expand Up @@ -185,19 +186,29 @@ class OTelAPIFactory extends OTelFactory {
} else if (value is Attribute) {
attributes.add(value);
} else if (value is List) {
// Element-check rather than hard-cast: the static list type is
// often List<Object> or List<dynamic> (e.g. from map literals),
// which `as List<String>` would reject at runtime.
if (value.isNotEmpty) {
if (value.first is String) {
if (value.every((e) => e is String)) {
attributes.add(AttributeCreate.create<List<String>>(
key, value as List<String>));
} else if (value.first is bool) {
key, value.cast<String>()));
} else if (value.every((e) => e is bool)) {
attributes.add(
AttributeCreate.create<List<bool>>(key, value as List<bool>));
} else if (value.first is int) {
attributes.add(
AttributeCreate.create<List<int>>(key, value as List<int>));
} else if (value.first is double) {
AttributeCreate.create<List<bool>>(key, value.cast<bool>()));
} else if (value.every((e) => e is int)) {
attributes
.add(AttributeCreate.create<List<int>>(key, value.cast<int>()));
} else if (value.every((e) => e is double || e is int)) {
// Mixed numeric lists are promoted to double.
attributes.add(AttributeCreate.create<List<double>>(
key, value as List<double>));
key,
value
.map((e) => e is int ? e.toDouble() : e as double)
.toList()));
} else {
OTelLog.warn(
'Ignoring attribute $key because the list contains unsupported types. Only String, bool, int, double lists are allowed by the OTel specification.');
}
}
} else {
Expand Down
28 changes: 9 additions & 19 deletions lib/src/api/metrics/meter_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,16 @@ class APIMeterProvider {
return true; // Already shut down
}

try {
// Mark as shut down immediately to prevent new meters
_isShutdown = true;
// Mark as shut down immediately to prevent new meters
_isShutdown = true;

// Clear the meter cache
_meterCache.clear();
// Clear the meter cache
_meterCache.clear();

// Disable the provider
_enabled = false;
// Disable the provider
_enabled = false;

return true;
} catch (e) {
OTelLog.error('Error during MeterProvider shutdown: $e');
return false;
}
return true;
}

/// Forces the MeterProvider to flush all pending metrics to exporters.
Expand All @@ -177,12 +172,7 @@ class APIMeterProvider {
return false; // Already shut down
}

try {
// In the API implementation, this is a no-op
return true;
} catch (e) {
OTelLog.error('Error during MeterProvider forceFlush: $e');
return false;
}
// In the API implementation, this is a no-op
return true;
}
}
24 changes: 8 additions & 16 deletions lib/src/api/metrics/observable_result.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

import '../../factory/otel_factory.dart';
import '../common/attributes.dart';
import 'measurement.dart';

/// Interface for recording observations from observable instruments.
class APIObservableResult<T extends num> {
List<Measurement<T>>? _measurements;

APIObservableResult._(this._measurements);

///
/// Implementations are provided by SDKs (the previous API-side
/// implementation had a private constructor and no factory, so it was
/// unconstructible).
abstract class APIObservableResult<T extends num> {
/// Records a measurement with the given value and attributes.
void observe(T value, [Attributes? attributes]) {
_measurements ??= [];
_measurements!
.add(OTelFactory.otelFactory!.createMeasurement(value, attributes));
}
void observe(T value, [Attributes? attributes]);

/// Records a measurement with the given value and attributes as a map.
void observeWithMap(T value, Map<String, Object> attributes) {
observe(value, attributes.toAttributes());
}
void observeWithMap(T value, Map<String, Object> attributes);

/// Get all recorded measurements
List<Measurement<T>> get measurements =>
List.unmodifiable(_measurements ?? []);
List<Measurement<T>> get measurements;
}
Loading
Loading