2222import threading
2323import traceback
2424import warnings
25+ import fnmatch
2526from os import environ
2627from threading import Lock
2728from time import time_ns
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):
9699warnings .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+
99173class 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