Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
TracerProvider,
_default_tracer_configurator,
_RuleBasedTracerConfigurator,
_scope_name_matches_glob,
_TracerConfig,
sampling,
)
from opentelemetry.sdk.util.instrumentation import _scope_name_matches_glob

tracer = TracerProvider(
sampler=sampling.DEFAULT_ON,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
LoggingHandler,
LogRecordProcessor,
)
from opentelemetry.sdk._logs._internal import LoggerConfiguratorT
from opentelemetry.sdk._logs.export import (
BatchLogRecordProcessor,
LogRecordExporter,
Expand All @@ -52,6 +53,7 @@
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
OTEL_EXPORTER_OTLP_PROTOCOL,
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
OTEL_PYTHON_LOGGER_CONFIGURATOR,
OTEL_PYTHON_TRACER_CONFIGURATOR,
OTEL_TRACES_SAMPLER,
OTEL_TRACES_SAMPLER_ARG,
Expand Down Expand Up @@ -171,6 +173,10 @@ def _get_tracer_configurator() -> str | None:
return environ.get(OTEL_PYTHON_TRACER_CONFIGURATOR, None)


def _get_logger_configurator() -> str | None:
return environ.get(OTEL_PYTHON_LOGGER_CONFIGURATOR, None)


def _get_exporter_entry_point(
exporter_name: str, signal_type: Literal["traces", "metrics", "logs"]
):
Expand Down Expand Up @@ -294,8 +300,11 @@ def _init_logging(
log_record_processors: Sequence[LogRecordProcessor] | None = None,
export_log_record_processor: _ConfigurationExporterLogRecordProcessorT
| None = None,
logger_configurator: LoggerConfiguratorT | None = None,
):
provider = LoggerProvider(resource=resource)
provider = LoggerProvider(
resource=resource, logger_configurator=logger_configurator
)
set_logger_provider(provider)

exporter_args_map = exporter_args_map or {}
Expand Down Expand Up @@ -366,6 +375,27 @@ def overwritten_config_fn(*args, **kwargs):
logging.basicConfig = wrapper(logging.basicConfig)


def _import_logger_configurator(
logger_configurator_name: str | None,
) -> LoggerConfiguratorT | None:
if not logger_configurator_name:
return None

try:
_, logger_configurator_impl = _import_config_components(
[logger_configurator_name.strip()],
"_opentelemetry_logger_configurator",
)[0]
except Exception as exc: # pylint: disable=broad-exception-caught
_logger.warning(
"Using default logger configurator. Failed to load logger configurator, %s: %s",
logger_configurator_name,
exc,
)
return None
return logger_configurator_impl


def _import_tracer_configurator(
tracer_configurator_name: str | None,
) -> _TracerConfiguratorT | None:
Expand Down Expand Up @@ -507,6 +537,7 @@ def _initialize_components(
export_log_record_processor: _ConfigurationExporterLogRecordProcessorT
| None = None,
tracer_configurator: _TracerConfiguratorT | None = None,
logger_configurator: LoggerConfiguratorT | None = None,
):
# pylint: disable=too-many-locals
if trace_exporter_names is None:
Expand Down Expand Up @@ -538,6 +569,11 @@ def _initialize_components(
tracer_configurator = _import_tracer_configurator(
tracer_configurator_name
)
if logger_configurator is None:
logger_configurator_name = _get_logger_configurator()
logger_configurator = _import_logger_configurator(
logger_configurator_name
)

# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
# from the env variable else defaults to "unknown_service"
Expand Down Expand Up @@ -572,6 +608,7 @@ def _initialize_components(
exporter_args_map=exporter_args_map,
log_record_processors=log_record_processors,
export_log_record_processor=export_log_record_processor,
logger_configurator=logger_configurator,
)


Expand Down
120 changes: 109 additions & 11 deletions opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@
import threading
import traceback
import warnings
from _weakrefset import WeakSet
from dataclasses import dataclass, field
from os import environ
from threading import Lock
from time import time_ns
from typing import Any, Callable, Tuple, Union, cast, overload # noqa
from typing import ( # noqa
Any,
Callable,
Sequence,
Tuple,
Union,
cast,
overload,
)

from typing_extensions import deprecated

Expand All @@ -51,7 +60,10 @@
)
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.util import ns_to_iso_str
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
from opentelemetry.sdk.util.instrumentation import (
InstrumentationScope,
_InstrumentationScopePredicateT,
)
from opentelemetry.semconv._incubating.attributes import code_attributes
from opentelemetry.semconv.attributes import exception_attributes
from opentelemetry.trace import (
Expand Down Expand Up @@ -631,6 +643,15 @@ def flush(self) -> None:
thread.start()


@dataclass
class LoggerConfig:
is_enabled: bool = True

@classmethod
def default(cls) -> "LoggerConfig":
return LoggerConfig()


class Logger(APILogger):
def __init__(
self,
Expand All @@ -642,6 +663,7 @@ def __init__(
instrumentation_scope: InstrumentationScope,
*,
logger_metrics: LoggerMetrics,
logger_config: LoggerConfig,
):
super().__init__(
instrumentation_scope.name,
Expand All @@ -653,6 +675,17 @@ def __init__(
self._multi_log_record_processor = multi_log_record_processor
self._instrumentation_scope = instrumentation_scope
self._logger_metrics = logger_metrics
self._logger_config = logger_config

def _is_enabled(self) -> bool:
return self._logger_config.is_enabled

def set_logger_config(self, logger_config: LoggerConfig) -> None:
self._logger_config = logger_config

@property
def instrumentation_scope(self):
return self._instrumentation_scope

@property
def resource(self):
Expand All @@ -675,6 +708,8 @@ def emit(
"""Emits the :class:`ReadWriteLogRecord` by setting instrumentation scope
and forwarding to the processor.
"""
if not self._is_enabled():
return
# If a record is provided, use it directly
if record is not None:
if not isinstance(record, ReadWriteLogRecord):
Expand Down Expand Up @@ -709,6 +744,42 @@ def emit(
self._multi_log_record_processor.on_emit(writable_record)


LoggerConfiguratorT = Callable[[InstrumentationScope], LoggerConfig]
LoggerConfiguratorRulesT = Sequence[
tuple[_InstrumentationScopePredicateT, LoggerConfig]
]


def default_logger_configurator(
_logger_scope: InstrumentationScope,
) -> LoggerConfig:
return LoggerConfig.default()


def disable_logger_configurator(
_logger_scope: InstrumentationScope,
) -> LoggerConfig:
return LoggerConfig(is_enabled=False)


class RuleBasedLoggerConfigurator:
def __init__(
self,
*,
rules: LoggerConfiguratorRulesT,
default_config: LoggerConfig,
):
self._rules = rules
self._default_config = default_config

def __call__(self, logger_scope: InstrumentationScope) -> LoggerConfig:
for predicate, logger_config in self._rules:
if predicate(logger_scope):
return logger_config
# by default return default config
return self._default_config


class LoggerProvider(APILoggerProvider):
def __init__(
self,
Expand All @@ -719,6 +790,7 @@ def __init__(
| None = None,
*,
meter_provider: MeterProvider | None = None,
logger_configurator: LoggerConfiguratorT | None = None,
):
if resource is None:
self._resource = Resource.create({})
Expand All @@ -732,11 +804,14 @@ def __init__(
)
disabled = environ.get(OTEL_SDK_DISABLED, "")
self._disabled = disabled.lower().strip() == "true"
self._logger_configurator = logger_configurator
self._at_exit_handler = None
if shutdown_on_exit:
self._at_exit_handler = atexit.register(self.shutdown)
self._logger_cache = {}
self._logger_cache_lock = Lock()
self._active_loggers = WeakSet()
self._active_loggers_lock = Lock()

@property
def resource(self):
Expand All @@ -749,16 +824,14 @@ def _get_logger_no_cache(
schema_url: str | None = None,
attributes: _ExtendedAttributes | None = None,
) -> Logger:
scope = InstrumentationScope(name, version, schema_url, attributes)

return Logger(
self._resource,
self._multi_log_record_processor,
InstrumentationScope(
name,
version,
schema_url,
attributes,
),
scope,
logger_metrics=self._logger_metrics,
logger_config=self._logger_configurator(scope),
)

def _get_logger_cached(
Expand Down Expand Up @@ -791,9 +864,16 @@ def get_logger(
schema_url=schema_url,
attributes=attributes,
)
if attributes is None:
return self._get_logger_cached(name, version, schema_url)
return self._get_logger_no_cache(name, version, schema_url, attributes)
logger = (
self._get_logger_cached(name, version, schema_url)
if attributes is None
else self._get_logger_no_cache(
name, version, schema_url, attributes
)
)
with self._active_loggers_lock:
self._active_loggers.add(logger)
return logger

def add_log_record_processor(
self, log_record_processor: LogRecordProcessor
Expand All @@ -806,6 +886,24 @@ def add_log_record_processor(
log_record_processor
)

def set_logger_configurator(
self, *, logger_configurator: LoggerConfiguratorT
):
"""Set a new LoggerConfigurator for this LoggerProvider.

Setting a new LoggerConfigurator will result in the configurator being called
for each outstanding Logger and for any newly created loggers thereafter.
Therefore, it is important that the provided function returns quickly.
"""
self._logger_configurator = logger_configurator
with self._active_loggers_lock:
for logger in self._active_loggers:
if not isinstance(logger, Logger):
continue
logger.set_logger_config(
self._logger_configurator(logger.instrumentation_scope)
)

def shutdown(self):
"""Shuts down the log processors."""
self._multi_log_record_processor.shutdown()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -814,3 +814,15 @@ def channel_credential_provider() -> grpc.ChannelCredentials:
This is an experimental environment variable and the name of this variable and its behavior can
change in a non-backwards compatible way.
"""

OTEL_PYTHON_LOGGER_CONFIGURATOR = "OTEL_PYTHON_LOGGER_CONFIGURATOR"
"""
.. envvar:: OTEL_PYTHON_LOGGER_CONFIGURATOR

The :envvar:`OTEL_PYTHON_LOGGER_CONFIGURATOR` environment variable allows users to set a
custom Logger Configurator function.
Default: opentelemetry.sdk._logs._internal._default_logger_configurator

This is an experimental environment variable and the name of this variable and its behavior can
change in a non-backwards compatible way.
"""
13 changes: 1 addition & 12 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import abc
import atexit
import concurrent.futures
import fnmatch
import json
import logging
import os
Expand Down Expand Up @@ -70,6 +69,7 @@
from opentelemetry.sdk.util.instrumentation import (
InstrumentationInfo,
InstrumentationScope,
_InstrumentationScopePredicateT,
)
from opentelemetry.semconv.attributes.exception_attributes import (
EXCEPTION_ESCAPED,
Expand Down Expand Up @@ -1262,22 +1262,11 @@ def start_span( # pylint: disable=too-many-locals


_TracerConfiguratorT = Callable[[InstrumentationScope], _TracerConfig]
_InstrumentationScopePredicateT = Callable[[InstrumentationScope], bool]
_TracerConfiguratorRulesT = Sequence[
tuple[_InstrumentationScopePredicateT, _TracerConfig]
]


# TODO: share this with configurators for other signals
def _scope_name_matches_glob(
glob_pattern: str,
) -> _InstrumentationScopePredicateT:
def inner(scope: InstrumentationScope) -> bool:
return fnmatch.fnmatch(scope.name, glob_pattern)

return inner


class _RuleBasedTracerConfigurator:
def __init__(
self,
Expand Down
Loading
Loading