Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add cross-cutting context api functions #176

Merged
merged 13 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
23 changes: 5 additions & 18 deletions .github/workflows/dart_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:
contents: read
issues: write
pull-requests: write

jobs:
test-dartv2:
runs-on: ubuntu-latest
Expand All @@ -26,14 +26,8 @@ jobs:
- uses: dart-lang/setup-dart@v1
with:
sdk: 2.19.6
- name: Install protobuf-compiler
run: sudo apt install -y protobuf-compiler
- name: Install Dart dependencies
run: dart pub get
- name: Initialize protobuf
run: make init
- name: Format, analyze, and run tests
run: make format analyze test
- run: sudo apt install -y protobuf-compiler
- run: make init format analyze test
- name: Generate Coverage
run: dart test --coverage=./coverage
- name: Activate Coverage Package
Expand All @@ -56,12 +50,5 @@ jobs:
- uses: dart-lang/setup-dart@v1
with:
sdk: 3.2.0
- name: Install protobuf-compiler
run: sudo apt install -y protobuf-compiler
- name: Install Dart dependencies
run: dart pub get
- name: Initialize protobuf
run: make init
- name: Format, analyze, and run tests
run: |
make format analyze test
- run: sudo apt install -y protobuf-compiler
- run: make init format analyze test
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ init:
opentelemetry-proto/opentelemetry/proto/collector/trace/v1/trace_service.proto \
opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto \
opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto
./scripts/attach_copyright.sh

analyze:
@dart analyze
Expand Down
236 changes: 63 additions & 173 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,208 +1,98 @@
# OpenTelemetry for Dart

This repo is intended to be the Dart implementation of the OpenTelemetry project, with a
long-term goal of being open sourced.
This repository is the Dart implementation of the [OpenTelemetry project](https://opentelemetry.io/). All contributions and designs should follow the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification).

All contributions and designs should follow the
[OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification)
in an effort to be consistent with [all other languages](https://github.com/open-telemetry).
## Project Status

## Getting Started

First, you will need to configure at least one exporter. An exporter determines what happens to the spans you collect.
The current options are:

| Exporter | Description |
| -------- | ----------- |
| [CollectorExporter](#collectorexporter) | Sends Spans to a configured opentelemetry-collector. |
| [ConsoleExporter](#consoleexporter) | Prints Spans to the console. |

### Span Exporters

#### CollectorExporter

The CollectorExporter requires a Uri of the opentelemetry-collector instance's trace collector.

```dart
import 'package:opentelemetry/sdk.dart' as otel_sdk;

final exporter = otel_sdk.CollectorExporter(Uri.parse('https://my-collector.com/v1/traces'));
```

#### ConsoleExporter

The ConsoleExporter has no requirements, and has no configuration options.

```dart
import 'package:opentelemetry/sdk.dart' as otel_sdk;

final exporter = otel_sdk.ConsoleExporter();
```

### Span Processors
| Signal | Status |
| - | - |
| Traces | Beta |
| Metrics | Alpha |
| Logs | Unimplemented |

Next, you will need at least one span processor. A span processor is responsible for collecting the spans you create and feeding them to the exporter.
The current options are:

| SpanProcessor | Description |
| -------- | ----------- |
| [BatchSpanProcessor](#batchspanprocessor) | Batches spans to be exported on a configured time interval. |
| [SimpleSpanProcessor](#simplespanprocessor) | Executes the provided exporter immediately upon closing the span. |
## Getting Started

#### BatchSpanProcessor
This section will show you how to initialize the OpenTelemetry SDK, capture a span, and propagate context.

BatchSpanProcessors collect up to 2048 spans per interval, and executes the provided exporter on a timer.
| Option | Description | Default |
| ------ | ----------- | ------- |
| maxExportBatchSize | At most, how many spans are processed per batch. | 512 |
| scheduledDelayMillis | How long to collect spans before processing them. | 5000 ms |
### Initialize the OpenTelemetry SDK

```dart
import 'package:opentelemetry/sdk.dart' as otel_sdk;

final exporter = otel_sdk.ConsoleExporter();
final processor = otel_sdk.BatchSpanProcessor(exporter, scheduledDelayMillis: 10000);
import 'package:opentelemetry/sdk.dart'
show
BatchSpanProcessor,
CollectorExporter,
ConsoleExporter,
SimpleSpanProcessor,
TracerProviderBase;
import 'package:opentelemetry/api.dart'
show registerGlobalTracerProvider, globalTracerProvider;

void main(List<String> args) {
final tracerProvider = TracerProviderBase(processors: [
BatchSpanProcessor(
CollectorExporter(Uri.parse('https://my-collector.com/v1/traces'))),
SimpleSpanProcessor(ConsoleExporter())
]);

registerGlobalTracerProvider(tracerProvider);
final tracer = globalTracerProvider.getTracer('instrumentation-name');
}
```

#### SimpleSpanProcessor

A SimpleSpanProcessor has no configuration options, and executes the exporter when each span is closed.
### Capture a Span

```dart
import 'package:opentelemetry/sdk.dart' as otel_sdk;

final exporter = otel_sdk.ConsoleExporter();
final processor = otel_sdk.SimpleSpanProcessor(exporter);
import 'package:opentelemetry/api.dart' show StatusCode, globalTracerProvider;

void main(List<String> args) {
final tracer = globalTracerProvider.getTracer('instrumentation-name');

final span = tracer.startSpan('main');
try {
// do some work
span.addEvent('some work');
} catch (e, s) {
span
..setStatus(StatusCode.error, e.toString())
..recordException(e, stackTrace: s);
rethrow;
} finally {
span.end();
}
}
```

### Tracer Provider

A trace provider registers your span processors, and is responsible for managing any tracers.
| Option | Description | Default |
| ------ | ----------- | ------- |
| processors | A list of SpanProcessors to register. | A [SimpleSpanProcessor](#simplespanprocessor) configured with a [ConsoleExporter](#consoleexporter). |
### Propagate Context

```dart
import 'package:opentelemetry/sdk.dart' as otel_sdk;
import 'package:opentelemetry/api.dart';
### Intra-process

final exporter = otel_sdk.CollectorExporter(Uri.parse('https://my-collector.com/v1/traces'));
final processor = otel_sdk.BatchSpanProcessor(exporter);
In order to parent spans, context must be propagated. Propagation can be achieved by manually passing an instance of `Context` or by using Dart [`Zones`](https://dart.dev/libraries/async/zones).

// Send spans to a collector every 5 seconds
final provider = otel_sdk.TracerProviderBase(processors: [processor]);
See the [noop context manager example](./example/noop_context_manager.dart) and [zone context manager example](./example/zone_context_manager.dart) for more information.

// Optionally, multiple processors can be registered
final provider = otel_sdk.TracerProviderBase(processors: [
otel_sdk.BatchSpanProcessor(otel_sdk.CollectorExporter(Uri.parse('https://my-collector.com/v1/traces'))),
otel_sdk.SimpleSpanProcessor(otel_sdk.ConsoleExporter())
]);
### Inter-process

registerGlobalTracerProvider(provider);
In order to parent spans between processes, context can be serialized and deserialized using a `TextMapPropagator`, `TextMapSetter`, and `TextMapGetter`.

final tracer = provider.getTracer('instrumentation-name');
// or
final tracer = globalTracerProvider.getTracer('instrumentation-name');
```
See the [W3C context propagation example](./example/w3c_context_propagation.dart) for more information.

#### Tracer Provider with Browser Performance Features
#### High Resolution Timestamps

A web-specific trace provider is also available. This trace provider makes available configurable options using the browser's performance API.
A tracer provider can register a web-specific time provider that uses the browser's [performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) instead of [DateTime](https://api.dart.dev/stable/dart-core/DateTime-class.html) when recording timestamps for a span's start timestamp, end timestamp, and span events.

```dart
import 'package:opentelemetry/sdk.dart' as otel_sdk;
import 'package:opentelemetry/web_sdk.dart' as web_sdk;
import 'package:opentelemetry/api.dart';

final exporter = otel_sdk.CollectorExporter(Uri.parse('https://my-collector.com/v1/traces'));
final processor = otel_sdk.BatchSpanProcessor(exporter);

// This provider is configured to create tracers which use the browser's
// performance API instead of Dart's DateTime class when determining
// timestamps for any spans they create.
final provider = web_sdk.WebTracerProvider(
processors: [processor],
timeProvider: web_sdk.WebTimeProvider()
);

// This tracer has been configured to use the browser's performance API when
// determining timestamps for any spans it creates.
final tracer = provider.getTracer('instrumentation-name');

// Or, these trace providers can also be registered globally.
registerGlobalTracerProvider(provider);
final tracer = globalTracerProvider.getTracer('instrumentation-name');
```

Important Note: Span timestamps resulting from use of this trace provider may be inaccurate if the executing system is suspended for sleep.
See [https://github.com/open-telemetry/opentelemetry-js/issues/852](https://github.com/open-telemetry/opentelemetry-js/issues/852) for more information.

## Collecting Spans

To start a span, execute `startSpan` on the tracer with the name of what you are tracing. When complete, call `end` on the span.

```dart
final span = tracer.startSpan('doingWork');
...
span.end();
```

To create children spans, use `Context.withSpan` and `Context.execute()` to execute work with a given span.

```dart
final checkoutSpan = tracer.startSpan('checkout');
Context.current.withSpan(checkoutSpan).execute(() {
final ringUpSpan = tracer.startSpan('ringUp');
...
ringUpSpan.end();
final receiveSpan = tracer.startSpan('receiveCash');
...
receiveSpan.end();
final returnSpan = tracer.startSpan('returnChange');
...
returnSpan.end();
});
checkoutSpan.end();
```

To avoid needing to pass spans around as arguments to other functions, you can get the current span with `Context.current.span`.

```dart
doWork() {
Span parentSpan = Context.current.span;

Context.current.withSpan(parentSpan).execute(() {
Span span = tracer.startSpan('doWork');
...
span.end();
});
}
final tracerProvider =
web_sdk.WebTracerProvider(timeProvider: web_sdk.WebTimeProvider());
```

### Span Events

A Span Event is a human-readable message on an Span that represents a discrete event with no duration that can be tracked by a single timestamp. You can think of it like a primitive log.

```dart
span.addEvent('Doing something');

const result = doWork();
```

You can also create Span Events with additional Attributes:
```dart
span.addEvent('some log', attributes: {
'log.severity': 'error',
'log.message': 'Data not found',
'request.id': requestId,
});
```
Important Note: Span timestamps may be inaccurate if the executing system is suspended for sleep. See [https://github.com/open-telemetry/opentelemetry-js/issues/852](https://github.com/open-telemetry/opentelemetry-js/issues/852) for more information.

## Development
## Contributing

In order to generate protobuf definitions, you must have [protoc](https://github.com/protocolbuffers/protobuf/releases) installed and available in your path.

### Publishing New Versions
See https://github.com/Workiva/Observability/blob/master/doc/publishing_opentelemetry_dart.md

Only Workiva maintainers can publish new versions of opentelemetry-dart.
Only Workiva maintainers can publish new versions of opentelemetry-dart. See [Publishing opentelemetry-dart](https://github.com/Workiva/Observability/blob/master/doc/publishing_opentelemetry_dart.md)
26 changes: 26 additions & 0 deletions example/noop_context_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:opentelemetry/api.dart';
import 'package:opentelemetry/sdk.dart'
show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase;
import 'package:opentelemetry/src/experimental_api.dart'
show NoopContextManager, registerGlobalContextManager;

void main(List<String> args) async {
final tp =
TracerProviderBase(processors: [SimpleSpanProcessor(ConsoleExporter())]);
registerGlobalTracerProvider(tp);

final cm = NoopContextManager();
registerGlobalContextManager(cm);

final span = tp.getTracer('instrumentation-name').startSpan('test-span-0');
await test(contextWithSpan(cm.active, span));
span.end();
}

Future test(Context context) async {
spanFromContext(context).setStatus(StatusCode.error, 'test error');
globalTracerProvider
.getTracer('instrumentation-name')
.startSpan('test-span-1', context: context)
.end();
}
51 changes: 51 additions & 0 deletions example/w3c_context_propagation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:opentelemetry/api.dart';
import 'package:opentelemetry/sdk.dart'
show ConsoleExporter, SimpleSpanProcessor, TracerProviderBase;
import 'package:opentelemetry/src/experimental_api.dart'
show globalContextManager, NoopContextManager, registerGlobalContextManager;

class MapSetter implements TextMapSetter<Map> {
@override
void set(Map carrier, String key, String value) {
carrier[key] = value;
}
}

class MapGetter implements TextMapGetter<Map> {
@override
String? get(Map? carrier, String key) {
return (carrier == null) ? null : carrier[key];
}

@override
Iterable<String> keys(Map carrier) {
return carrier.keys.map((key) => key.toString());
}
}

void main(List<String> args) async {
final tp =
TracerProviderBase(processors: [SimpleSpanProcessor(ConsoleExporter())]);
registerGlobalTracerProvider(tp);

final cm = NoopContextManager();
registerGlobalContextManager(cm);

final tmp = W3CTraceContextPropagator();
registerGlobalTextMapPropagator(tmp);

final span = tp.getTracer('instrumentation-name').startSpan('test-span-0');
final carrier = <String, String>{};
tmp.inject(contextWithSpan(cm.active, span), carrier, MapSetter());
await test(carrier);
span.end();
}

Future test(Map<String, String> carrier) async {
globalTracerProvider
.getTracer('instrumentation-name')
.startSpan('test-span-1',
context: globalTextMapPropagator.extract(
globalContextManager.active, carrier, MapGetter()))
.end();
}
Loading