Setting the file. One moment. Di Snapshot Rendering · AWS Observability · aws/agent-toolkit-for-aws · Skills Docs69
Creating Amazon Aurora Db Cluster With Instances
92
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Debugging Lambda Timeouts
scripts/cloudwatch/di_snapshot_rendering.py
scripts/cloudwatch/di_snapshot_rendering.py
Python·311 lines·12 KB
(
13 service_name: str,
14 environment: str,
15 location_hash: str,
16 custom_filters: Optional[List[str]],
17 start_time_utc: str,
18 end_time_utc: str,
19 start_epoch: int,
20 end_epoch: int,
21 query_string: str,
22 query_result: Dict[str, Any],
23) -> str:
24 """Render the snapshot-search response as JSON text."""
25 log_group_name = resolve_snapshot_log_group(service_name)
26 if query_result["status"] == "Error":
27 return json.dumps(
28 {
29 "status": "ERROR",
30 "service_name": service_name,
31 "environment": environment,
32 "log_group_name": log_group_name,
33 "location_hash": location_hash,
34 "custom_filters": custom_filters if custom_filters else [],
35 "start_time_utc": start_time_utc,
36 "end_time_utc": end_time_utc,
37 "query_string": query_string,
38 "error": query_result.get("error", "Unknown error"),
39 },
40 indent=2,
41 )
42
43 if query_result["status"] == "Polling Timeout":
44 return json.dumps(
45 {
46 "queryId": query_result.get("queryId"),
47 "status": "TIMEOUT",
48 "log_group_name": log_group_name,
49 "service_name": service_name,
50 "environment": environment,
51 "location_hash": location_hash,
52 "custom_filters": custom_filters if custom_filters else [],
53 "start_time_utc": start_time_utc,
54 "end_time_utc": end_time_utc,
55 "query_string": query_string,
56 "message": (
57 "Query did not complete within the requested timeout. "
58 "Use get-query-results with the returned queryId to retry."
59 ),
60 },
61 indent=2,
62 )
63
64 if query_result["status"] != "Complete":
65 # Failed/Cancelled (or any unexpected non-Complete) status: surface it instead of
66 # falling through to the success path, which would emit an empty-but-success-shaped
67 # response indistinguishable from "completed, zero snapshots". Mirrors the guard in
68 # render_get_sample_snapshot_for_breakpoint_output.
69 return json.dumps(
70 {
71 "status": query_result["status"],
72 "queryId": query_result.get("queryId"),
73 "service_name": service_name,
74 "environment": environment,
75 "log_group_name": log_group_name,
76 "location_hash": location_hash,
77 "query_string": query_string,
78 "messages": query_result.get("messages", []),
79 },
80 indent=2,
81 )
82
83 results = query_result["results"]
84 snapshot_summaries = []
85
86 for result in results:
87 try:
88 snapshot_data = json.loads(result.get("@message", "{}"))
89 except (json.JSONDecodeError, TypeError):
90 snapshot_data = {}
91 attributes = snapshot_data.get("attributes", {})
92 if not isinstance(attributes, dict):
93 attributes = {}
94 snapshot_summaries.append(
95 {
96 "@timestamp": result.get("@timestamp"),
97 "snapshot_id": attributes.get("aws.di.snapshot_id"),
98 "location_hash": attributes.get("aws.di.location_hash"),
99 "traceId": snapshot_data.get("traceId"),
100 "spanId": snapshot_data.get("spanId"),
101 }
102 )
103
104 output = {
105 "queryId": query_result.get("queryId"),
106 "status": query_result["status"],
107 "log_group_name": log_group_name,
108 "service_name": service_name,
109 "environment": environment,
110 "location_hash": location_hash,
111 "custom_filters": custom_filters if custom_filters else [],
112 "start_time_utc": start_time_utc,
113 "end_time_utc": end_time_utc,
114 "start_epoch": start_epoch,
115 "end_epoch": end_epoch,
116 "query_string": query_string,
117 "messages": query_result.get("messages", []),
118 "snapshot_summaries": snapshot_summaries,
119 "results": results,
120 }
121
122 return json.dumps(output, indent=2)
123
124
125def render_get_sample_snapshot_for_breakpoint_output(
126 service_name: str,
127 environment: str,
128 location_hash: str,
129 start_time_utc: str,
130 end_time_utc: str,
131 max_timeout: int,
132 query_string: str,
133 query_result: Dict[str, Any],
134 include_raw: bool = False,
135) -> str:
136 """Render the sample-snapshot response as JSON text."""
137 log_group_name = resolve_snapshot_log_group(service_name)
138 if query_result["status"] == "Error":
139 return json.dumps(
140 {
141 "status": "ERROR",
142 "service_name": service_name,
143 "environment": environment,
144 "log_group_name": log_group_name,
145 "location_hash": location_hash,
146 "error": query_result.get("error", "Unknown error"),
147 "query_string": query_string,
148 },
149 indent=2,
150 )
151
152 if query_result["status"] == "Polling Timeout":
153 return json.dumps(
154 {
155 "status": "TIMEOUT",
156 "queryId": query_result.get("queryId"),
157 "service_name": service_name,
158 "environment": environment,
159 "log_group_name": log_group_name,
160 "location_hash": location_hash,
161 "message": f"Query did not complete within {max_timeout} seconds.",
162 "query_string": query_string,
163 },
164 indent=2,
165 )
166
167 if query_result["status"] != "Complete":
168 return json.dumps(
169 {
170 "status": query_result["status"],
171 "queryId": query_result.get("queryId"),
172 "service_name": service_name,
173 "environment": environment,
174 "log_group_name": log_group_name,
175 "location_hash": location_hash,
176 "query_string": query_string,
177 "messages": query_result.get("messages", []),
178 },
179 indent=2,
180 )
181
182 results = query_result["results"]
183 if not results:
184 return json.dumps(
185 {
186 "status": "NO_SNAPSHOTS_FOUND",
187 "queryId": query_result.get("queryId"),
188 "service_name": service_name,
189 "environment": environment,
190 "log_group_name": log_group_name,
191 "location_hash": location_hash,
192 "time_range": {
193 "start": start_time_utc,
194 "end": end_time_utc,
195 },
196 "message": (
197 "No snapshots found in this window. Suggestions: "
198 "(1) Try an older ACTIVE event timestamp — older events have had more time "
199 "for CloudWatch Logs ingestion. "
200 "(2) If all timestamps fail, wait 1-2 minutes for ingestion delay. "
201 "(3) Verify the breakpoint is still ACTIVE and not DISABLED from max_hits exhaustion."
202 ),
203 "query_string": query_string,
204 },
205 indent=2,
206 )
207
208 raw_message = results[0].get("@message", "{}")
209 raw_size = len(raw_message.encode("utf-8"))
210 use_parsed = raw_size > _RAW_SNAPSHOT_SIZE_THRESHOLD and not include_raw
211
212 if use_parsed:
213 parsed = _parse_snapshot_fields(results[0])
214 parsed.pop("raw_snapshot", None)
215 sample_snapshot = parsed
216 else:
217 try:
218 sample_snapshot = json.loads(raw_message)
219 except (json.JSONDecodeError, TypeError):
220 sample_snapshot = {}
221
222 output = {
223 "status": "SUCCESS",
224 "queryId": query_result.get("queryId"),
225 "service_name": service_name,
226 "environment": environment,
227 "log_group_name": log_group_name,
228 "location_hash": location_hash,
229 "time_range": {
230 "start": start_time_utc,
231 "end": end_time_utc,
232 },
233 "cloudwatch_timestamp": results[0].get("@timestamp"),
234 }
235
236 if use_parsed:
237 output["note"] = (
238 f"Raw snapshot was {raw_size:,} bytes and has been replaced with a "
239 "compact parsed summary. To get the full raw snapshot, call this tool "
240 "again with include_raw=True."
241 )
242
243 output["sample_snapshot"] = sample_snapshot
244 output["field_documentation"] = {
245 "attributes.aws.di.snapshot_id": "Unique snapshot identifier (UUID v4).",
246 "timeUnixNano": "Snapshot timestamp in nanoseconds since Unix epoch.",
247 "attributes.aws.di.duration_ms": (
248 "Function execution duration in milliseconds. "
249 "Present for method-level breakpoints only; absent for line-level."
250 ),
251 "resource.attributes.service.name": "Service name from OTel resource.",
252 "resource.attributes.deployment.environment": (
253 "Deployment environment from OTel resource (legacy semconv key used by the Python agent "
254 "and the Java agent's fallback path). Filter on both this key and "
255 "resource.attributes.deployment.environment.name to cover every agent path."
256 ),
257 "resource.attributes.deployment.environment.name": (
258 "Deployment environment under the modern semconv key. The Java agent emits this via "
259 "OTel autoconfiguration / OTEL_RESOURCE_ATTRIBUTES."
260 ),
261 "attributes.aws.di.location_hash": (
262 'Breakpoint identifier. Use in filters: attributes.aws.di.location_hash = "<value>"'
263 ),
264 "attributes.aws.di.*": (
265 "Breakpoint location metadata: code_unit, class_name, method_name, file_path, "
266 "instrumentation_level, instrumentation_type."
267 ),
268 "traceId": (
269 "OpenTelemetry trace ID (hex, 32 chars). Use to filter snapshots from the same request: "
270 'traceId = "<value>"'
271 ),
272 "spanId": "OpenTelemetry span ID (hex, 16 chars). Use with traceId for precise span correlation.",
273 "body.stack": (
274 "Call stack frames (file_path, function, line_number), top to bottom. "
275 "First few frames are DI internals; application frames follow after."
276 ),
277 "body.captures.entry.arguments.<name>": (
278 "Input arguments at function entry (method-level only). "
279 'Filter: @message like /"arguments"/ and @message like /"<name>"/'
280 ),
281 "body.captures.entry.locals.<name>": "Local variables at function entry (method-level only).",
282 "body.captures.return.return_value": (
283 "Function return value (method-level only). "
284 'Filter: @message like /"return_value"/ and @message like /"<value>"/'
285 ),
286 "body.captures.return.arguments.<name>": (
287 "Arguments at function exit. Compare with entry arguments to detect mutation."
288 ),
289 "body.captures.return.locals.<name>": "Local variables at function exit (method-level only).",
290 "body.captures.return.throwable": "Exception info if function threw: type, message, stacktrace.",
291 "body.captures.lines.<line>.locals.<name>": (
292 "Local variables at a specific line (line-level only). "
293 'Filter: @message like /"locals"/ and @message like /"<name>"/'
294 ),
295 "CapturedValue shapes": (
296 "Each captured value has 'type' and one of: "
297 "'value' (string representation for primitives/strings/numbers), "
298 "'fields' (map of field name to CapturedValue, for objects/structs), "
299 "'elements' (array of CapturedValue, for lists/arrays), "
300 "'entries' (array of {key: CapturedValue, value: CapturedValue}, for maps/dicts), "
301 "'is_null': true (for null values), "
302 "'not_captured_reason' — the literal is agent-specific: Python emits lowercase "
303 "camelCase (depth, fieldCount, timeout); Java emits uppercase enum names "
304 "(DEPTH, TIMEOUT). Match both forms when filtering. "
305 "Oversize collections/maps are signaled via 'truncated: true' plus 'size' (original element count), "
306 "not via a not_captured_reason."
307 ),
308 }
309 output["messages"] = query_result.get("messages", [])
310
311 return json.dumps(output, indent=2)