58
58
59
59
from opentelemetry .instrumentation .botocore .version import __version__
60
60
from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
61
+ from opentelemetry .instrumentation .utils import unwrap
61
62
from opentelemetry .sdk .trace import Resource
62
63
from opentelemetry .trace import SpanKind , get_tracer
63
64
64
65
logger = logging .getLogger (__name__ )
65
66
66
67
67
68
class BotocoreInstrumentor (BaseInstrumentor ):
68
- """A instrumentor for Botocore
69
+ """A instrumentor for Botocore.
69
70
70
71
See `BaseInstrumentor`
71
72
"""
72
73
73
74
def _instrument (self , ** kwargs ):
74
75
75
- # FIXME should the tracer provider be accessed via Configuration
76
- # instead?
77
76
# pylint: disable=attribute-defined-outside-init
78
77
self ._tracer = get_tracer (
79
78
__name__ , __version__ , kwargs .get ("tracer_provider" )
@@ -90,45 +89,18 @@ def _uninstrument(self, **kwargs):
90
89
91
90
def _patched_api_call (self , original_func , instance , args , kwargs ):
92
91
93
- endpoint_name = deep_getattr (instance , "_endpoint._endpoint_prefix" )
92
+ # pylint: disable=protected-access
93
+ service_name = instance ._service_model .service_name
94
+ operation_name , _ = args
94
95
95
96
with self ._tracer .start_as_current_span (
96
- "{}.command " .format (endpoint_name ), kind = SpanKind .CONSUMER ,
97
+ "{}" .format (service_name ), kind = SpanKind .CLIENT ,
97
98
) as span :
98
99
99
- operation = None
100
- if args and span .is_recording ():
101
- operation = args [0 ]
102
- span .resource = Resource (
103
- attributes = {
104
- "endpoint" : endpoint_name ,
105
- "operation" : operation .lower (),
106
- }
107
- )
108
-
109
- else :
110
- span .resource = Resource (
111
- attributes = {"endpoint" : endpoint_name }
112
- )
113
-
114
- add_span_arg_tags (
115
- span ,
116
- endpoint_name ,
117
- args ,
118
- ("action" , "params" , "path" , "verb" ),
119
- {"params" , "path" , "verb" },
120
- )
121
-
122
100
if span .is_recording ():
123
- region_name = deep_getattr (instance , "meta.region_name" )
124
-
125
- meta = {
126
- "aws.agent" : "botocore" ,
127
- "aws.operation" : operation ,
128
- "aws.region" : region_name ,
129
- }
130
- for key , value in meta .items ():
131
- span .set_attribute (key , value )
101
+ span .set_attribute ("aws.operation" , operation_name )
102
+ span .set_attribute ("aws.region" , instance .meta .region_name )
103
+ span .set_attribute ("aws.service" , service_name )
132
104
133
105
result = original_func (* args , ** kwargs )
134
106
@@ -137,86 +109,5 @@ def _patched_api_call(self, original_func, instance, args, kwargs):
137
109
"http.status_code" ,
138
110
result ["ResponseMetadata" ]["HTTPStatusCode" ],
139
111
)
140
- span .set_attribute (
141
- "retry_attempts" ,
142
- result ["ResponseMetadata" ]["RetryAttempts" ],
143
- )
144
112
145
113
return result
146
-
147
-
148
- def unwrap (obj , attr ):
149
- function = getattr (obj , attr , None )
150
- if (
151
- function
152
- and isinstance (function , ObjectProxy )
153
- and hasattr (function , "__wrapped__" )
154
- ):
155
- setattr (obj , attr , function .__wrapped__ )
156
-
157
-
158
- def add_span_arg_tags (span , endpoint_name , args , args_names , args_traced ):
159
- def truncate_arg_value (value , max_len = 1024 ):
160
- """Truncate values which are bytes and greater than `max_len`.
161
- Useful for parameters like "Body" in `put_object` operations.
162
- """
163
- if isinstance (value , bytes ) and len (value ) > max_len :
164
- return b"..."
165
-
166
- return value
167
-
168
- def flatten_dict (dict_ , sep = "." , prefix = "" ):
169
- """
170
- Returns a normalized dict of depth 1 with keys in order of embedding
171
- """
172
- # adapted from https://stackoverflow.com/a/19647596
173
- return (
174
- {
175
- prefix + sep + k if prefix else k : v
176
- for kk , vv in dict_ .items ()
177
- for k , v in flatten_dict (vv , sep , kk ).items ()
178
- }
179
- if isinstance (dict_ , dict )
180
- else {prefix : dict_ }
181
- )
182
-
183
- if not span .is_recording ():
184
- return
185
-
186
- if endpoint_name not in {"kms" , "sts" }:
187
- tags = dict (
188
- (name , value )
189
- for (name , value ) in zip (args_names , args )
190
- if name in args_traced
191
- )
192
- tags = flatten_dict (tags )
193
- for key , value in {
194
- k : truncate_arg_value (v )
195
- for k , v in tags .items ()
196
- if k not in {"s3" : ["params.Body" ]}.get (endpoint_name , [])
197
- }.items ():
198
- span .set_attribute (key , value )
199
-
200
-
201
- def deep_getattr (obj , attr_string , default = None ):
202
- """
203
- Returns the attribute of ``obj`` at the dotted path given by
204
- ``attr_string``, if no such attribute is reachable, returns ``default``.
205
-
206
- >>> deep_getattr(cass, "cluster")
207
- <cassandra.cluster.Cluster object at 0xa20c350
208
-
209
- >>> deep_getattr(cass, "cluster.metadata.partitioner")
210
- u"org.apache.cassandra.dht.Murmur3Partitioner"
211
-
212
- >>> deep_getattr(cass, "i.dont.exist", default="default")
213
- "default"
214
- """
215
- attrs = attr_string .split ("." )
216
- for attr in attrs :
217
- try :
218
- obj = getattr (obj , attr )
219
- except AttributeError :
220
- return default
221
-
222
- return obj
0 commit comments