2222import threading
2323import traceback
2424import warnings
25+ from _weakrefset import WeakSet
2526from dataclasses import dataclass , field
2627from os import environ
2728from threading import Lock
2829from 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
3140from typing_extensions import deprecated
3241
5160)
5261from opentelemetry .sdk .resources import Resource
5362from 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+ )
5567from opentelemetry .semconv ._incubating .attributes import code_attributes
5668from opentelemetry .semconv .attributes import exception_attributes
5769from 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+
640661class 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+
718789class 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 ()
0 commit comments