structure saas with tools

This commit is contained in:
Davidson Gomes
2025-04-25 15:30:54 -03:00
commit 1aef473937
16434 changed files with 6584257 additions and 0 deletions

View File

@@ -0,0 +1,481 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
OpenTelemetry SDK Configurator for Easy Instrumentation with Distros
"""
from __future__ import annotations
import logging
import os
from abc import ABC, abstractmethod
from os import environ
from typing import Callable, Sequence, Type, Union
from typing_extensions import Literal
from opentelemetry._events import set_event_logger_provider
from opentelemetry._logs import set_logger_provider
from opentelemetry.environment_variables import (
OTEL_LOGS_EXPORTER,
OTEL_METRICS_EXPORTER,
OTEL_PYTHON_ID_GENERATOR,
OTEL_TRACES_EXPORTER,
)
from opentelemetry.metrics import set_meter_provider
from opentelemetry.sdk._events import EventLoggerProvider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, LogExporter
from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
OTEL_EXPORTER_OTLP_PROTOCOL,
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
OTEL_TRACES_SAMPLER,
OTEL_TRACES_SAMPLER_ARG,
)
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
MetricExporter,
MetricReader,
PeriodicExportingMetricReader,
)
from opentelemetry.sdk.resources import Attributes, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.sdk.trace.sampling import Sampler
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.trace import set_tracer_provider
from opentelemetry.util._importlib_metadata import entry_points
_EXPORTER_OTLP = "otlp"
_EXPORTER_OTLP_PROTO_GRPC = "otlp_proto_grpc"
_EXPORTER_OTLP_PROTO_HTTP = "otlp_proto_http"
_EXPORTER_BY_OTLP_PROTOCOL = {
"grpc": _EXPORTER_OTLP_PROTO_GRPC,
"http/protobuf": _EXPORTER_OTLP_PROTO_HTTP,
}
_EXPORTER_ENV_BY_SIGNAL_TYPE = {
"traces": OTEL_TRACES_EXPORTER,
"metrics": OTEL_METRICS_EXPORTER,
"logs": OTEL_LOGS_EXPORTER,
}
_PROTOCOL_ENV_BY_SIGNAL_TYPE = {
"traces": OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
"metrics": OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
"logs": OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
}
_RANDOM_ID_GENERATOR = "random"
_DEFAULT_ID_GENERATOR = _RANDOM_ID_GENERATOR
_OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler"
_logger = logging.getLogger(__name__)
def _import_config_components(
selected_components: list[str], entry_point_name: str
) -> Sequence[tuple[str, object]]:
component_implementations = []
for selected_component in selected_components:
try:
component_implementations.append(
(
selected_component,
next(
iter(
entry_points(
group=entry_point_name, name=selected_component
)
)
).load(),
)
)
except KeyError:
raise RuntimeError(
f"Requested entry point '{entry_point_name}' not found"
)
except StopIteration:
raise RuntimeError(
f"Requested component '{selected_component}' not found in "
f"entry point '{entry_point_name}'"
)
return component_implementations
def _get_sampler() -> str | None:
return environ.get(OTEL_TRACES_SAMPLER, None)
def _get_id_generator() -> str:
return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR)
def _get_exporter_entry_point(
exporter_name: str, signal_type: Literal["traces", "metrics", "logs"]
):
if exporter_name not in (
_EXPORTER_OTLP,
_EXPORTER_OTLP_PROTO_GRPC,
_EXPORTER_OTLP_PROTO_HTTP,
):
return exporter_name
# Checking env vars for OTLP protocol (grpc/http).
otlp_protocol = environ.get(
_PROTOCOL_ENV_BY_SIGNAL_TYPE[signal_type]
) or environ.get(OTEL_EXPORTER_OTLP_PROTOCOL)
if not otlp_protocol:
if exporter_name == _EXPORTER_OTLP:
return _EXPORTER_OTLP_PROTO_GRPC
return exporter_name
otlp_protocol = otlp_protocol.strip()
if exporter_name == _EXPORTER_OTLP:
if otlp_protocol not in _EXPORTER_BY_OTLP_PROTOCOL:
# Invalid value was set by the env var
raise RuntimeError(
f"Unsupported OTLP protocol '{otlp_protocol}' is configured"
)
return _EXPORTER_BY_OTLP_PROTOCOL[otlp_protocol]
# grpc/http already specified by exporter_name, only add a warning in case
# of a conflict.
exporter_name_by_env = _EXPORTER_BY_OTLP_PROTOCOL.get(otlp_protocol)
if exporter_name_by_env and exporter_name != exporter_name_by_env:
_logger.warning(
"Conflicting values for %s OTLP exporter protocol, using '%s'",
signal_type,
exporter_name,
)
return exporter_name
def _get_exporter_names(
signal_type: Literal["traces", "metrics", "logs"],
) -> Sequence[str]:
names = environ.get(_EXPORTER_ENV_BY_SIGNAL_TYPE.get(signal_type, ""))
if not names or names.lower().strip() == "none":
return []
return [
_get_exporter_entry_point(_exporter.strip(), signal_type)
for _exporter in names.split(",")
]
def _init_tracing(
exporters: dict[str, Type[SpanExporter]],
id_generator: IdGenerator | None = None,
sampler: Sampler | None = None,
resource: Resource | None = None,
):
provider = TracerProvider(
id_generator=id_generator,
sampler=sampler,
resource=resource,
)
set_tracer_provider(provider)
for _, exporter_class in exporters.items():
exporter_args = {}
provider.add_span_processor(
BatchSpanProcessor(exporter_class(**exporter_args))
)
def _init_metrics(
exporters_or_readers: dict[
str, Union[Type[MetricExporter], Type[MetricReader]]
],
resource: Resource | None = None,
):
metric_readers = []
for _, exporter_or_reader_class in exporters_or_readers.items():
exporter_args = {}
if issubclass(exporter_or_reader_class, MetricReader):
metric_readers.append(exporter_or_reader_class(**exporter_args))
else:
metric_readers.append(
PeriodicExportingMetricReader(
exporter_or_reader_class(**exporter_args)
)
)
provider = MeterProvider(resource=resource, metric_readers=metric_readers)
set_meter_provider(provider)
def _init_logging(
exporters: dict[str, Type[LogExporter]],
resource: Resource | None = None,
setup_logging_handler: bool = True,
):
provider = LoggerProvider(resource=resource)
set_logger_provider(provider)
for _, exporter_class in exporters.items():
exporter_args = {}
provider.add_log_record_processor(
BatchLogRecordProcessor(exporter_class(**exporter_args))
)
event_logger_provider = EventLoggerProvider(logger_provider=provider)
set_event_logger_provider(event_logger_provider)
if setup_logging_handler:
_patch_basic_config()
# Add OTel handler
handler = LoggingHandler(
level=logging.NOTSET, logger_provider=provider
)
logging.getLogger().addHandler(handler)
def _patch_basic_config():
original_basic_config = logging.basicConfig
def patched_basic_config(*args, **kwargs):
root = logging.getLogger()
has_only_otel = len(root.handlers) == 1 and isinstance(
root.handlers[0], LoggingHandler
)
if has_only_otel:
otel_handler = root.handlers.pop()
original_basic_config(*args, **kwargs)
root.addHandler(otel_handler)
else:
original_basic_config(*args, **kwargs)
logging.basicConfig = patched_basic_config
def _import_exporters(
trace_exporter_names: Sequence[str],
metric_exporter_names: Sequence[str],
log_exporter_names: Sequence[str],
) -> tuple[
dict[str, Type[SpanExporter]],
dict[str, Union[Type[MetricExporter], Type[MetricReader]]],
dict[str, Type[LogExporter]],
]:
trace_exporters = {}
metric_exporters = {}
log_exporters = {}
for (
exporter_name,
exporter_impl,
) in _import_config_components(
trace_exporter_names, "opentelemetry_traces_exporter"
):
if issubclass(exporter_impl, SpanExporter):
trace_exporters[exporter_name] = exporter_impl
else:
raise RuntimeError(f"{exporter_name} is not a trace exporter")
for (
exporter_name,
exporter_impl,
) in _import_config_components(
metric_exporter_names, "opentelemetry_metrics_exporter"
):
# The metric exporter components may be push MetricExporter or pull exporters which
# subclass MetricReader directly
if issubclass(exporter_impl, (MetricExporter, MetricReader)):
metric_exporters[exporter_name] = exporter_impl
else:
raise RuntimeError(f"{exporter_name} is not a metric exporter")
for (
exporter_name,
exporter_impl,
) in _import_config_components(
log_exporter_names, "opentelemetry_logs_exporter"
):
if issubclass(exporter_impl, LogExporter):
log_exporters[exporter_name] = exporter_impl
else:
raise RuntimeError(f"{exporter_name} is not a log exporter")
return trace_exporters, metric_exporters, log_exporters
def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]:
_, sampler_impl = _import_config_components(
[sampler_name.strip()], _OTEL_SAMPLER_ENTRY_POINT_GROUP
)[0]
return sampler_impl
def _import_sampler(sampler_name: str) -> Sampler | None:
if not sampler_name:
return None
try:
sampler_factory = _import_sampler_factory(sampler_name)
arg = None
if sampler_name in ("traceidratio", "parentbased_traceidratio"):
try:
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
except (ValueError, TypeError):
_logger.warning(
"Could not convert TRACES_SAMPLER_ARG to float. Using default value 1.0."
)
rate = 1.0
arg = rate
else:
arg = os.getenv(OTEL_TRACES_SAMPLER_ARG)
sampler = sampler_factory(arg)
if not isinstance(sampler, Sampler):
message = f"Sampler factory, {sampler_factory}, produced output, {sampler}, which is not a Sampler."
_logger.warning(message)
raise ValueError(message)
return sampler
except Exception as exc: # pylint: disable=broad-exception-caught
_logger.warning(
"Using default sampler. Failed to initialize sampler, %s: %s",
sampler_name,
exc,
)
return None
def _import_id_generator(id_generator_name: str) -> IdGenerator:
id_generator_name, id_generator_impl = _import_config_components(
[id_generator_name.strip()], "opentelemetry_id_generator"
)[0]
if issubclass(id_generator_impl, IdGenerator):
return id_generator_impl()
raise RuntimeError(f"{id_generator_name} is not an IdGenerator")
def _initialize_components(
auto_instrumentation_version: str | None = None,
trace_exporter_names: list[str] | None = None,
metric_exporter_names: list[str] | None = None,
log_exporter_names: list[str] | None = None,
sampler: Sampler | None = None,
resource_attributes: Attributes | None = None,
id_generator: IdGenerator | None = None,
setup_logging_handler: bool | None = None,
):
if trace_exporter_names is None:
trace_exporter_names = []
if metric_exporter_names is None:
metric_exporter_names = []
if log_exporter_names is None:
log_exporter_names = []
span_exporters, metric_exporters, log_exporters = _import_exporters(
trace_exporter_names + _get_exporter_names("traces"),
metric_exporter_names + _get_exporter_names("metrics"),
log_exporter_names + _get_exporter_names("logs"),
)
if sampler is None:
sampler_name = _get_sampler()
sampler = _import_sampler(sampler_name)
if id_generator is None:
id_generator_name = _get_id_generator()
id_generator = _import_id_generator(id_generator_name)
if resource_attributes is None:
resource_attributes = {}
# populate version if using auto-instrumentation
if auto_instrumentation_version:
resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = (
auto_instrumentation_version
)
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
# from the env variable else defaults to "unknown_service"
resource = Resource.create(resource_attributes)
_init_tracing(
exporters=span_exporters,
id_generator=id_generator,
sampler=sampler,
resource=resource,
)
_init_metrics(metric_exporters, resource)
if setup_logging_handler is None:
setup_logging_handler = (
os.getenv(
_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false"
)
.strip()
.lower()
== "true"
)
_init_logging(log_exporters, resource, setup_logging_handler)
class _BaseConfigurator(ABC):
"""An ABC for configurators
Configurators are used to configure
SDKs (i.e. TracerProvider, MeterProvider, Processors...)
to reduce the amount of manual configuration required.
"""
_instance = None
_is_instrumented = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kwargs)
return cls._instance
@abstractmethod
def _configure(self, **kwargs):
"""Configure the SDK"""
def configure(self, **kwargs):
"""Configure the SDK"""
self._configure(**kwargs)
class _OTelSDKConfigurator(_BaseConfigurator):
"""A basic Configurator by OTel Python for initializing OTel SDK components
Initializes several crucial OTel SDK components (i.e. TracerProvider,
MeterProvider, Processors...) according to a default implementation. Other
Configurators can subclass and slightly alter this initialization.
NOTE: This class should not be instantiated nor should it become an entry
point on the `opentelemetry-sdk` package. Instead, distros should subclass
this Configurator and enhance it as needed.
"""
def _configure(self, **kwargs):
_initialize_components(**kwargs)