1from __future__ import annotations
2
3from typing import cast
4
5from opentelemetry import trace
6from opentelemetry.sdk.resources import Resource
7from opentelemetry.sdk.trace import TracerProvider
8from opentelemetry.semconv.attributes import service_attributes
9
10from plain.logs import app_logger
11from plain.packages import PackageConfig, register_config
12from plain.runtime import settings
13
14from .logging import observer_log_handler
15from .otel import (
16 ObserverCombinedSampler,
17 ObserverSampler,
18 ObserverSpanProcessor,
19 get_observer_span_processor,
20)
21
22
23@register_config
24class Config(PackageConfig):
25 package_label = "plainobserver"
26
27 def ready(self) -> None:
28 sampler = ObserverSampler()
29 span_processor = ObserverSpanProcessor()
30
31 if provider := self.get_existing_trace_provider():
32 # There is already a trace provider, so combine our sampler
33 # and add an additional span processor for Observer
34 if hasattr(provider, "sampler"):
35 provider.sampler = ObserverCombinedSampler(provider.sampler, sampler)
36
37 if not get_observer_span_processor():
38 provider.add_span_processor(span_processor)
39 else:
40 # Start our own provider, new sampler, and span processor
41 resource = Resource.create(
42 {
43 service_attributes.SERVICE_NAME: settings.NAME,
44 service_attributes.SERVICE_VERSION: settings.VERSION,
45 }
46 )
47 provider = TracerProvider(sampler=sampler, resource=resource)
48 provider.add_span_processor(span_processor)
49 trace.set_tracer_provider(provider)
50
51 # Install the logging handler to capture logs during traces
52 if observer_log_handler not in app_logger.handlers:
53 # Copy formatter from existing app_logger handler to match log formatting
54 for handler in app_logger.handlers:
55 if handler.formatter:
56 observer_log_handler.setFormatter(handler.formatter)
57 break
58
59 app_logger.addHandler(observer_log_handler)
60
61 @staticmethod
62 def get_existing_trace_provider() -> TracerProvider | None:
63 """Return the currently configured provider if set."""
64 current_provider = trace.get_tracer_provider()
65 if current_provider and not isinstance(
66 current_provider, trace.ProxyTracerProvider
67 ):
68 return cast(TracerProvider, current_provider)
69 return None