Skip to content

Commit

Permalink
feat(otel): ✨ factories for meter trace provider with config evolution
Browse files Browse the repository at this point in the history
  • Loading branch information
miragecentury committed Aug 11, 2024
1 parent 771dc6b commit 54d3646
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 33 deletions.
71 changes: 66 additions & 5 deletions src/python_factory/core/plugins/opentelemetry_plugin/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,82 @@

from typing import Annotated

from pydantic import BaseModel, Field, UrlConstraints
from pydantic import BaseModel, ConfigDict, Field, UrlConstraints
from pydantic_core import Url


class OpenTelemetryMeterConfig(BaseModel):
"""
Provides the configuration model for the OpenTelemetry meter as sub-model.
"""

model_config = ConfigDict(frozen=True, extra="forbid")

reader_interval_millis: float = Field(
default=60000,
description="The interval in miliseconds to read and export metrics.",
)

reader_timeout_millis: float = Field(
default=1000,
description="The timeout in miliseconds for the reader.",
)


class OpenTelemetryTracerConfig(BaseModel):
"""
Provides the configuration model for the OpenTelemetry tracer as sub-model.
"""

model_config = ConfigDict(frozen=True, extra="forbid")

max_queue_size: int = Field(
default=2048,
description="The maximum queue size for the tracer.",
)
max_export_batch_size: int = Field(
default=512,
description="The maximum export batch size for the tracer.",
)
schedule_delay_millis: int = Field(
default=5000,
description="The schedule delay in miliseconds for the tracer.",
)
export_timeout_millis: int = Field(
default=30000,
description="The export timeout in miliseconds for the tracer.",
)


class OpenTelemetryConfig(BaseModel):
"""
Provides the configuration model for the OpenTelemetry plugin.
"""

COLLECTOR_ENDPOINT_DEFAULT: str = "http://localhost:4317"

collector_export_activate: bool = Field(
model_config = ConfigDict(frozen=True, extra="forbid")

activate: bool = Field(
default=False,
description="Whether to activate the OpenTelemetry collector export.",
)
collector_export_endpoint: Annotated[
Url, UrlConstraints(allowed_schemes=["http", "https"])
] = Field(
endpoint: Annotated[Url, UrlConstraints(allowed_schemes=["http", "https"])] = Field(
default=Url(url=COLLECTOR_ENDPOINT_DEFAULT),
description="The collector endpoint.",
)

timeout: int = Field(
default=10,
description="The timeout in seconds for the collector.",
)

meter_config: OpenTelemetryMeterConfig | None = Field(
default_factory=OpenTelemetryMeterConfig,
description="The meter configuration.",
)

tracer_config: OpenTelemetryTracerConfig | None = Field(
default_factory=OpenTelemetryTracerConfig,
description="The tracer configuration.",
)
11 changes: 11 additions & 0 deletions src/python_factory/core/plugins/opentelemetry_plugin/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Provides the exceptions for the OpenTelemetryPlugin.
"""


class OpenTelemetryPluginBaseException(BaseException):
pass


class OpenTelemetryPluginConfigError(OpenTelemetryPluginBaseException):
pass
124 changes: 96 additions & 28 deletions src/python_factory/core/plugins/opentelemetry_plugin/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@

import injector
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
MetricExporter,
PeriodicExportingMetricReader,
)
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import (
DEPLOYMENT_ENVIRONMENT,
SERVICE_NAME,
SERVICE_NAMESPACE,
SERVICE_VERSION,
Resource,
)
from opentelemetry.sdk.trace import SynchronousMultiSpanProcessor, TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from python_factory.core.app.base.application import BaseApplication

from .configs import OpenTelemetryConfig
from .exceptions import OpenTelemetryPluginConfigError


def resource_factory(application: injector.Inject[BaseApplication]) -> Resource:
"""
Expand All @@ -42,47 +45,112 @@ def resource_factory(application: injector.Inject[BaseApplication]) -> Resource:
)


def metric_exporter_factory() -> MetricExporter:
def meter_provider_factory(
resource: Resource,
opentelemetry_config: OpenTelemetryConfig,
) -> MeterProvider:
"""
Build a metric exporter object for OpenTelemetry.
Build a meter provider object for OpenTelemetry
Args:
resource (Resource): The resource object for OpenTelemetry.
opentelemetry_config (OpenTelemetryConfig): The configuration object
for OpenTelemetry.
Returns:
MetricExporter: The metric exporter object for OpenTelemetry.
MeterProvider: The meter provider object for OpenTelemetry.
"""
return OTLPMetricExporter()

# Exit with a void MeterProvider if the export is not activated
if opentelemetry_config.activate is False:
return MeterProvider(
resource=resource,
metric_readers=[],
shutdown_on_exit=True,
views=[],
)

if opentelemetry_config.meter_config is None:
# TODO: switch to a custom exception
raise OpenTelemetryPluginConfigError("The meter configuration is missing.")

# Setup the Exporter
exporter = OTLPMetricExporter(
endpoint=opentelemetry_config.endpoint.unicode_string(),
timeout=opentelemetry_config.timeout,
)

def periodic_exporting_metric_reader_factory(
metric_exporter: MetricExporter,
) -> PeriodicExportingMetricReader:
"""
Build a periodic exporting metric reader object for OpenTelemetry.
# Setup the Metric Reader
reader = PeriodicExportingMetricReader(
exporter=exporter,
export_interval_millis=opentelemetry_config.meter_config.reader_interval_millis,
export_timeout_millis=opentelemetry_config.meter_config.reader_timeout_millis,
)

Returns:
PeriodicExportingMetricReader: The periodic exporting metric reader object
for OpenTelemetry.
"""
return PeriodicExportingMetricReader(
exporter=metric_exporter,
# Setup the Meter Provider
return MeterProvider(
resource=resource,
metric_readers=[reader],
shutdown_on_exit=True,
views=[],
)


def meter_provider_factory(
def tracer_provider_factory(
resource: Resource,
metric_reader: PeriodicExportingMetricReader,
) -> MeterProvider:
opentelemetry_config: OpenTelemetryConfig,
) -> TracerProvider:
"""
Build a meter provider object for OpenTelemetry.
Provides a tracer provider for OpenTelemetry.
Args:
resource (Resource): The resource object for OpenTelemetry.
metric_reader (PeriodicExportingMetricReader): The periodic exporting
metric reader object for OpenTelemetry.
opentelemetry_config (OpenTelemetryConfig): The configuration object
for OpenTelemetry.
Returns:
MeterProvider: The meter provider object for OpenTelemetry.
TracerProvider: The tracer provider object for OpenTelemetry.
"""
return MeterProvider(

# Exit with a void TracerProvider if the export is not activated
if opentelemetry_config.activate is False:
return TracerProvider(
resource=resource,
sampler=None,
id_generator=None,
active_span_processor=None,
shutdown_on_exit=True,
span_limits=None,
)

if opentelemetry_config.tracer_config is None:
raise OpenTelemetryPluginConfigError("The tracer configuration is missing.")

# Setup the Exporter
exporter = OTLPSpanExporter(
endpoint=opentelemetry_config.endpoint.unicode_string(),
timeout=opentelemetry_config.timeout,
)

# Setup the Span Processor
span_processor = BatchSpanProcessor(
span_exporter=exporter,
max_queue_size=opentelemetry_config.tracer_config.max_queue_size,
max_export_batch_size=opentelemetry_config.tracer_config.max_export_batch_size,
schedule_delay_millis=opentelemetry_config.tracer_config.schedule_delay_millis,
export_timeout_millis=opentelemetry_config.tracer_config.export_timeout_millis,
)

# Setup the Multi Span Processor
synchronous_multi_span_processor = SynchronousMultiSpanProcessor()
synchronous_multi_span_processor.add_span_processor(span_processor=span_processor)

# Setup the Tracer Provider
return TracerProvider(
sampler=None,
resource=resource,
metric_readers=[metric_reader],
active_span_processor=synchronous_multi_span_processor,
id_generator=None,
span_limits=None,
shutdown_on_exit=True,
)

0 comments on commit 54d3646

Please sign in to comment.