swift-otel
swift-otel copied to clipboard
Streamline bootstrap for defaults
trafficstars
Right now if you want to bootstrap the logging, metrics, and tracing systems you require the following code, which we also show in the HTTP server example:^1
// Bootstrap the logging backend with the OTel metadata provider which includes span IDs in logging messages.
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardError(label: label, metadataProvider: .otel)
handler.logLevel = .trace
return handler
}
// Configure OTel resource detection to automatically apply helpful attributes to events.
let environment = OTelEnvironment.detected()
let resourceDetection = OTelResourceDetection(detectors: [
OTelProcessResourceDetector(),
OTelEnvironmentResourceDetector(environment: environment),
.manual(OTelResource(attributes: ["service.name": "example_server"])),
])
let resource = await resourceDetection.resource(environment: environment, logLevel: .trace)
// Bootstrap the metrics backend to export metrics periodically in OTLP/gRPC.
let registry = OTelMetricRegistry()
let metricsExporter = try OTLPGRPCMetricExporter(configuration: .init(environment: environment))
let metricsReader = OTelPeriodicExportingMetricsReader(
resource: resource,
producer: registry,
exporter: metricsExporter,
configuration: .init(
environment: environment
)
)
MetricsSystem.bootstrap(OTLPMetricsFactory(registry: registry))
// Bootstrap the tracing backend to export traces periodically in OTLP/gRPC.
let exporter = try OTLPGRPCSpanExporter(configuration: .init(environment: environment))
let processor = OTelBatchSpanProcessor(exporter: exporter, configuration: .init(environment: environment))
let tracer = OTelTracer(
idGenerator: OTelRandomIDGenerator(),
sampler: OTelConstantSampler(isOn: true),
propagator: OTelW3CPropagator(),
processor: processor,
environment: environment,
resource: resource
)
InstrumentationSystem.bootstrap(tracer)
// Pass `tracer` and `metricsReader` to your service group.
This is quite a bit of boilerplate to get just the default behaviour and it would be nice to provide a more streamlined setup for folks that just want the defaults.
I'm not making any concrete API proposals, but probably a combination of:
- Default values for initialisers.
- Taking configuration from environment variables as directed in the OTel spec.
- Providing higher level factory methods that create, wire up, and return the layered types.
- Possibly unifying the API shape between tracing and metrics (tracer currently is both the factory and the service, but they are decoupled with metrics).
We may already be doing some of the above but I think we have some way to go.
As a strawman goal, it'd be nice to be able to get the code to something more like this:
// Configure OTel resource detection to automatically apply helpful attributes to events.
await OTelResourceDetection.bootstrap() // use process and environment detectors automatically by default
// Bootstrap the logging backend with the OTel metadata provider which includes span IDs in logging messages.
LoggingSystem.bootstrap(OTelStandardErrorLoggerFactory(level: .trace))
// Bootstrap the metrics backend to export metrics periodically in OTLP/gRPC.
let metricsFactory, metricsReader = OTelPeriodicExportingMetricsReader.make()
MetricsSystem.bootstrap(metricsFactory)
// Bootstrap the tracing backend to export traces periodically in OTLP/gRPC.
let tracer = OTelTracer()
InstrumentationSystem.bootstrap(tracer)
// Pass `tracer` and `metricsReader` to your service group.