Setting the file. One moment.
Di Snapshot Parsing · AWS Observability · aws/agent-toolkit-for-aws · Skills Docs
ContentsBack to the top of the page 69
Creating Amazon Aurora Db Cluster With Instances
92
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Debugging Lambda Timeouts
scripts/cloudwatch/ di_snapshot_parsing.py
Python · 304 lines · 11 KB
12
13 preview: Dict[ str , object ] = {}
14 value_type = captured_value.get( "type" )
15 if value_type not in ( None , "" ):
16 preview[ "type" ] = value_type
17
18 if captured_value.get( "is_null" ) is True :
19 preview[ "is_null" ] = True
20 return preview
21
22 if "not_captured_reason" in captured_value:
23 preview[ "not_captured_reason" ] = captured_value.get( "not_captured_reason" )
24 return preview
25
26 if "value" in captured_value:
27 preview[ "value" ] = captured_value.get( "value" )
28 if captured_value.get( "truncated" ) is True :
29 preview[ "truncated" ] = True
30 if "size" in captured_value:
31 preview[ "size" ] = captured_value.get( "size" )
32 return preview
33
34 if isinstance (captured_value.get( "fields" ), dict ):
35 fields = captured_value[ "fields" ]
36 # Expand one level: show primitive field values directly,
37 # collapse nested objects to just their type.
38 fields_preview: Dict[ str , object ] = {}
39 for fname, fval in fields.items():
40 if not isinstance (fval, dict ):
41 fields_preview[fname] = fval
42 continue
43 if fval.get( "is_null" ) is True :
44 fields_preview[fname] = None
45 elif "not_captured_reason" in fval:
46 fields_preview[fname] = f '< { fval[ "not_captured_reason" ] } >'
47 elif "value" in fval:
48 fields_preview[fname] = fval[ "value" ]
49 else :
50 # Nested object/collection — show type only
51 fields_preview[fname] = f '< { fval.get( "type" , "object" ) } >'
52 preview[ "fields_preview" ] = fields_preview
53 if "size" in captured_value:
54 preview[ "size" ] = captured_value.get( "size" )
55 return preview
56
57 if isinstance (captured_value.get( "elements" ), list ):
58 preview[ "element_count" ] = len (captured_value[ "elements" ])
59 if captured_value[ "elements" ]:
60 preview[ "first_element" ] = _preview_captured_value(captured_value[ "elements" ][ 0 ])
61 return preview
62
63 if isinstance (captured_value.get( "entries" ), list ):
64 preview[ "entry_count" ] = len (captured_value[ "entries" ])
65 return preview
66
67 return preview or captured_value
68
69
70 def _escape_logs_insights_regex (value: object ) -> str :
71 """Escape dynamic values for use inside a /.../ CloudWatch Logs Insights regex."""
72 return re.escape( str (value)).replace( "/" , r " \/ " )
73
74
75 def _escape_logs_insights_string (value: object ) -> str :
76 """Escape a value for a double-quoted CloudWatch Logs Insights string literal.
77
78 Logs Insights string literals are double-quoted with backslash escaping.
79 Escape backslashes first (so the escapes added next are not themselves
80 re-escaped), then double-quotes, so an embedded quote cannot terminate the
81 literal and inject caller-controlled query syntax. This is distinct from
82 ``_escape_logs_insights_regex``, which escapes for ``/.../`` regex context,
83 not ``"..."`` literal context.
84 """
85 return str (value).replace( " \\ " , " \\\\ " ).replace( '"' , ' \\ "' )
86
87
88 def _parse_snapshot_fields (result: dict ) -> dict :
89 """Extract key debugging fields from a raw CloudWatch Logs snapshot result.
90
91 Handles the OTLP log record format where:
92 - Metadata is in top-level `attributes` (aws.di.*)
93 - Resource info is in `resource.attributes` (service.name, deployment.environment —
94 the Java agent's autoconfig path may alternatively publish deployment.environment.name)
95 - Captures and stack are nested under `body`
96 - Trace/span IDs are at root level (`traceId`, `spanId`)
97 - Stack frames use `file_path`/`line_number` (not `fileName`/`lineNumber`)
98 - Return value key is `return_value` (not `returnValue`)
99 """
100 message = result.get( "@message" , "" )
101 try :
102 snapshot_data = json.loads(message)
103 except (json.JSONDecodeError, TypeError ):
104 snapshot_data = {}
105
106 if not isinstance (snapshot_data, dict ):
107 snapshot_data = {}
108
109 attributes = snapshot_data.get( "attributes" , {})
110 if not isinstance (attributes, dict ):
111 attributes = {}
112
113 resource = snapshot_data.get( "resource" , {})
114 if not isinstance (resource, dict ):
115 resource = {}
116 resource_attributes = resource.get( "attributes" , {})
117 if not isinstance (resource_attributes, dict ):
118 resource_attributes = {}
119
120 body = snapshot_data.get( "body" , {})
121 if not isinstance (body, dict ):
122 body = {}
123
124 location = {
125 "class_name" : attributes.get( "aws.di.class_name" ),
126 "method_name" : attributes.get( "aws.di.method_name" ),
127 "file_path" : attributes.get( "aws.di.file_path" ),
128 "code_unit" : attributes.get( "aws.di.code_unit" ),
129 "instrumentation_level" : attributes.get( "aws.di.instrumentation_level" ),
130 "instrumentation_type" : attributes.get( "aws.di.instrumentation_type" ),
131 }
132
133 trace = {
134 "traceId" : snapshot_data.get( "traceId" ),
135 "spanId" : snapshot_data.get( "spanId" ),
136 }
137
138 stack = body.get( "stack" , [])
139 if not isinstance (stack, list ):
140 stack = []
141
142 captures = body.get( "captures" , {})
143 if not isinstance (captures, dict ):
144 captures = {}
145
146 entry_capture = captures.get( "entry" , {})
147 if not isinstance (entry_capture, dict ):
148 entry_capture = {}
149
150 return_capture = captures.get( "return" , {})
151 if not isinstance (return_capture, dict ):
152 return_capture = {}
153
154 line_captures = captures.get( "lines" , {})
155 if not isinstance (line_captures, dict ):
156 line_captures = {}
157
158 entry_arguments = entry_capture.get( "arguments" , {})
159 if not isinstance (entry_arguments, dict ):
160 entry_arguments = {}
161
162 entry_locals = entry_capture.get( "locals" , {})
163 if not isinstance (entry_locals, dict ):
164 entry_locals = {}
165
166 return_arguments = return_capture.get( "arguments" , {})
167 if not isinstance (return_arguments, dict ):
168 return_arguments = {}
169
170 return_locals = return_capture.get( "locals" , {})
171 if not isinstance (return_locals, dict ):
172 return_locals = {}
173
174 return_value = return_capture.get( "return_value" )
175 throwable = return_capture.get( "throwable" , {})
176 if not isinstance (throwable, dict ):
177 throwable = {}
178
179 line_locals: Dict[ str , list[ str ]] = {}
180 line_local_previews: Dict[ str , Dict[ str , object ]] = {}
181 line_arguments: Dict[ str , list[ str ]] = {}
182 line_argument_previews: Dict[ str , Dict[ str , object ]] = {}
183 line_return_values: Dict[ str , object ] = {}
184 line_throwables: Dict[ str , object ] = {}
185 for line_number, line_capture in line_captures.items():
186 if not isinstance (line_capture, dict ):
187 continue
188 ln = str (line_number)
189
190 locals_map = line_capture.get( "locals" , {})
191 if isinstance (locals_map, dict ) and locals_map:
192 line_locals[ln] = list (locals_map.keys())
193 line_local_previews[ln] = {
194 name: _preview_captured_value(value) for name, value in locals_map.items()
195 }
196
197 args_map = line_capture.get( "arguments" , {})
198 if isinstance (args_map, dict ) and args_map:
199 line_arguments[ln] = list (args_map.keys())
200 line_argument_previews[ln] = {
201 name: _preview_captured_value(value) for name, value in args_map.items()
202 }
203
204 ret_val = line_capture.get( "return_value" )
205 if ret_val is not None :
206 line_return_values[ln] = _preview_captured_value(ret_val)
207
208 throwable_val = line_capture.get( "throwable" )
209 if isinstance (throwable_val, dict ) and throwable_val:
210 line_throwables[ln] = {
211 "type" : throwable_val.get( "type" ),
212 "message" : throwable_val.get( "message" ),
213 "stacktrace_frame_count" : (
214 len (throwable_val.get( "stacktrace" , []))
215 if isinstance (throwable_val.get( "stacktrace" ), list )
216 else 0
217 ),
218 }
219
220 duration_ms = attributes.get( "aws.di.duration_ms" )
221
222 stack_preview = []
223 for frame in stack[: 5 ]:
224 if not isinstance (frame, dict ):
225 continue
226 stack_preview.append(
227 {
228 "file_path" : frame.get( "file_path" ),
229 "function" : frame.get( "function" ),
230 "line_number" : frame.get( "line_number" ),
231 }
232 )
233
234 def _line_key (value):
235 """Order numeric line keys first (by value), non-numeric keys last (lexically).
236
237 Returns a ``(group, sort_value)`` tuple so the two kinds never compare
238 across types. A bare ``int(v) if v.isdigit() else v`` key would mix
239 ``int`` and ``str`` and raise ``TypeError`` the moment a non-digit key
240 appears alongside numeric ones (e.g. a negative line ``'-1'``, since
241 ``'-1'.isdigit()`` is ``False``), crashing snapshot parsing.
242 """
243 text = str (value)
244 if text.isdigit():
245 return ( 0 , int (text), "" )
246 return ( 1 , 0 , text)
247
248 all_line_numbers = sorted (
249 set (line_locals.keys())
250 | set (line_arguments.keys())
251 | set (line_return_values.keys())
252 | set (line_throwables.keys()),
253 key = _line_key,
254 )
255
256 return {
257 "@timestamp" : result.get( "@timestamp" ),
258 "snapshot_id" : attributes.get( "aws.di.snapshot_id" ),
259 "timeUnixNano" : snapshot_data.get( "timeUnixNano" ),
260 "duration_ms" : duration_ms,
261 "location_hash" : attributes.get( "aws.di.location_hash" ),
262 "location" : location,
263 "trace" : trace,
264 "stack_preview" : stack_preview,
265 "stack_frame_count" : len (stack),
266 "entry_argument_names" : list (entry_arguments.keys()),
267 "entry_arguments" : {
268 name: _preview_captured_value(value) for name, value in entry_arguments.items()
269 },
270 "entry_local_names" : list (entry_locals.keys()),
271 "entry_locals" : {
272 name: _preview_captured_value(value) for name, value in entry_locals.items()
273 },
274 "return_argument_names" : list (return_arguments.keys()),
275 "return_arguments" : {
276 name: _preview_captured_value(value) for name, value in return_arguments.items()
277 },
278 "return_local_names" : list (return_locals.keys()),
279 "return_locals" : {
280 name: _preview_captured_value(value) for name, value in return_locals.items()
281 },
282 "return_value" : _preview_captured_value(return_value) if return_value is not None else None ,
283 "throwable" : (
284 {
285 "type" : throwable.get( "type" ),
286 "message" : throwable.get( "message" ),
287 "stacktrace_frame_count" : (
288 len (throwable.get( "stacktrace" , []))
289 if isinstance (throwable.get( "stacktrace" ), list )
290 else 0
291 ),
292 }
293 if throwable
294 else None
295 ),
296 "line_numbers" : all_line_numbers,
297 "line_locals" : line_locals,
298 "line_local_previews" : line_local_previews,
299 "line_arguments" : line_arguments if line_arguments else None ,
300 "line_argument_previews" : line_argument_previews if line_argument_previews else None ,
301 "line_return_values" : line_return_values if line_return_values else None ,
302 "line_throwables" : line_throwables if line_throwables else None ,
303 "raw_snapshot" : snapshot_data,
304 }