2828# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2929# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3030
31+ from collections import namedtuple
32+
3133from elasticapm .instrumentation .packages .base import AbstractInstrumentedModule
3234from elasticapm .traces import capture_span
3335from elasticapm .utils .compat import urlparse
3436
37+ HandlerInfo = namedtuple ("HandlerInfo" , ("signature" , "span_type" , "span_subtype" , "span_action" , "context" ))
38+
3539
3640class BotocoreInstrumentation (AbstractInstrumentedModule ):
3741 name = "botocore"
@@ -44,14 +48,109 @@ def call(self, module, method, wrapped, instance, args, kwargs):
4448 else :
4549 operation_name = args [0 ]
4650
47- target_endpoint = instance ._endpoint .host
48- parsed_url = urlparse .urlparse (target_endpoint )
49- if "." in parsed_url .hostname :
50- service = parsed_url .hostname .split ("." , 2 )[0 ]
51- else :
52- service = parsed_url .hostname
51+ service = instance ._service_model .service_id
5352
54- signature = "{}:{}" .format (service , operation_name )
53+ parsed_url = urlparse .urlparse (instance ._endpoint .host )
54+ context = {
55+ "destination" : {
56+ "address" : parsed_url .hostname ,
57+ "port" : parsed_url .port ,
58+ "cloud" : {"region" : instance .meta .region_name },
59+ }
60+ }
5561
56- with capture_span (signature , "aws" , leaf = True , span_subtype = service , span_action = operation_name ):
62+ handler_info = None
63+ handler = handlers .get (service , False )
64+ if handler :
65+ handler_info = handler (operation_name , service , instance , args , kwargs , context )
66+ if not handler_info :
67+ handler_info = handle_default (operation_name , service , instance , args , kwargs , context )
68+
69+ with capture_span (
70+ handler_info .signature ,
71+ span_type = handler_info .span_type ,
72+ leaf = True ,
73+ span_subtype = handler_info .span_subtype ,
74+ span_action = handler_info .span_action ,
75+ extra = handler_info .context ,
76+ ):
5777 return wrapped (* args , ** kwargs )
78+
79+
80+ def handle_s3 (operation_name , service , instance , args , kwargs , context ):
81+ span_type = "storage"
82+ span_subtype = "s3"
83+ span_action = operation_name
84+ if len (args ) > 1 and "Bucket" in args [1 ]:
85+ bucket = args [1 ]["Bucket" ]
86+ else :
87+ # TODO handle Access Points
88+ bucket = ""
89+ signature = f"S3 { operation_name } { bucket } "
90+
91+ context ["destination" ]["name" ] = span_subtype
92+ context ["destination" ]["resource" ] = bucket
93+ context ["destination" ]["service" ] = {"type" : span_type }
94+
95+ return HandlerInfo (signature , span_type , span_subtype , span_action , context )
96+
97+
98+ def handle_dynamodb (operation_name , service , instance , args , kwargs , context ):
99+ span_type = "db"
100+ span_subtype = "dynamodb"
101+ span_action = "query"
102+ if len (args ) > 1 and "TableName" in args [1 ]:
103+ table = args [1 ]["TableName" ]
104+ else :
105+ table = ""
106+ signature = f"DynamoDB { operation_name } { table } " .rstrip ()
107+
108+ context ["db" ] = {"type" : "dynamodb" , "instance" : instance .meta .region_name }
109+ if operation_name == "Query" and len (args ) > 1 and "KeyConditionExpression" in args [1 ]:
110+ context ["db" ]["statement" ] = args [1 ]["KeyConditionExpression" ]
111+
112+ context ["destination" ]["name" ] = span_subtype
113+ context ["destination" ]["resource" ] = table
114+ context ["destination" ]["service" ] = {"type" : span_type }
115+ return HandlerInfo (signature , span_type , span_subtype , span_action , context )
116+
117+
118+ def handle_sns (operation_name , service , instance , args , kwargs , context ):
119+ if operation_name != "Publish" :
120+ # only "publish" is handled specifically, other endpoints get the default treatment
121+ return False
122+ span_type = "messaging"
123+ span_subtype = "sns"
124+ span_action = "send"
125+ topic_name = ""
126+ if len (args ) > 1 :
127+ if "Name" in args [1 ]:
128+ topic_name = args [1 ]["Name" ]
129+ if "TopicArn" in args [1 ]:
130+ topic_name = args [1 ]["TopicArn" ].rsplit (":" , maxsplit = 1 )[- 1 ]
131+ signature = f"SNS { operation_name } { topic_name } " .rstrip ()
132+ context ["destination" ]["name" ] = span_subtype
133+ context ["destination" ]["resource" ] = f"{ span_subtype } /{ topic_name } " if topic_name else span_subtype
134+ context ["destination" ]["type" ] = span_type
135+ return HandlerInfo (signature , span_type , span_subtype , span_action , context )
136+
137+
138+ def handle_sqs (operation_name , service , instance , args , kwargs , destination ):
139+ pass
140+
141+
142+ def handle_default (operation_name , service , instance , args , kwargs , destination ):
143+ span_type = "aws"
144+ span_subtype = service .lower ()
145+ span_action = operation_name
146+
147+ signature = f"{ service } :{ operation_name } "
148+ return HandlerInfo (signature , span_type , span_subtype , span_action , destination )
149+
150+
151+ handlers = {
152+ "S3" : handle_s3 ,
153+ "DynamoDB" : handle_dynamodb ,
154+ "SNS" : handle_sns ,
155+ "default" : handle_default ,
156+ }
0 commit comments