77from azure .monitor .opentelemetry .exporter import AzureMonitorLogExporter
88from opentelemetry .sdk ._logs import LoggerProvider , LoggingHandler
99from opentelemetry .sdk ._logs .export import BatchLogRecordProcessor
10- from opentelemetry .sdk .resources import Resource , ResourceAttributes
10+ from opentelemetry .sdk .resources import Resource
1111from opentelemetry ._logs import set_logger_provider
1212
1313
1414from common .telemetry .log_classes import LogProperties
1515
16- resource = Resource .create ({ResourceAttributes . SERVICE_NAME : "telemetry" })
16+ DEFAULT_RESOURCE = Resource .create ({"resource.name" : "telemetry" })
1717
1818class LogEvent (Enum ):
1919 REQUEST_RECEIVED = "Request.Received"
@@ -28,40 +28,86 @@ class ConsoleLogFilter(logging.Filter):
2828 def __init__ (self ):
2929 super ().__init__ ()
3030 self .base_dir = os .path .abspath (os .path .join (__file__ , ".." , ".." ))
31+
3132 # Define allowed third-party loggers (only show WARNING and above)
3233 self .allowed_third_party = [
3334 "semantic_kernel" ,
34- "agent_framework" ,
35+ "agent_framework" ,
3536 "azure_mcp"
3637 ]
37-
38+
3839 def filter (self , record ):
3940 # Always allow logs from our application
4041 if os .path .abspath (record .pathname ).startswith (self .base_dir ):
4142 return True
42-
43+
4344 # For third-party libraries, only show WARNING and above to reduce noise
4445 for allowed in self .allowed_third_party :
4546 if record .name .startswith (allowed ):
4647 return record .levelno >= logging .WARNING
47-
48+
4849 # Filter out everything else
4950 return False
5051
5152class AppLogger :
52- def __init__ (self , connection_string : str ):
53- self .connection_string = connection_string
53+ def __init__ (self , connection_string : str = None , logger : logging .Logger = None , resource : Resource = None ):
54+ """
55+ Initialize AppLogger with either a connection string or an existing logger.
5456
55- logging .getLogger ("azure.identity" ).setLevel (logging .WARNING )
56- logging .getLogger ("azure.core.pipeline.policies" ).setLevel (logging .WARNING )
57- logging .getLogger ("azure.monitor.opentelemetry.exporter.export" ).setLevel (logging .WARNING )
57+ Args:
58+ connection_string: Azure Monitor connection string for telemetry (optional if logger is provided)
59+ logger: Existing logger instance to wrap (optional if connection_string is provided)
60+ resource: Resource describing the service (optional)
61+ """
62+ if logger is not None :
63+ # Initialize from existing logger
64+ self .connection_string = connection_string
65+ self .logger = logger
66+ self .logger_provider = LoggerProvider (resource or DEFAULT_RESOURCE )
67+ self ._from_existing_logger = True
68+
69+ self .logger .setLevel (logging .INFO )
70+
71+ # Set up third-party logger levels
72+ logging .getLogger ("azure.identity" ).setLevel (logging .WARNING )
73+ logging .getLogger ("azure.core.pipeline.policies" ).setLevel (logging .WARNING )
74+ logging .getLogger ("azure.monitor.opentelemetry.exporter.export" ).setLevel (logging .WARNING )
75+
76+ # Only initialize Azure Monitor if connection string is provided
77+ if connection_string :
78+ self .initialize_loggers ()
79+ else :
80+ # Original initialization path
81+ if connection_string is None :
82+ raise ValueError ("Either connection_string or logger must be provided" )
83+
84+ self .connection_string = connection_string
85+ self ._from_existing_logger = False
86+
87+ logging .getLogger ("azure.identity" ).setLevel (logging .WARNING )
88+ logging .getLogger ("azure.core.pipeline.policies" ).setLevel (logging .WARNING )
89+ logging .getLogger ("azure.monitor.opentelemetry.exporter.export" ).setLevel (logging .WARNING )
90+
91+ self .logger = logging .getLogger ()
92+ self .logger .setLevel (logging .INFO )
93+ self .logger_provider = LoggerProvider (resource )
94+
95+ self .initialize_loggers ()
96+
97+ @classmethod
98+ def from_logger (cls , logger : logging .Logger , connection_string : str = None , resource : Resource = None ) -> "AppLogger" :
99+ """
100+ Create an AppLogger instance from an existing logger.
58101
59- self .logger = logging .getLogger ()
60- self .logger .setLevel (logging .INFO )
61- self .logger_provider = LoggerProvider (resource )
102+ Args:
103+ logger: Existing logger instance to wrap
104+ connection_string: Optional Azure Monitor connection string for telemetry
105+
106+ Returns:
107+ AppLogger instance that wraps the provided logger
108+ """
109+ return cls (connection_string = connection_string , logger = logger , resource = resource )
62110
63- self .initialize_loggers ()
64-
65111 def initialize_loggers (self ):
66112 if self .connection_string :
67113 if not any (
@@ -73,7 +119,7 @@ def initialize_loggers(self):
73119 self .handler = LoggingHandler ()
74120 self .logger .addHandler (self .handler )
75121
76- # add console logger if it is not already added by another instance of CustomLogger
122+ # Only add console handler if we're not using an existing logger or if no StreamHandler exists
77123 if not any (
78124 isinstance (handler , logging .StreamHandler )
79125 for handler in self .logger .handlers
@@ -85,7 +131,9 @@ def initialize_loggers(self):
85131 console_handler .setLevel (log_level )
86132 console_handler .addFilter (ConsoleLogFilter ())
87133 self .logger .addHandler (console_handler )
88- set_logger_provider (self .logger_provider )
134+
135+ if not self ._from_existing_logger :
136+ set_logger_provider (self .logger_provider )
89137
90138 def info (self , message :str , properties : dict = None ):
91139 self .logger .info (message )
0 commit comments