1313# limitations under the License.
1414
1515import sys
16+ import logging
1617from contextlib import contextmanager
1718
1819from opentelemetry import trace as otel_api_trace
20+
1921from newrelic .api .application import application_instance , register_application
20- from newrelic .api .web_transaction import WebTransaction
2122from newrelic .api .background_task import BackgroundTask
22- from newrelic .api .message_transaction import MessageTransaction
23- from newrelic .api .function_trace import FunctionTrace
2423from newrelic .api .datastore_trace import DatastoreTrace
25- from newrelic .api .message_trace import MessageTrace
2624from newrelic .api .external_trace import ExternalTrace
25+ from newrelic .api .function_trace import FunctionTrace
26+ from newrelic .api .message_trace import MessageTrace
27+ from newrelic .api .message_transaction import MessageTransaction
2728from newrelic .api .time_trace import current_trace , notice_error
28- from newrelic .api .transaction import (
29- current_transaction ,
30- record_custom_metric ,
31- record_dimensional_metric ,
32- Sentinel ,
33- )
29+ from newrelic .api .transaction import Sentinel , current_transaction
30+ from newrelic .api .web_transaction import WebTransaction
31+ from newrelic .core .otlp_utils import create_resource
32+
33+ _logger = logging .getLogger (__name__ )
3434
3535# Attributes that help distinguish span types are
3636# sometimes added after span creation and sometimes
6464# Custom OTel Spans and Traces
6565# ----------------------------------------------
6666
67- # TracerProvider: we can think of this as the agent instance. Only one can exist (in both NR and Otel)
67+ # TracerProvider: we can think of this as the agent instance. Only one can exist
6868# SpanProcessor: we can think of this as an application. In NR, we can have multiple applications
6969# though right now, we can only do SpanProcessor and SynchronousMultiSpanProcessor
7070# Tracer: we can think of this as the transaction.
7171# Span: we can think of this as the trace.
72- # Links functionality has now been enabled. Links are relationships between spans, but
73- # lateral in hierarchy. In NR we only have parent-child relationships. We might want
74- # to preserve this information with a custom attribute. We can also add this as a new
75- # attribute in a trace, but it will still not be seen in the UI other than a trace attribute.
72+ # Links functionality has now been enabled but not implemented yet. Links are relationships
73+ # between spans, but lateral in hierarchy. In NR we only have parent-child relationships.
74+ # We may want to preserve this information with a custom attribute. We can also add this
75+ # as a new attribute in a trace, but it will still not be seen in the UI other than a trace
76+ # attribute.
7677
7778
7879class Span (otel_api_trace .Span ):
@@ -83,7 +84,7 @@ def __init__(
8384 resource = None ,
8485 attributes = None ,
8586 kind = otel_api_trace .SpanKind .INTERNAL ,
86- nr_transaction = current_transaction (), # This attribute is purely to prevent garbage collection
87+ nr_transaction = None ,
8788 nr_trace_type = FunctionTrace ,
8889 instrumenting_module = None ,
8990 * args ,
@@ -93,23 +94,17 @@ def __init__(
9394 self .otel_parent = parent
9495 self .attributes = attributes or {}
9596 self .kind = kind
96- self .nr_transaction = nr_transaction
97+ self .nr_transaction = (
98+ nr_transaction or current_transaction ()
99+ ) # This attribute is purely to prevent garbage collection
97100 self .nr_trace = None
98101 self .instrumenting_module = instrumenting_module
99102
100- # We should never reach this point. Leave this in
101- # for debug purposes but eventually take this out.
102- if not self .nr_transaction :
103- raise ValueError ("HOW DID WE GET HERE??" )
104-
105103 self .nr_parent = None
106104 current_nr_trace = current_trace ()
107105 if (
108106 not self .otel_parent
109- or (
110- self .otel_parent
111- and self .otel_parent .span_id == int (current_nr_trace .guid , 16 )
112- )
107+ or (self .otel_parent and self .otel_parent .span_id == int (current_nr_trace .guid , 16 ))
113108 or (self .otel_parent and isinstance (current_nr_trace , Sentinel ))
114109 ):
115110 # Expected to come here if one of three scenarios have occured:
@@ -124,14 +119,13 @@ def __init__(
124119 else :
125120 # Not sure if there is a usecase where we could get in here
126121 # but for debug purposes, we will raise an error
122+ _logger .debug ("Otel span and NR trace do not match nor correspond to a remote span" )
123+ _logger .debug ("otel span: %s\n newrelic trace: %s" , self .otel_parent , current_nr_trace )
127124 raise ValueError ("Unexpected span parent scenario encountered" )
125+
128126
129127 if nr_trace_type == FunctionTrace :
130- trace_kwargs = {
131- "name" : self .name ,
132- "params" : self .attributes ,
133- "parent" : self .nr_parent ,
134- }
128+ trace_kwargs = {"name" : self .name , "params" : self .attributes , "parent" : self .nr_parent }
135129 self .nr_trace = nr_trace_type (** trace_kwargs )
136130 elif nr_trace_type == DatastoreTrace :
137131 trace_kwargs = {
@@ -161,43 +155,31 @@ def __init__(
161155 }
162156 self .nr_trace = nr_trace_type (** trace_kwargs )
163157 else :
164- # TODO: Still need to implement GraphQLOperationTrace
165- # and GraphQLResolverTrace
166- trace_kwargs = {
167- "name" : self .name ,
168- "params" : self .attributes ,
169- "parent" : self .nr_parent ,
170- }
158+ # TODO: Still need to implement GraphQLOperationTrace and GraphQLResolverTrace
159+ trace_kwargs = {"name" : self .name , "params" : self .attributes , "parent" : self .nr_parent }
171160 self .nr_trace = nr_trace_type (** trace_kwargs )
172161
173162 self .nr_trace .__enter__ ()
174163
175164 def _is_sampled (self ):
176165 # Uses NR to determine if the trace is sampled
177-
166+
178167 # transaction.sampled can be None, True, False.
179168 # If None, this has not been computed by NR which
180169 # can also mean the following:
181170 # 1. There was not a context passed in that explicitly has sampling disabled.
182171 # This flag would be found in the traceparent or traceparent and tracespan headers.
183172 # 2. Transaction was not created where DT headers are accepted during __init__
184173 # Therefore, we will treat a value of `None` as `True` for now.
185-
174+
186175 if self .otel_parent :
187176 return bool (self .otel_parent .trace_flags )
188177 else :
189- return bool (
190- self .nr_transaction
191- and (
192- self .nr_transaction .sampled
193- or
194- (self .nr_transaction ._sampled is None )
195- )
196- )
178+ return bool (self .nr_transaction and (self .nr_transaction .sampled or (self .nr_transaction ._sampled is None )))
197179
198180 def _is_remote (self ):
199181 # Remote span denotes if propagated from a remote parent
200- if ( self .otel_parent and self .otel_parent .is_remote ) :
182+ if self .otel_parent and self .otel_parent .is_remote :
201183 return True
202184 return False
203185
@@ -246,28 +228,21 @@ def update_name(self, name):
246228
247229 def is_recording (self ):
248230 return self ._is_sampled () and not (
249- hasattr (self , "nr_trace" )
250- and hasattr (self .nr_trace , "end_time" )
251- and self .nr_trace .end_time
231+ hasattr (self , "nr_trace" ) and hasattr (self .nr_trace , "end_time" ) and self .nr_trace .end_time
252232 )
253233
254234 def set_status (self , status , description = None ):
255235 # TODO: not implemented yet
256236 pass
257237
258- def record_exception (
259- self , exception , attributes = None , timestamp = None , escaped = False
260- ):
238+ def record_exception (self , exception , attributes = None , timestamp = None , escaped = False ):
261239 if not hasattr (self , "nr_trace" ):
262240 if exception :
263241 notice_error ((type (exception ), exception , exception .__traceback__ ))
264242 else :
265243 notice_error (sys .exc_info (), attributes = attributes )
266244 else :
267- self .nr_trace .notice_error (
268- (type (exception ), exception , exception .__traceback__ ),
269- attributes = attributes ,
270- )
245+ self .nr_trace .notice_error ((type (exception ), exception , exception .__traceback__ ), attributes = attributes )
271246
272247 def end (self , end_time = None , * args , ** kwargs ):
273248 # We will ignore the end_time parameter and use NR's end_time
@@ -289,7 +264,7 @@ def end(self, end_time=None, *args, **kwargs):
289264 # Add attributes as Trace parameters
290265 self ._set_attributes_in_nr (self .attributes )
291266
292- # For each kind of NR Trace, we will need to add
267+ # For each kind of NR Trace, we will need to add
293268 # specific attributes since they were likely not
294269 # available at the time of the trace's creation.
295270 if self .instrumenting_module in ("Redis" , "Mongodb" ):
@@ -313,25 +288,16 @@ class Tracer(otel_api_trace.Tracer):
313288 def __init__ (self , resource = None , instrumentation_library = None , * args , ** kwargs ):
314289 self .resource = resource
315290 self .instrumentation_library = instrumentation_library .split ("." )[- 1 ].capitalize ()
316- self .nr_application = application_instance (
317- activate = False
318- ) or register_application ("OtelTracer" )
319- self .global_settings = (
320- self .nr_application and self .nr_application .global_settings
321- )
291+ self .nr_application = application_instance (activate = False ) or register_application ("OtelTracer" )
292+ self .global_settings = self .nr_application and self .nr_application .global_settings
322293
323- if (
324- self .nr_application
325- and self .global_settings .enabled
326- and self .nr_application .enabled
327- ):
294+ if self .nr_application and self .global_settings .enabled and self .nr_application .enabled :
328295 self ._settings = self .nr_application .settings
329296 if not self ._settings :
330297 self .nr_application .activate ()
331298 self ._settings = self .nr_application .settings
332299 else :
333- # Unable to find or register application. We should log this.
334- pass
300+ _logger .warning ("Unable to find or register New Relic application for Otel Tracer" )
335301
336302 def start_span (
337303 self ,
@@ -376,33 +342,25 @@ def start_span(
376342 request_path = request_path ,
377343 headers = headers ,
378344 )
379- elif kind in (
380- otel_api_trace .SpanKind .PRODUCER ,
381- otel_api_trace .SpanKind .INTERNAL ,
382- ):
345+ elif kind in (otel_api_trace .SpanKind .PRODUCER , otel_api_trace .SpanKind .INTERNAL ):
383346 transaction = BackgroundTask (self .nr_application , name = name )
384347 elif kind == otel_api_trace .SpanKind .CONSUMER :
385348 # NOTE: NR uses MessageTransaction for Pika, RabbitMQ, Kafka
386349 if (
387350 self .instrumentation_library in INSTRUMENTING_MODULE_TYPE
388- and INSTRUMENTING_MODULE_TYPE [self .instrumentation_library ]
389- == "message"
351+ and INSTRUMENTING_MODULE_TYPE [self .instrumentation_library ] == "message"
390352 ):
391353 transaction = MessageTransaction (
392354 library = self .instrumentation_library ,
393355 destination_type = "Topic" ,
394356 destination_name = name ,
395357 application = self .nr_application ,
396358 transport_type = self .instrumentation_library ,
397- headers = _headers ,
359+ headers = headers ,
398360 )
399361 else :
400- transaction = BackgroundTask (
401- self .nr_application ,
402- name = name ,
403- group = "Celery" ,
404- )
405-
362+ transaction = BackgroundTask (self .nr_application , name = name , group = "Celery" )
363+
406364 transaction .__enter__ ()
407365
408366 # If not parent_span_context or not parent_span_context.is_remote
@@ -432,7 +390,7 @@ def start_span(
432390 request_method = request_method ,
433391 request_path = request_path ,
434392 headers = headers ,
435- )
393+ )
436394 transaction .__enter__ ()
437395 elif kind == otel_api_trace .SpanKind .INTERNAL :
438396 if transaction :
@@ -443,12 +401,8 @@ def start_span(
443401 if transaction :
444402 if (
445403 (self .instrumentation_library in INSTRUMENTING_MODULE_TYPE )
446- and (
447- INSTRUMENTING_MODULE_TYPE [self .instrumentation_library ]
448- == "db"
449- )
450- or (attributes and ("db.system" in attributes ))
451- ):
404+ and (INSTRUMENTING_MODULE_TYPE [self .instrumentation_library ] == "db" )
405+ ) or (attributes and ("db.system" in attributes )):
452406 nr_trace_type = DatastoreTrace
453407 else :
454408 nr_trace_type = ExternalTrace
@@ -461,23 +415,18 @@ def start_span(
461415 # NOTE: NR uses MessageTransaction for Pika, RabbitMQ, Kafka
462416 if (
463417 self .instrumentation_library in INSTRUMENTING_MODULE_TYPE
464- and INSTRUMENTING_MODULE_TYPE [self .instrumentation_library ]
465- == "message"
418+ and INSTRUMENTING_MODULE_TYPE [self .instrumentation_library ] == "message"
466419 ):
467420 transaction = MessageTransaction (
468421 library = self .instrumentation_library ,
469422 destination_type = "Topic" ,
470423 destination_name = name ,
471424 application = self .nr_application ,
472425 transport_type = self .instrumentation_library ,
473- headers = _headers ,
426+ headers = headers ,
474427 )
475428 else :
476- transaction = BackgroundTask (
477- self .nr_application ,
478- name = name ,
479- group = "Celery" ,
480- )
429+ transaction = BackgroundTask (self .nr_application , name = name , group = "Celery" )
481430 transaction .__enter__ ()
482431 elif kind == otel_api_trace .SpanKind .PRODUCER :
483432 if transaction :
@@ -501,7 +450,6 @@ def start_span(
501450
502451 return span
503452
504-
505453 @contextmanager
506454 def start_as_current_span (
507455 self ,
@@ -523,9 +471,7 @@ def start_as_current_span(
523471 set_status_on_exception = set_status_on_exception ,
524472 )
525473
526- with otel_api_trace .use_span (
527- span , end_on_exit = end_on_exit , record_exception = record_exception
528- ) as current_span :
474+ with otel_api_trace .use_span (span , end_on_exit = end_on_exit , record_exception = record_exception ) as current_span :
529475 yield current_span
530476
531477
@@ -542,8 +488,4 @@ def get_tracer(
542488 * args ,
543489 ** kwargs ,
544490 ):
545- return Tracer (
546- resource = self ._resource , instrumentation_library = instrumenting_module_name
547- )
548-
549-
491+ return Tracer (resource = self ._resource , instrumentation_library = instrumenting_module_name )
0 commit comments