Skip to content

Commit 4f3ee75

Browse files
committed
Add LoggerConfigurator
1 parent a8b7ccf commit 4f3ee75

File tree

2 files changed

+366
-24
lines changed

2 files changed

+366
-24
lines changed

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

Lines changed: 149 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import threading
2323
import traceback
2424
import warnings
25+
import fnmatch
2526
from os import environ
2627
from threading import Lock
2728
from time import time_ns
@@ -61,6 +62,8 @@
6162

6263
_logger = logging.getLogger(__name__)
6364

65+
LoggerConfigurator = Callable[[InstrumentationScope], "LoggerConfig | None"]
66+
6467
_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128
6568
_ENV_VALUE_UNSET = ""
6669

@@ -96,6 +99,77 @@ class LogDeprecatedInitWarning(UserWarning):
9699
warnings.simplefilter("once", LogDeprecatedInitWarning)
97100

98101

102+
class LoggerConfig:
103+
def __init__(
104+
self,
105+
disabled: bool = False,
106+
minimum_severity: SeverityNumber = SeverityNumber.UNSPECIFIED,
107+
trace_based: bool = False,
108+
):
109+
"""Initialize LoggerConfig with specified parameters.
110+
111+
Args:
112+
disabled: A boolean indication of whether the logger is enabled.
113+
If not explicitly set, defaults to False (i.e. Loggers are enabled by default).
114+
If True, the logger behaves equivalently to a No-op Logger.
115+
minimum_severity: A SeverityNumber indicating the minimum severity level
116+
for log records to be processed. If not explicitly set, defaults to UNSPECIFIED (0).
117+
If a log record's SeverityNumber is specified and is less than the configured
118+
minimum_severity, the log record is dropped by the Logger.
119+
trace_based: A boolean indication of whether the logger should only
120+
process log records associated with sampled traces. If not explicitly set,
121+
defaults to False. If True, log records associated with unsampled traces
122+
are dropped by the Logger.
123+
"""
124+
self.disabled = disabled
125+
self.minimum_severity = minimum_severity
126+
self.trace_based = trace_based
127+
128+
def __repr__(self):
129+
return (
130+
f"LoggerConfig(disabled={self.disabled}, "
131+
f"minimum_severity={self.minimum_severity}, "
132+
f"trace_based={self.trace_based})"
133+
)
134+
135+
136+
def create_logger_configurator_by_name(
137+
logger_configs: dict[str, LoggerConfig]
138+
) -> LoggerConfigurator:
139+
"""Create a LoggerConfigurator that selects configuration based on logger name.
140+
141+
Args:
142+
logger_configs: A dictionary mapping logger names to LoggerConfig instances.
143+
Loggers not found in this mapping will use the default config.
144+
145+
Returns:
146+
A LoggerConfigurator function that can be used with LoggerProvider.
147+
"""
148+
def configurator(scope: InstrumentationScope) -> LoggerConfig | None:
149+
return logger_configs.get(scope.name)
150+
return configurator
151+
152+
153+
def create_logger_configurator_with_pattern(
154+
patterns: list[tuple[str, LoggerConfig]]
155+
) -> LoggerConfigurator:
156+
"""Create a LoggerConfigurator that matches logger names using patterns.
157+
158+
Args:
159+
patterns: A list of (pattern, config) tuples. Patterns are matched in order,
160+
and the first match is used. Use '*' as a wildcard.
161+
162+
Returns:
163+
A LoggerConfigurator function that can be used with LoggerProvider.
164+
"""
165+
def configurator(scope: InstrumentationScope) -> LoggerConfig | None:
166+
for pattern, config in patterns:
167+
if fnmatch.fnmatch(scope.name, pattern):
168+
return config
169+
return None
170+
return configurator
171+
172+
99173
class LogLimits:
100174
"""This class is based on a SpanLimits class in the Tracing module.
101175
@@ -685,9 +759,15 @@ def __init__(
685759
ConcurrentMultiLogRecordProcessor,
686760
],
687761
instrumentation_scope: InstrumentationScope,
762+
config: LoggerConfig | None = None,
688763
min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED,
689764
trace_based: bool = False,
690765
):
766+
if config is not None:
767+
self._config = config
768+
else:
769+
self._config = LoggerConfig()
770+
691771
super().__init__(
692772
instrumentation_scope.name,
693773
instrumentation_scope.version,
@@ -704,6 +784,23 @@ def __init__(
704784
def resource(self):
705785
return self._resource
706786

787+
@property
788+
def config(self):
789+
return self._config
790+
791+
@property
792+
def instrumentation_scope(self):
793+
"""Get the instrumentation scope for this logger."""
794+
return self._instrumentation_scope
795+
796+
def update_config(self, config: LoggerConfig) -> None:
797+
"""Update the logger's configuration.
798+
799+
Args:
800+
config: The new LoggerConfig to use.
801+
"""
802+
self._config = config
803+
707804
@overload
708805
def emit(
709806
self,
@@ -766,7 +863,16 @@ def emit(
766863
if should_drop_logs_for_unsampled_traces(record, self._trace_based):
767864
return
768865

769-
log_data = LogData(record, self._instrumentation_scope)
866+
if self._config.disabled:
867+
return
868+
869+
if is_less_than_min_severity(record, self._config.minimum_severity):
870+
return
871+
872+
if should_drop_logs_for_unsampled_traces(record, self._config.trace_based):
873+
return
874+
875+
log_data = LogData(record, self._instrumentation_scope)
770876

771877
self._multi_log_record_processor.on_emit(log_data)
772878

@@ -781,6 +887,7 @@ def __init__(
781887
| None = None,
782888
min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED,
783889
trace_based: bool = False,
890+
logger_configurator: LoggerConfigurator | None = None,
784891
):
785892
if resource is None:
786893
self._resource = Resource.create({})
@@ -799,6 +906,17 @@ def __init__(
799906
self._min_severity_level = min_severity_level
800907
self._trace_based = trace_based
801908

909+
if logger_configurator is not None:
910+
self._logger_configurator = logger_configurator
911+
else:
912+
def default_configurator(scope: InstrumentationScope) -> LoggerConfig:
913+
return LoggerConfig(
914+
disabled=self._disabled,
915+
minimum_severity=self._min_severity_level,
916+
trace_based=self._trace_based,
917+
)
918+
self._logger_configurator = default_configurator
919+
802920
@property
803921
def resource(self):
804922
return self._resource
@@ -810,17 +928,24 @@ def _get_logger_no_cache(
810928
schema_url: str | None = None,
811929
attributes: _ExtendedAttributes | None = None,
812930
) -> Logger:
931+
instrumentation_scope = InstrumentationScope(
932+
name,
933+
version,
934+
schema_url,
935+
attributes,
936+
)
937+
config = self._logger_configurator(instrumentation_scope)
938+
if config is None:
939+
config = LoggerConfig(
940+
disabled=self._disabled,
941+
minimum_severity=self._min_severity_level,
942+
trace_based=self._trace_based,
943+
)
813944
return Logger(
814945
self._resource,
815946
self._multi_log_record_processor,
816-
InstrumentationScope(
817-
name,
818-
version,
819-
schema_url,
820-
attributes,
821-
),
822-
self._min_severity_level,
823-
self._trace_based,
947+
instrumentation_scope,
948+
config=config,
824949
)
825950

826951
def _get_logger_cached(
@@ -868,6 +993,21 @@ def add_log_record_processor(
868993
log_record_processor
869994
)
870995

996+
def set_logger_configurator(self, configurator: LoggerConfigurator) -> None:
997+
"""Update the logger configurator and apply the new configuration to all existing loggers.
998+
"""
999+
with self._logger_cache_lock:
1000+
self._logger_configurator = configurator
1001+
for logger in self._logger_cache.values():
1002+
new_config = configurator(logger.instrumentation_scope)
1003+
if new_config is None:
1004+
new_config = LoggerConfig(
1005+
disabled=self._disabled,
1006+
minimum_severity=self._min_severity_level,
1007+
trace_based=self._trace_based,
1008+
)
1009+
logger.update_config(new_config)
1010+
8711011
def shutdown(self):
8721012
"""Shuts down the log processors."""
8731013
self._multi_log_record_processor.shutdown()

0 commit comments

Comments
 (0)