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