22import json
33import logging
44import textwrap
5- from typing import Any , Callable
5+ from typing import Any , Callable , Union
6+ from urllib .parse import urlparse
67
78from arango .api import ApiGroup
89from arango .request import Request
910from arango .response import Response
1011from wrapt import wrap_function_wrapper
1112
1213from opentelemetry import trace
14+ from opentelemetry .instrumentation .arangodb import arangodb_attributes
1315from opentelemetry .instrumentation .arangodb .package import _instruments
1416from opentelemetry .instrumentation .arangodb .version import __version__
1517from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
1618from opentelemetry .instrumentation .utils import unwrap
17- from opentelemetry .semconv .attributes import db_attributes , error_attributes
18- from opentelemetry .trace import SpanKind
19+ from opentelemetry .semconv .attributes import (
20+ db_attributes ,
21+ error_attributes ,
22+ server_attributes ,
23+ )
24+ from opentelemetry .trace import Span , SpanKind
1925from opentelemetry .trace .status import StatusCode
2026
2127logger = logging .getLogger (__name__ )
2228
2329
2430class ArangoDBInstrumentor (BaseInstrumentor ):
25- """An instrumentor for arangodb module
31+ """An instrumentor for arangodb module.
32+
2633 See `BaseInstrumentor`
2734 """
2835
@@ -65,7 +72,7 @@ def execute_with_span(
6572 )
6673
6774 with tracer .start_as_current_span (
68- span_name , kind = SpanKind .CLIENT
75+ span_name , kind = SpanKind .CLIENT , attributes = span_attributes
6976 ) as span :
7077 # We install a custom response handler to get access to the raw response,
7178 # before the arango client attempts to convert it to one of many different
@@ -74,9 +81,9 @@ def execute_with_span(
7481
7582 def custom_response_handler (response : Response ):
7683 span .set_attributes (self ._get_response_attributes (response ))
77- return original_response_handler ( response )
84+ self . _add_span_events ( span , response )
7885
79- span . set_attributes ( span_attributes )
86+ return original_response_handler ( response )
8087
8188 try :
8289 result = wrapped (request , custom_response_handler )
@@ -96,8 +103,8 @@ def _get_request_attributes(
96103 attributes = {}
97104
98105 query : str = ""
99- bind_vars : dict [str , Any ] | None = None
100- options : Any | None = None
106+ bind_vars : Union [ dict [str , Any ], None ] = None
107+ options : Union [ Any , None ] = None
101108
102109 span_name = f"ArangoDB: { request .method .upper ()} { request .endpoint } "
103110
@@ -113,8 +120,12 @@ def _get_request_attributes(
113120 else :
114121 query = str (request .data )
115122
116- attributes = {
123+ endpoint = urlparse (instance .conn ._hosts [0 ])
124+
125+ attributes : dict [str , Any ] = {
117126 db_attributes .DB_SYSTEM_NAME : "arangodb" ,
127+ server_attributes .SERVER_ADDRESS : endpoint .hostname ,
128+ server_attributes .SERVER_PORT : endpoint .port ,
118129 db_attributes .DB_NAMESPACE : instance .db_name ,
119130 db_attributes .DB_OPERATION_NAME : request .endpoint ,
120131 db_attributes .DB_QUERY_TEXT : textwrap .dedent (query .strip ("\n " )),
@@ -124,7 +135,35 @@ def _get_request_attributes(
124135 for key , value in bind_vars .items ():
125136 attributes [f"db.query.parameter.{ key } " ] = json .dumps (value )
126137
127- attributes ["db.query.options" ] = json .dumps (options )
138+ if options and isinstance (options , dict ):
139+ if "allowDirtyReads" in options :
140+ attributes [arangodb_attributes .ALLOW_DIRTY_READS ] = (
141+ options .get ("allowDirtyReads" )
142+ )
143+ if "allowRetry" in options :
144+ attributes [arangodb_attributes .ALLOW_RETRY ] = options .get (
145+ "allowRetry"
146+ )
147+ if "cache" in options :
148+ attributes [arangodb_attributes .CACHE ] = options .get ("cache" )
149+ if "failOnWarning" in options :
150+ attributes [arangodb_attributes .FAIL_ON_WARNING ] = options .get (
151+ "failOnWarning"
152+ )
153+ if "fullCount" in options :
154+ attributes [arangodb_attributes .FULL_COUNT ] = options .get (
155+ "fullCount"
156+ )
157+ if "maxRuntime" in options :
158+ attributes [arangodb_attributes .MAX_RUNTIME ] = options .get (
159+ "maxRuntime"
160+ )
161+ if "stream" in options :
162+ attributes [arangodb_attributes .STREAM ] = options .get ("stream" )
163+ if "usePlanCache" in options :
164+ attributes [arangodb_attributes .USE_PLAN_CACHE ] = options .get (
165+ "usePlanCache"
166+ )
128167
129168 return span_name , attributes
130169
@@ -133,17 +172,27 @@ def _get_response_attributes(self, response: Response) -> dict[str, Any]:
133172 db_attributes .DB_RESPONSE_STATUS_CODE : response .status_code
134173 }
135174
136- attributes ["db.execution.cached" ] = response .body .get ("cached" , False )
175+ if "cached" in response .body :
176+ attributes ["arangodb.response.cached" ] = response .body .get (
177+ "cached"
178+ )
179+ if "count" in response .body :
180+ attributes ["arangodb.response.count" ] = response .body .get ("count" )
181+ if "hasMore" in response .body :
182+ attributes ["arangodb.response.hasMore" ] = response .body .get (
183+ "hasMore"
184+ )
137185
138- if (count := response .body .get ("count" )) is not None :
139- attributes ["db.execution.count" ] = count
186+ return attributes
140187
188+ def _add_span_events (self , span : Span , response : Response ):
141189 if extra := response .body .get ("extra" ):
142- stats = extra .get ("stats" )
143- for key , value in stats .items ():
144- attributes ["db.execution.stats." + key ] = value
145-
146- warnings = extra .get ("warnings" )
147- attributes ["db.execution.warnings" ] = json .dumps (warnings )
148-
149- return attributes
190+ if warnings := extra .get ("warnings" ):
191+ for warning in warnings :
192+ span .add_event (
193+ "ArangoDB Warning" ,
194+ {
195+ "code" : warning .get ("code" ),
196+ "message" : warning .get ("message" ),
197+ },
198+ )
0 commit comments