Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works

Random project

opentelemetry-delphi

OpenTelemetry SDK for Delphi: traces and logs over OTLP/HTTP in one unit

View on GitHub

Anyone may press Interesting, signed in or not. It helps others find this project.

Member rating

No ratings yet.

Sign in to rate this project.

README

OpenTelemetry for Delphi

A small OpenTelemetry SDK for Delphi in one file: OpenTelemetry.pas.

It sends traces and logs from your Delphi application to any OpenTelemetry backend (OpenTelemetry Collector, Jaeger, Grafana Tempo / Loki, Datadog, Dynatrace and others).

Features

  • Traces: spans, parent and child spans, attributes, events, status
  • Record exceptions in a span
  • Logs with severity, attributes and trace context
  • W3C traceparent header: read and write
  • Samplers, and a ready trace id ratio sampler
  • Export with OTLP/HTTP (JSON), with custom HTTP headers (API keys)
  • Configuration with standard OTEL_* environment variables
  • Export runs in a background thread, in batches
  • No external libraries, only Delphi RTL

Requirements

  • Delphi 10.3 Rio or newer (tested with Delphi 11, Win32 and Win64)
  • Windows (32-bit and 64-bit)

Install

Copy OpenTelemetry.pas to your project and add OpenTelemetry to the uses list.

Example: traces

pascal
uses
  OpenTelemetry;

var
  Provider: ITracerProvider;
  Tracer: IOTLPTracer;
  Span: IOTLPSpan;
begin
  Provider := TTracerProvider.Create('my-service', 'production');
  Provider.AddSpanProcessor(TOTLPSpanProcessor.Create(
    TOTLPTraceExporter.Create('http://localhost:4318/v1/traces')));

  Tracer := Provider.GetTracer('MyApp');
  Span := Tracer.StartActiveSpan('load-orders', skInternal);
  try
    try
      Span.SetAttribute('orders.count', Int64(42));
      // your code
    except
      on E: Exception do
      begin
        Span.RecordException(E);
        Span.SetStatus(scError, E.Message);
        raise;
      end;
    end;
  finally
    Span.EndSpan;
  end;
end;

One tracer is one trace. Spans that start while another span is active in the same thread become its children.

You can also give a service version:

pascal
Provider := TTracerProvider.Create('my-service', 'production', '2.4.1');

Example: logs

pascal
var
  LogProvider: ILoggerProvider;
  Logger: IOTLPLogger;
begin
  LogProvider := TLoggerProvider.Create('my-service', 'production');
  LogProvider.AddLogProcessor(TOTLPLogRecordProcessor.Create(
    TOTLPLogExporter.Create('http://localhost:4318/v1/logs')));

  Logger := LogProvider.GetLogger('MyApp');
  Logger.Emit(OTLP_SEVERITY_INFO, 'INFO', 'Application started');
end;

To link a log to a trace, pass Span.TraceId and Span.Id to Emit.

Distributed tracing

Send the trace to another service with an HTTP header:

pascal
Request.CustomHeaders['traceparent'] := FormatTraceParent(Span);

Continue the trace in the other service:

pascal
var TraceId, ParentId: string;
// HeaderValue is the 'traceparent' header of the incoming request
if ParseTraceParent(HeaderValue, TraceId, ParentId) then
  Span := Provider.GetTracer('MyServer', TraceId)
    .StartActiveSpan('handle-request', skServer, ParentId);

FormatTraceParent sets the sampled flag (01) unless a trace id ratio sampler has already decided not to keep the trace. To read the flag of an incoming header, use the overload with a fourth parameter:

pascal
var Sampled: Boolean;
if ParseTraceParent(HeaderValue, TraceId, ParentId, Sampled) then
  ...

Send to a hosted backend

Many hosted backends need an API key in an HTTP header:

pascal
Provider.AddSpanProcessor(TOTLPSpanProcessor.Create(
  TOTLPTraceExporter.Create('https://otlp.example.com/v1/traces',
    [TOTLPAttribute.Create('Authorization', 'Bearer my-api-key')],
    10000)));  // timeout in milliseconds

The timeout is the response timeout. The connection timeout is the same value, but at most 3 seconds, so a collector that is down does not block ForceFlush for long. Headers that contain a line break are ignored.

Configuration with environment variables

Use FromEnvironment to read the standard OpenTelemetry variables:

pascal
Provider.AddSpanProcessor(TOTLPSpanProcessor.Create(TOTLPTraceExporter.FromEnvironment));
LogProvider.AddLogProcessor(TOTLPLogRecordProcessor.Create(TOTLPLogExporter.FromEnvironment));
Variable Example
OTEL_EXPORTER_OTLP_ENDPOINT http://collector:4318 (/v1/traces and /v1/logs are added)
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT full URL, used as is
OTEL_EXPORTER_OTLP_HEADERS (also _TRACES_ / _LOGS_) api-key=abc123,x-team=web
OTEL_EXPORTER_OTLP_TIMEOUT (also _TRACES_ / _LOGS_) 10000 (milliseconds)
OTEL_SERVICE_NAME my-service
OTEL_RESOURCE_ATTRIBUTES service.namespace=shop,team=backend
OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG traceidratio, 0.1

Without these variables the endpoint is http://localhost:4318. Values that you give in code (service name, version, environment) win over the variables.

Sampling

To keep only a part of the traces, for example 10%:

pascal
Provider.AddSpanSampler(TOTLPTraceIdRatioSampler.Create(0.1));

The decision is made from the 56 least significant bits of the trace id, as the OpenTelemetry specification says, so all services that use this algorithm keep the same traces, and trace ids made from GUIDs are sampled correctly too. A trace is exported when at least one sampler says yes.

Notes

  • Only traces and logs. Metrics are not supported yet.
  • Only OTLP/HTTP with JSON. gRPC and protobuf are not supported.
  • If the collector is not available, data is dropped for 5 seconds.
  • Before the application closes, call ForceFlush to send all data.
  • The SDK does not use the sampled flag of an incoming traceparent for its own sampling decision; read it with ParseTraceParent if you need it.

Self-test

test/OtlpTest.dpr is a console program that compiles the whole public API and checks ParseKeyValueList, ParseTraceParent, the trace id ratio sampler, FormatTraceParent and the OTEL_* variables without a network. Build it from the RAD Studio Command Prompt:

pascal
cd test
build.cmd
bin64\OtlpTest.exe

With a collector it also sends one trace and one log:

pascal
bin64\OtlpTest.exe net http://localhost:4318

Changes

  • 1.1.0: HTTP headers and timeout for exporters, FromEnvironment, OTEL_* variables, trace id ratio sampler, real sampled flag in FormatTraceParent, service.version, telemetry.sdk.name / telemetry.sdk.version, deployment.environment.name.
  • 1.0.0: first version.

Author

Volodymyr Plavshudin

License

MIT License. See LICENSE.