Skip to content

Commit b5ec117

Browse files
committed
feat: add experimental LoggerConfigurator
1 parent e301732 commit b5ec117

2 files changed

Lines changed: 109 additions & 23 deletions

File tree

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,20 @@
2222
import threading
2323
import traceback
2424
import warnings
25+
from _weakrefset import WeakSet
2526
from dataclasses import dataclass, field
2627
from os import environ
2728
from threading import Lock
2829
from time import time_ns
29-
from typing import Any, Callable, Tuple, Union, cast, overload # noqa
30+
from typing import ( # noqa
31+
Any,
32+
Callable,
33+
Sequence,
34+
Tuple,
35+
Union,
36+
cast,
37+
overload,
38+
)
3039

3140
from typing_extensions import deprecated
3241

@@ -51,7 +60,10 @@
5160
)
5261
from opentelemetry.sdk.resources import Resource
5362
from opentelemetry.sdk.util import ns_to_iso_str
54-
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
63+
from opentelemetry.sdk.util.instrumentation import (
64+
InstrumentationScope,
65+
_InstrumentationScopePredicateT,
66+
)
5567
from opentelemetry.semconv._incubating.attributes import code_attributes
5668
from opentelemetry.semconv.attributes import exception_attributes
5769
from opentelemetry.trace import (
@@ -637,6 +649,15 @@ def flush(self) -> None:
637649
thread.start()
638650

639651

652+
@dataclass
653+
class LoggerConfig:
654+
is_enabled: bool = True
655+
656+
@classmethod
657+
def default(cls) -> "LoggerConfig":
658+
return LoggerConfig()
659+
660+
640661
class Logger(APILogger):
641662
def __init__(
642663
self,
@@ -648,6 +669,7 @@ def __init__(
648669
instrumentation_scope: InstrumentationScope,
649670
*,
650671
logger_metrics: LoggerMetrics,
672+
logger_config: LoggerConfig,
651673
):
652674
super().__init__(
653675
instrumentation_scope.name,
@@ -659,6 +681,17 @@ def __init__(
659681
self._multi_log_record_processor = multi_log_record_processor
660682
self._instrumentation_scope = instrumentation_scope
661683
self._logger_metrics = logger_metrics
684+
self._logger_config = logger_config
685+
686+
def _is_enabled(self) -> bool:
687+
return self._logger_config.is_enabled
688+
689+
def set_logger_config(self, logger_config: LoggerConfig) -> None:
690+
self._logger_config = logger_config
691+
692+
@property
693+
def instrumentation_scope(self):
694+
return self._instrumentation_scope
662695

663696
@property
664697
def resource(self):
@@ -681,6 +714,8 @@ def emit(
681714
"""Emits the :class:`ReadWriteLogRecord` by setting instrumentation scope
682715
and forwarding to the processor.
683716
"""
717+
if not self._is_enabled():
718+
return
684719
# If a record is provided, use it directly
685720
if record is not None:
686721
if not isinstance(record, ReadWriteLogRecord):
@@ -715,6 +750,42 @@ def emit(
715750
self._multi_log_record_processor.on_emit(writable_record)
716751

717752

753+
LoggerConfiguratorT = Callable[[InstrumentationScope], LoggerConfig]
754+
LoggerConfiguratorRulesT = Sequence[
755+
tuple[_InstrumentationScopePredicateT, LoggerConfig]
756+
]
757+
758+
759+
def default_logger_configurator(
760+
_logger_scope: InstrumentationScope,
761+
) -> LoggerConfig:
762+
return LoggerConfig.default()
763+
764+
765+
def disable_logger_configurator(
766+
_logger_scope: InstrumentationScope,
767+
) -> LoggerConfig:
768+
return LoggerConfig(is_enabled=False)
769+
770+
771+
class RuleBasedLoggerConfigurator:
772+
def __init__(
773+
self,
774+
*,
775+
rules: LoggerConfiguratorRulesT,
776+
default_config: LoggerConfig,
777+
):
778+
self._rules = rules
779+
self._default_config = default_config
780+
781+
def __call__(self, meter_scope: InstrumentationScope) -> LoggerConfig:
782+
for predicate, meter_config in self._rules:
783+
if predicate(meter_scope):
784+
return meter_config
785+
# by default return default config
786+
return self._default_config
787+
788+
718789
class LoggerProvider(APILoggerProvider):
719790
def __init__(
720791
self,
@@ -725,6 +796,7 @@ def __init__(
725796
| None = None,
726797
*,
727798
meter_provider: MeterProvider | None = None,
799+
logger_configurator: LoggerConfiguratorT | None = None,
728800
):
729801
if resource is None:
730802
self._resource = Resource.create({})
@@ -738,11 +810,14 @@ def __init__(
738810
)
739811
disabled = environ.get(OTEL_SDK_DISABLED, "")
740812
self._disabled = disabled.lower().strip() == "true"
813+
self._logger_configurator = logger_configurator
741814
self._at_exit_handler = None
742815
if shutdown_on_exit:
743816
self._at_exit_handler = atexit.register(self.shutdown)
744817
self._logger_cache = {}
745818
self._logger_cache_lock = Lock()
819+
self._active_loggers = WeakSet()
820+
self._active_loggers_lock = Lock()
746821

747822
@property
748823
def resource(self):
@@ -755,16 +830,14 @@ def _get_logger_no_cache(
755830
schema_url: str | None = None,
756831
attributes: _ExtendedAttributes | None = None,
757832
) -> Logger:
833+
scope = InstrumentationScope(name, version, schema_url, attributes)
834+
758835
return Logger(
759836
self._resource,
760837
self._multi_log_record_processor,
761-
InstrumentationScope(
762-
name,
763-
version,
764-
schema_url,
765-
attributes,
766-
),
838+
scope,
767839
logger_metrics=self._logger_metrics,
840+
logger_config=self._logger_configurator(scope),
768841
)
769842

770843
def _get_logger_cached(
@@ -797,9 +870,16 @@ def get_logger(
797870
schema_url=schema_url,
798871
attributes=attributes,
799872
)
800-
if attributes is None:
801-
return self._get_logger_cached(name, version, schema_url)
802-
return self._get_logger_no_cache(name, version, schema_url, attributes)
873+
logger = (
874+
self._get_logger_cached(name, version, schema_url)
875+
if attributes is None
876+
else self._get_logger_no_cache(
877+
name, version, schema_url, attributes
878+
)
879+
)
880+
with self._active_loggers_lock:
881+
self._active_loggers.add(logger)
882+
return logger
803883

804884
def add_log_record_processor(
805885
self, log_record_processor: LogRecordProcessor
@@ -812,6 +892,24 @@ def add_log_record_processor(
812892
log_record_processor
813893
)
814894

895+
def set_logger_configurator(
896+
self, *, logger_configurator: LoggerConfiguratorT
897+
):
898+
"""Set a new LoggerConfigurator for this LoggerProvider.
899+
900+
Setting a new LoggerConfigurator will result in the configurator being called
901+
for each outstanding Logger and for any newly created loggers thereafter.
902+
Therefore, it is important that the provided function returns quickly.
903+
"""
904+
self._logger_configurator = logger_configurator
905+
with self._active_loggers_lock:
906+
for logger in self._active_loggers:
907+
if not isinstance(logger, Logger):
908+
continue
909+
logger.set_logger_config(
910+
self._logger_configurator(logger.instrumentation_scope)
911+
)
912+
815913
def shutdown(self) -> None:
816914
"""Shuts down the log processors."""
817915
self._multi_log_record_processor.shutdown()

opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -814,15 +814,3 @@ def channel_credential_provider() -> grpc.ChannelCredentials:
814814
This is an experimental environment variable and the name of this variable and its behavior can
815815
change in a non-backwards compatible way.
816816
"""
817-
818-
OTEL_PYTHON_METER_CONFIGURATOR = "OTEL_PYTHON_METER_CONFIGURATOR"
819-
"""
820-
.. envvar:: OTEL_PYTHON_METER_CONFIGURATOR
821-
822-
The :envvar:`OTEL_PYTHON_METER_CONFIGURATOR` environment variable allows users to set a
823-
custom Meter Configurator function.
824-
Default: opentelemetry.sdk.metrics._internal._default_meter_configurator
825-
826-
This is an experimental environment variable and the name of this variable and its behavior can
827-
change in a non-backwards compatible way.
828-
"""

0 commit comments

Comments
 (0)