55import json
66import logging
77import os
8- import sys
98import time
109
1110import flask
12- from opencensus .ext .azure .log_exporter import AzureLogHandler
13- from opencensus .ext .azure .trace_exporter import AzureExporter
14- from opencensus .trace import samplers
15- from opencensus .trace .span import SpanKind
16- from opencensus .trace .tracer import Tracer
11+ from azure .monitor .opentelemetry .exporter import AzureMonitorLogExporter , AzureMonitorTraceExporter
12+ from opentelemetry .sdk .resources import get_aggregated_resources , ProcessResourceDetector
13+ from opentelemetry .semconv .resource import ResourceAttributes
14+ from opentelemetry import trace
15+ from opentelemetry .sdk .trace import TracerProvider , Resource
16+ from opentelemetry .sdk .trace .export import BatchSpanProcessor
17+ from opentelemetry .sdk ._logs import LoggingHandler , LoggerProvider
18+ from opentelemetry .sdk ._logs .export import BatchLogRecordProcessor
19+ from opentelemetry ._logs import set_logger_provider , get_logger_provider
20+ from opentelemetry .sdk .trace .sampling import ALWAYS_ON
1721
1822from .config import config
1923
@@ -40,35 +44,67 @@ def __init__(self):
4044 if config .app_insights_enabled and config .app_insights_key :
4145 try :
4246 instrumentation_key = config .app_insights_key .get_secret_value ()
43- self .azureLogHandler = AzureLogHandler (
44- instrumentation_key = instrumentation_key ,
45- export_interval = AppInsightsClient .send_interval ,
46- max_batch_size = AppInsightsClient .send_buffer_size ,
47- )
48- logger .addHandler (self .azureLogHandler )
49- logging .getLogger ("azmlinfsrv.print" ).addHandler (self .azureLogHandler )
50- azureExporter = AzureExporter (
51- instrumentation_key = instrumentation_key ,
52- export_interval = AppInsightsClient .send_interval ,
53- max_batch_size = AppInsightsClient .send_buffer_size ,
54- )
55- self .tracer = Tracer (
56- exporter = azureExporter ,
57- sampler = samplers .AlwaysOnSampler (),
47+ connection_string = f"InstrumentationKey={ instrumentation_key } "
48+
49+ resource = get_aggregated_resources (
50+ detectors = [ProcessResourceDetector ()],
51+ initial_resource = Resource .create (
52+ attributes = {ResourceAttributes .SERVICE_NAME : config .service_name }
53+ ),
5854 )
59- self ._container_id = config .hostname
60- self .enabled = True
55+
56+ # Initialize OpenTelemetry logging
57+ self .init_otel_log (connection_string , resource )
58+
59+ # Initialize OpenTelemetry tracing
60+ self .init_otel_trace (connection_string , resource )
61+
6162 except Exception as ex :
6263 self .log_app_insights_exception (ex )
6364
65+ def init_otel_trace (self , connection_string , resource ):
66+
67+ # Setup tracer provider and exporter
68+ tracer_provider = TracerProvider (sampler = ALWAYS_ON , resource = resource )
69+ trace .set_tracer_provider (tracer_provider )
70+ trace_exporter = AzureMonitorTraceExporter (
71+ connection_string = connection_string ,
72+ send_interval = AppInsightsClient .send_interval ,
73+ send_buffer_size = AppInsightsClient .send_buffer_size ,
74+ )
75+ trace .get_tracer_provider ().add_span_processor (BatchSpanProcessor (trace_exporter ))
76+
77+ # Set up tracer
78+ self .tracer = trace .get_tracer (__name__ )
79+ self ._container_id = config .hostname
80+ self .enabled = True
81+
82+ def init_otel_log (self , connection_string , resource ):
83+
84+ # Setup logger provider and exporter
85+ logger_provider = LoggerProvider (resource = resource )
86+ set_logger_provider (logger_provider )
87+ log_exporter = AzureMonitorLogExporter (connection_string = connection_string )
88+ log_processor = BatchLogRecordProcessor (
89+ exporter = log_exporter ,
90+ schedule_delay_millis = AppInsightsClient .send_interval * 1000 ,
91+ max_export_batch_size = AppInsightsClient .send_buffer_size ,
92+ )
93+ get_logger_provider ().add_log_record_processor (log_processor )
94+
95+ # Add log handler
96+ self .azureLogHandler = LoggingHandler (level = logging .INFO )
97+ logger .addHandler (self .azureLogHandler )
98+ logging .getLogger ("azmlinfsrv.print" ).addHandler (self .azureLogHandler )
99+
64100 def close (self ):
65101 if self .azureLogHandler :
66102 logger .removeHandler (self .azureLogHandler )
67103 logging .getLogger ("azmlinfsrv.print" ).removeHandler (self .azureLogHandler )
68104
69- def log_app_insights_exception (self , ex ) :
70- print ( "Error logging to Application Insights:" )
71- print ( ex )
105+ def log_app_insights_exception (self , ex : Exception ) -> None :
106+ """Log exceptions to Application Insights."""
107+ logger . error ( "Error logging to Application Insights:" , exc_info = ex )
72108
73109 def send_model_data_log (self , request_id , client_request_id , model_input , prediction ):
74110 try :
@@ -110,10 +146,9 @@ def log_request(
110146 except (UnicodeDecodeError , AttributeError ) as ex :
111147 self .log_app_insights_exception (ex )
112148 response_value = "Scoring request response payload is a non serializable object or raw binary"
113-
114- # We have to encode the response value (which is a string) as a JSON to maintain backwards compatibility.
115- # This encodes '{"a": 12}' as '"{\\"a\\": 12}"'
116- response_value = json .dumps (response_value )
149+ # We have to encode the response value (which is string) as a JSON to maintain backwards compatibility.
150+ # This encodes '{"a": 12}' as '"{\\"a\\": 12}"'
151+ response_value = json .dumps (response_value )
117152 else :
118153 response_value = None
119154
@@ -137,12 +172,11 @@ def log_request(
137172 }
138173
139174 # Send the log to the requests table
140- with self .tracer .span (name = request .path ) as span :
141- span .span_id = request_id
142- span .start_time = formatted_start_time
143- span .attributes = attributes
144- span .span_kind = SpanKind .SERVER
175+ with self .tracer .start_as_current_span (request .path , kind = trace .SpanKind .SERVER ) as span :
176+ for key , value in attributes .items ():
177+ span .set_attribute (key , value )
145178 except Exception as ex :
179+ logger .error ("Error while logging request" , exc_info = True )
146180 self .log_app_insights_exception (ex )
147181
148182 def send_exception_log (self , exc_info , request_id = "Unknown" , client_request_id = "" ):
@@ -177,16 +211,27 @@ def _get_model_ids(self):
177211 # Model information is stored in /var/azureml-app/model_config_map.json in AKS deployments. But, in ACI
178212 # deployments, that file does not exist due to a bug in container build-out code. Until the bug is fixed
179213 # /var/azureml-app/azureml-models will be used to enumerate all the models.
214+ # For single model setup, config.azureml_model_dir points
215+ # to /var/azureml-app/azureml-models/$MODEL_NAME/$VERSION
216+ # For multiple model setup, it points to /var/azureml-app/azureml-models
180217 model_ids = []
181218 try :
182- models = [str (model ) for model in os .listdir (config .azureml_model_dir )]
183-
184- for model in models :
185- versions = [int (version ) for version in os .listdir (os .path .join (config .azureml_model_dir , model ))]
186- ids = ["{}:{}" .format (model , version ) for version in versions ]
187- model_ids .extend (ids )
219+ if not config .azureml_model_dir or not os .path .exists (config .azureml_model_dir ):
220+ logger .warning ("Model directory is not set or does not exist: %s" , config .azureml_model_dir )
221+ return model_ids
222+ elif (os .path .basename (config .azureml_model_dir )).isdigit ():
223+ model_name = os .path .basename (os .path .dirname (config .azureml_model_dir ))
224+ model_version = os .path .basename (config .azureml_model_dir )
225+ model_ids = ["{}:{}" .format (model_name , model_version )]
226+ return model_ids
227+ else :
228+ models = [str (model ) for model in os .listdir (config .azureml_model_dir )]
229+ for model in models :
230+ versions = [int (version ) for version in os .listdir (os .path .join (config .azureml_model_dir , model ))]
231+ ids = ["{}:{}" .format (model , version ) for version in versions ]
232+ model_ids .extend (ids )
188233 except Exception :
189- self . send_exception_log ( sys . exc_info () )
234+ logger . exception ( "Error while fetching model IDs" )
190235
191236 return model_ids
192237
0 commit comments