174 lines
6.8 KiB
Markdown
174 lines
6.8 KiB
Markdown
# Extending the OpenTelemetry .NET SDK
|
|
|
|
* [Building your own exporter](#exporter)
|
|
* [Building your own reader](#reader)
|
|
* [Building your own exemplar reservoir](#exemplarreservoir)
|
|
* [Building your own resource detector](../../resources/README.md#resource-detector)
|
|
* [References](#references)
|
|
|
|
## Exporter
|
|
|
|
OpenTelemetry .NET SDK has provided the following built-in metric exporters:
|
|
|
|
* [InMemory](../../../src/OpenTelemetry.Exporter.InMemory/README.md)
|
|
* [Console](../../../src/OpenTelemetry.Exporter.Console/README.md)
|
|
* [OpenTelemetryProtocol](../../../src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md)
|
|
* [Prometheus HttpListener](../../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md)
|
|
* [Prometheus AspNetCore](../../../src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md)
|
|
|
|
Custom exporters can be implemented to send telemetry data to places which are
|
|
not covered by the built-in exporters:
|
|
|
|
* Exporters should derive from `OpenTelemetry.BaseExporter<Metric>` (which
|
|
belongs to the [OpenTelemetry](../../../src/OpenTelemetry/README.md) package)
|
|
and implement the `Export` method.
|
|
* Exporters can optionally implement the `OnShutdown` method.
|
|
* Exporters should not throw exceptions from `Export` and
|
|
`OnShutdown`.
|
|
* Exporters are responsible for any retry logic needed by the scenario. The SDK
|
|
does not implement any retry logic.
|
|
* Exporters should avoid generating telemetry and causing live-loop, this can be
|
|
done via `OpenTelemetry.SuppressInstrumentationScope`.
|
|
* Exporters receives a batch of `Metric`, and each `Metric`
|
|
can contain 1 or more `MetricPoint`s.
|
|
The exporter should perform all actions (e.g. serializing etc.) with
|
|
the `Metric`s and `MetricsPoint`s in the batch before returning control from
|
|
`Export`, once the control is returned, the exporter can no longer make any
|
|
assumptions about the state of the batch or anything inside it.
|
|
* Exporters should use `ParentProvider.GetResource()` to get the `Resource`
|
|
associated with the provider.
|
|
|
|
```csharp
|
|
class MyExporter : BaseExporter<Metric>
|
|
{
|
|
public override ExportResult Export(in Batch<Metric> batch)
|
|
{
|
|
using var scope = SuppressInstrumentationScope.Begin();
|
|
|
|
foreach (var metric in batch)
|
|
{
|
|
Console.WriteLine($"Export: {metric.metric}");
|
|
|
|
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
|
|
{
|
|
Console.WriteLine($"Export: {metricPoint.StartTime}");
|
|
}
|
|
}
|
|
|
|
return ExportResult.Success;
|
|
}
|
|
}
|
|
```
|
|
|
|
A demo exporter which simply writes metric name, metric point start time
|
|
and tags to the console is shown [here](./MyExporter.cs).
|
|
|
|
Apart from the exporter itself, you should also provide extension methods as
|
|
shown [here](./MyExporterExtensions.cs). This allows users to add the Exporter
|
|
to the `MeterProvider` as shown in the example [here](./Program.cs).
|
|
|
|
## Reader
|
|
|
|
Not supported.
|
|
|
|
## ExemplarReservoir
|
|
|
|
> [!NOTE]
|
|
> `ExemplarReservoir` is an experimental API only available in pre-release
|
|
builds. For details see:
|
|
[OTEL1004](../../diagnostics/experimental-apis/OTEL1004.md). Please [provide
|
|
feedback](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5629)
|
|
to help inform decisions about what should be exposed stable and when.
|
|
|
|
Custom [ExemplarReservoir](../customizing-the-sdk/README.md#exemplarreservoir)s
|
|
can be implemented to control how `Exemplar`s are recorded for a metric:
|
|
|
|
* `ExemplarReservoir`s should derive from `FixedSizeExemplarReservoir` (which
|
|
belongs to the [OpenTelemetry](../../../src/OpenTelemetry/README.md) package)
|
|
and implement the `Offer` methods.
|
|
* The `FixedSizeExemplarReservoir` constructor accepts a `capacity` parameter to
|
|
control the number of `Exemplar`s which may be recorded by the
|
|
`ExemplarReservoir`.
|
|
* The `virtual` `OnCollected` method is called after the `ExemplarReservoir`
|
|
collection operation has completed and may be used to implement cleanup or
|
|
reset logic.
|
|
* The `bool` `ResetOnCollect` property on `ExemplarReservoir` is set to `true`
|
|
when delta aggregation temporality is used for the metric using the
|
|
`ExemplarReservoir`.
|
|
* The `Offer` and `Collect` `ExemplarReservoir` methods are called concurrently
|
|
by the OpenTelemetry SDK. As such any state required by custom
|
|
`ExemplarReservoir` implementations needs to be managed using appropriate
|
|
thread-safety/concurrency mechanisms (`lock`, `Interlocked`, etc.).
|
|
* Custom `ExemplarReservoir` implementations MUST NOT throw exceptions.
|
|
Exceptions thrown in custom implementations MAY lead to unreleased locks and
|
|
undefined behaviors.
|
|
|
|
The following example demonstrates a custom `ExemplarReservoir` implementation
|
|
which records `Exemplar`s for measurements which have the highest value. When
|
|
delta aggregation temporality is used the recorded `Exemplar` will be the
|
|
highest value for a given collection cycle. When cumulative aggregation
|
|
temporality is used the recorded `Exemplar` will be the highest value for the
|
|
lifetime of the process.
|
|
|
|
```csharp
|
|
class HighestValueExemplarReservoir : FixedSizeExemplarReservoir
|
|
{
|
|
private readonly object lockObject = new();
|
|
private long? previousValueLong;
|
|
private double? previousValueDouble;
|
|
|
|
public HighestValueExemplarReservoir()
|
|
: base(capacity: 1)
|
|
{
|
|
}
|
|
|
|
public override void Offer(in ExemplarMeasurement<long> measurement)
|
|
{
|
|
if (!this.previousValueLong.HasValue || measurement.Value > this.previousValueLong.Value)
|
|
{
|
|
lock (this.lockObject)
|
|
{
|
|
if (!this.previousValueLong.HasValue || measurement.Value > this.previousValueLong.Value)
|
|
{
|
|
this.UpdateExemplar(0, in measurement);
|
|
this.previousValueLong = measurement.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Offer(in ExemplarMeasurement<double> measurement)
|
|
{
|
|
if (!this.previousValueDouble.HasValue || measurement.Value > this.previousValueDouble.Value)
|
|
{
|
|
lock (this.lockObject)
|
|
{
|
|
if (!this.previousValueDouble.HasValue || measurement.Value > this.previousValueDouble.Value)
|
|
{
|
|
this.UpdateExemplar(0, in measurement);
|
|
this.previousValueDouble = measurement.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnCollected()
|
|
{
|
|
if (this.ResetOnCollect)
|
|
{
|
|
lock (this.lockObject)
|
|
{
|
|
this.previousValueLong = null;
|
|
this.previousValueDouble = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Custom [ExemplarReservoir](../customizing-the-sdk/README.md#exemplarreservoir)s
|
|
can be configured using the View API. For details see: [Change the
|
|
ExemplarReservoir](../customizing-the-sdk/README.md#change-the-exemplarreservoir).
|
|
|
|
## References
|