Setting the file. One moment. Di CRUD Tools · 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
Creating API Gateway Stage
Previous
Script Di CRUD Rendering
scripts/cloudwatch/di_crud_tools.py
Python·709 lines·29 KB
11 render_create_success_message,
12 render_get_instrumentation_output,
13 render_list_instrumentations_output,
14)
15from di_location import parse_create_inputs, parse_lookup_inputs
16from di_result import OpResult
17from di_validation import (
18 _format_code_location_troubleshooting,
19 normalize_instrumentation_type,
20 validate_capture_names,
21 validate_probe_constraints,
22)
23
24
25def create_instrumentation(
26 instrumentation_type: str,
27 service: str,
28 environment: str,
29 language: Optional[str] = None,
30 file_path: Optional[str] = None,
31 code_unit: Optional[str] = None,
32 class_name: Optional[str] = None,
33 method_name: Optional[str] = None,
34 line_number: Optional[int] = None,
35 capture_arguments: Optional[List[str]] = None,
36 capture_return: Optional[bool] = None,
37 capture_stack_trace: Optional[bool] = None,
38 capture_locals: Optional[List[str]] = None,
39 max_hits: Optional[int] = None,
40 max_string_length: Optional[int] = None,
41 max_collection_width: Optional[int] = None,
42 max_collection_depth: Optional[int] = None,
43 max_stack_frames: Optional[int] = None,
44 max_stack_trace_size: Optional[int] = None,
45 max_object_depth: Optional[int] = None,
46 max_fields_per_object: Optional[int] = None,
47 attribute_filters: Optional[List[Dict[str, str]]] = None,
48 description: str = "dynamic instrumentation",
49 ttl_hours: Optional[int] = None,
50) -> OpResult:
51 """Create a dynamic instrumentation configuration for BREAKPOINT or PROBE.
52
53 This is the main creation entrypoint for this command. BREAKPOINT and PROBE
54 create code-based instrumentation and require an explicit code location. Set
55 capture_arguments for method/function-level targets and capture_locals for
56 line-level targets.
57
58 Args:
59 instrumentation_type: BREAKPOINT or PROBE. PROBE is method/function-level only
60 (no line_number) and is not supported for JavaScript. Unlike BREAKPOINT,
61 PROBE has no max_hits cap — it fires on every hit, which makes it suited to
62 long-running observation/monitoring without worrying about hitting a limit.
63 The trade-off: a PROBE never expires on its own, so you must delete it
64 explicitly when done.
65 service: Backend service identifier used by the AWS API.
66 environment: Backend environment identifier used by the AWS API.
67 language: Required for BREAKPOINT/PROBE code instrumentation.
68 Typically Python or Java.
69 file_path: Required for BREAKPOINT/PROBE.
70 code_unit: Module/package name for code instrumentation.
71 For Python, use the dotted runtime import path for the defining module,
72 or "__main__" only when the target file is executed directly as the
73 process entry script.
74 class_name: Optional class name for class-based targets. Java should use the simple class name only.
75 method_name: Optional function or method name for method-level instrumentation.
76 line_number: Optional 1-based line number for line-level instrumentation.
77 capture_arguments: A list of argument names to capture, for method/function-level
78 instrumentation (when line_number is not set). this command does not infer argument names
79 automatically. Provide explicit names; an empty list and the wildcard "*" are
80 rejected. Omit to capture no arguments.
81 capture_return: Whether to capture return values for code instrumentation. Defaults to enabled.
82 capture_stack_trace: Whether to capture stack traces for code instrumentation. Defaults to enabled.
83 capture_locals: A list of local variable names to capture, for line-level
84 instrumentation (when line_number is set). this command does not infer variable names
85 automatically. Provide explicit names; an empty list and the wildcard "*" are
86 rejected. Omit to capture no locals.
87 max_hits: Optional capture limit for maximum number of hits. Applies to BREAKPOINT
88 only; PROBE has no max_hits (it fires on every hit) and the value is ignored.
89 max_string_length: Optional capture limit for string truncation.
90 max_collection_width: Optional capture limit for collection width.
91 max_collection_depth: Optional capture limit for nested collection depth.
92 max_stack_frames: Optional capture limit for stack frame count.
93 max_stack_trace_size: Optional capture limit for stack trace size.
94 max_object_depth: Optional capture limit for object traversal depth.
95 max_fields_per_object: Optional capture limit for object field count.
96 attribute_filters: Optional list of resource-attribute filter groups that scope
97 which service instances the instrumentation applies to. Each group is a
98 dict of OpenTelemetry resource-attribute names to exact-match values
99 (e.g. {"service.version": "1.2.0", "deployment.environment": "staging"}).
100 Matching is exact (no wildcards/patterns); conditions are AND-ed within a
101 group and groups are OR-ed together. Up to 10 groups; keys and values must
102 be 1-50 and 1-100 characters respectively. Omit to apply to all instances.
103 description: Free-form description stored with the instrumentation. Must be 50 characters or fewer.
104 ttl_hours: Optional expiration duration in hours. Converted to an absolute UTC
105 timestamp. If omitted, the Application Signals service applies its own default
106 expiration (~24h). Ignored for PROBE — a PROBE does not expire on its own and must be
107 deleted explicitly, so set up cleanup accordingly.
108
109 Notes:
110 - BREAKPOINT/PROBE require `language` and `file_path`.
111 - For Python, set `code_unit` to the dotted runtime import path for
112 the module that defines the target code, such as
113 `services.billing`.
114 - For Python, do not use a filename or filesystem path as `code_unit`.
115 - For Python, use `code_unit="__main__"` only when the target file
116 is executed directly as the process entry script.
117 - For Java, set `code_unit` to the package name and keep `class_name` as the simple class name only.
118 - `line_number` is only for line-level breakpoints and must be 1-based.
119 - Target an executable statement when setting `line_number`. Python/Java ignore a
120 non-executable line (blank/comment/decorator/signature) and the breakpoint never
121 fires; JavaScript slides the breakpoint to the next parseable line. Choose the
122 line deliberately.
123 - PROBE is method/function-level only: not supported for JavaScript, and
124 `line_number` must be omitted (create rejects a PROBE that sets it).
125 - PROBE has no `max_hits` and fires on every hit (unlike BREAKPOINT). This makes
126 it suited to long-running observation/monitoring without worrying about a hit
127 limit — but a PROBE does not expire on its own (`ttl_hours` is ignored), so you
128 must delete it explicitly when you are done.
129 - `capture_arguments` and `capture_locals` reject `["*"]` and empty lists; omit to capture none.
130 - `SignalType` is always SNAPSHOT.
131 - `description` must be 50 characters or fewer.
132 - Inspect the source file directly before calling this tool — choose `code_unit`,
133 `capture_arguments`, and method/class names explicitly.
134
135 Returns:
136 A human-readable success or failure message. Success responses include the
137 created LocationHash, resolved location details, and a delete hint.
138 """
139 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
140 if type_error:
141 return OpResult(False, type_error)
142
143 probe_error = validate_probe_constraints(normalized_type, language, line_number)
144 if probe_error:
145 return OpResult(False, probe_error)
146
147 location, location_error = parse_create_inputs(
148 normalized_type=normalized_type,
149 language=language,
150 file_path=file_path,
151 code_unit=code_unit,
152 class_name=class_name,
153 method_name=method_name,
154 line_number=line_number,
155 )
156 if location_error:
157 return OpResult(False, location_error)
158 if location is None:
159 # Defensive: parsers return (loc, None) or (None, error_text). This
160 # branch should be unreachable, but we return a user-facing error
161 # string (not ``raise``) so the tool's "always returns a string"
162 # contract holds even if a future parser bug fires this path.
163 return OpResult(
164 False, "ERROR: Internal error resolving location. Please report this issue."
165 )
166
167 location_troubleshooting = _format_code_location_troubleshooting(
168 language=language,
169 file_path=file_path,
170 code_unit=code_unit,
171 class_name=class_name,
172 method_name=method_name,
173 line_number=line_number,
174 )
175
176 capture_arguments_error = validate_capture_names("capture_arguments", capture_arguments)
177 if capture_arguments_error:
178 return OpResult(False, capture_arguments_error)
179 capture_locals_error = validate_capture_names("capture_locals", capture_locals)
180 if capture_locals_error:
181 return OpResult(False, capture_locals_error)
182
183 # Line-level instrumentation (line_number set) fires mid-function, where only
184 # locals carry data — arguments/return values are call-boundary concepts that
185 # do not apply. A line-level config without capture_locals would capture
186 # nothing useful, so require it. (JavaScript is always line-level per its
187 # location rules, so this requirement always applies to JavaScript.)
188 is_line_level = line_number is not None
189 if is_line_level and not capture_locals:
190 return OpResult(
191 False,
192 "ERROR: line-level instrumentation (line_number set) requires capture_locals.\n"
193 "At a specific line, only local variables carry data — arguments and return "
194 "values apply to method/function-level targets (no line_number).\n"
195 "Provide capture_locals=[...] with the local variable names to capture.",
196 )
197
198 code_capture_return = (not is_line_level) if capture_return is None else capture_return
199 code_capture_stack_trace = True if capture_stack_trace is None else capture_stack_trace
200 code_capture_locals = capture_locals
201
202 capture = CodeCapture(
203 capture_return=code_capture_return,
204 capture_stack_trace=code_capture_stack_trace,
205 capture_arguments=capture_arguments,
206 capture_locals=code_capture_locals,
207 limits=CaptureLimits(
208 max_hits=max_hits,
209 max_string_length=max_string_length,
210 max_collection_width=max_collection_width,
211 max_collection_depth=max_collection_depth,
212 max_stack_frames=max_stack_frames,
213 max_stack_trace_size=max_stack_trace_size,
214 max_object_depth=max_object_depth,
215 max_fields_per_object=max_fields_per_object,
216 ),
217 )
218
219 target_desc = location.describe()
220
221 request_kwargs: Dict[str, Any] = {
222 "InstrumentationType": normalized_type,
223 "Service": service,
224 "Environment": environment,
225 "SignalType": SNAPSHOT_SIGNAL_TYPE,
226 "Location": location.to_api_payload(),
227 "CaptureConfiguration": capture.to_api_payload(),
228 "Description": description,
229 }
230 if ttl_hours is not None:
231 request_kwargs["ExpiresAt"] = datetime.now(timezone.utc) + timedelta(hours=ttl_hours)
232 if attribute_filters:
233 request_kwargs["AttributeFilters"] = attribute_filters
234
235 try:
236 response = gateway.create_instrumentation_configuration(**request_kwargs)
237 except gateway.GatewayError as err:
238 return OpResult(
239 False,
240 gateway.render_error(
241 err,
242 action=f"create {normalized_type} instrumentation",
243 attempted_label="ATTEMPTED CONFIGURATION:",
244 attempted={
245 "Type": normalized_type,
246 "Target": target_desc,
247 "Service": service,
248 "Environment": environment,
249 },
250 possible_causes=[
251 "AWS credentials missing or scoped to a different account",
252 "Invalid service or environment identifier",
253 "Instrumentation already exists at this location",
254 "Invalid location/capture payload",
255 "AWS API endpoint not accessible",
256 ],
257 troubleshooting=[
258 "Verify AWS credentials: aws configure list",
259 "Check service name and environment match your deployment",
260 "Try listing existing instrumentations with list_instrumentations",
261 ],
262 trailer=location_troubleshooting,
263 ),
264 )
265
266 return OpResult(
267 True,
268 render_create_success_message(
269 response=response,
270 normalized_type=normalized_type,
271 service=service,
272 environment=environment,
273 location=location,
274 ttl_hours=ttl_hours,
275 capture_arguments=capture_arguments,
276 code_capture_locals=code_capture_locals,
277 is_line_level=is_line_level,
278 code_capture_return=code_capture_return,
279 code_capture_stack_trace=code_capture_stack_trace,
280 max_hits=max_hits,
281 max_string_length=max_string_length,
282 max_collection_width=max_collection_width,
283 max_collection_depth=max_collection_depth,
284 max_stack_frames=max_stack_frames,
285 max_stack_trace_size=max_stack_trace_size,
286 max_object_depth=max_object_depth,
287 max_fields_per_object=max_fields_per_object,
288 attribute_filters=attribute_filters,
289 ),
290 )
291
292
293def list_instrumentations(
294 service: str,
295 environment: str,
296 instrumentation_type: str,
297 synced_at: Optional[str] = None,
298 max_results: int = 100,
299 next_token: Optional[str] = None,
300) -> OpResult:
301 """List active instrumentation configurations for one service, environment, and type.
302
303 Args:
304 service: Backend service identifier.
305 environment: Backend environment identifier.
306 instrumentation_type: BREAKPOINT or PROBE.
307 synced_at: Optional AWS pagination/synchronization cursor timestamp.
308 max_results: Maximum number of configurations to request. Defaults to 100.
309 next_token: Optional AWS pagination token from a previous response.
310
311 Returns:
312 A human-readable list of configurations with location details, capture
313 settings, timing metadata, and pagination guidance when more results exist.
314 """
315 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
316 if type_error:
317 return OpResult(False, type_error)
318
319 request_kwargs: Dict[str, Any] = {
320 "Service": service,
321 "Environment": environment,
322 "InstrumentationType": normalized_type,
323 }
324 if synced_at:
325 request_kwargs["SyncedAt"] = synced_at
326 if max_results != 100:
327 request_kwargs["MaxResults"] = max_results
328 if next_token:
329 request_kwargs["NextToken"] = next_token
330
331 try:
332 data = gateway.list_instrumentation_configurations(**request_kwargs)
333 except gateway.GatewayError as err:
334 return OpResult(
335 False,
336 gateway.render_error(
337 err,
338 action="list instrumentations",
339 attempted={
340 "Service": service,
341 "Environment": environment,
342 "InstrumentationType": normalized_type,
343 },
344 ),
345 )
346
347 return OpResult(
348 True,
349 render_list_instrumentations_output(
350 data=data,
351 normalized_type=normalized_type,
352 service=service,
353 environment=environment,
354 ),
355 )
356
357
358def batch_delete_instrumentations_by_scope(
359 service: str,
360 environment: str,
361 instrumentation_type: str,
362) -> OpResult:
363 """Batch delete instrumentation configurations by scope.
364
365 This deletes all configurations that match the provided service, environment,
366 and instrumentation type.
367
368 Args:
369 service: Backend service identifier.
370 environment: Backend environment identifier.
371 instrumentation_type: BREAKPOINT or PROBE.
372
373 Returns:
374 A human-readable batch delete summary including deleted count, successful
375 deletions, and any per-item errors returned by the backend.
376 """
377 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
378 if type_error:
379 return OpResult(False, type_error)
380
381 deletion_target = {
382 "Scope": {
383 "Service": service,
384 "Environment": environment,
385 "InstrumentationType": normalized_type,
386 }
387 }
388
389 try:
390 data = gateway.batch_delete_instrumentation_configurations(
391 DeletionTarget=deletion_target,
392 )
393 except gateway.GatewayError as err:
394 return OpResult(
395 False,
396 gateway.render_error(
397 err,
398 action="batch delete instrumentation configurations (scope mode)",
399 attempted={
400 "Service": service,
401 "Environment": environment,
402 "InstrumentationType": normalized_type,
403 },
404 ),
405 )
406
407 # ok reflects whether the backend reported any per-item errors (read from the
408 # response, NOT the rendered text): a batch where every item errored still
409 # renders the "BATCH DELETE COMPLETED" header but must report failure.
410 return OpResult(
411 not data.get("Errors"),
412 _format_batch_delete_response(
413 mode="Scope",
414 data=data,
415 instrumentation_type=normalized_type,
416 service=service,
417 environment=environment,
418 ),
419 )
420
421
422def batch_delete_instrumentations_by_arns(
423 resource_arns: List[str],
424 instrumentation_type: str,
425) -> OpResult:
426 """Batch delete instrumentation configurations by explicit resource ARN list.
427
428 Args:
429 resource_arns: One to fifty instrumentation resource ARNs.
430 instrumentation_type: BREAKPOINT or PROBE.
431
432 Notes:
433 - The request is rejected when `resource_arns` is empty.
434 - The request is rejected when more than 50 ARNs are provided.
435 - All ARN values must be non-empty strings.
436
437 Returns:
438 A human-readable batch delete summary including deleted count, successful
439 deletions, and any per-item errors returned by the backend.
440 """
441 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
442 if type_error:
443 return OpResult(False, type_error)
444 if not resource_arns:
445 return OpResult(False, "ERROR: resource_arns must contain at least one ARN.")
446 if len(resource_arns) > 50:
447 return OpResult(False, "ERROR: resource_arns can include at most 50 ARNs per request.")
448
449 invalid_arns = [arn for arn in resource_arns if not isinstance(arn, str) or not arn.strip()]
450 if invalid_arns:
451 return OpResult(False, "ERROR: resource_arns must contain non-empty ARN strings only.")
452
453 deletion_target = {
454 "ResourceArns": {
455 "ResourceArns": resource_arns,
456 "InstrumentationType": normalized_type,
457 }
458 }
459
460 try:
461 data = gateway.batch_delete_instrumentation_configurations(
462 DeletionTarget=deletion_target,
463 )
464 except gateway.GatewayError as err:
465 return OpResult(
466 False,
467 gateway.render_error(
468 err,
469 action="batch delete instrumentation configurations (resource ARN mode)",
470 attempted={
471 "InstrumentationType": normalized_type,
472 "ResourceArnCount": len(resource_arns),
473 },
474 ),
475 )
476
477 # ok from the response, not the rendered text (see scope-mode note above).
478 return OpResult(
479 not data.get("Errors"),
480 _format_batch_delete_response(
481 mode="ResourceArns",
482 data=data,
483 instrumentation_type=normalized_type,
484 ),
485 )
486
487
488def _render_location_identifier_help(action: str) -> str:
489 return f"""ERROR: Must provide one of:
490- location_hash
491- language + file_path (for code locations)
492
493Usage:
4941. {action} by hash:
495 {action}_instrumentation(location_hash="abc123...")
496
4972. {action} by code location:
498 {action}_instrumentation(language="Python", file_path="/app/file.py", ...)"""
499
500
501def delete_instrumentation(
502 service: str,
503 environment: str,
504 instrumentation_type: str,
505 location_hash: Optional[str] = None,
506 language: Optional[str] = None,
507 file_path: Optional[str] = None,
508 code_unit: Optional[str] = None,
509 class_name: Optional[str] = None,
510 method_name: Optional[str] = None,
511 line_number: Optional[int] = None,
512) -> OpResult:
513 """Delete a single instrumentation configuration.
514
515 The target can be resolved by `location_hash` or by a full location
516 description. The target can be resolved by `location_hash` or by a full code
517 location description.
518
519 Args:
520 service: Backend service identifier.
521 environment: Backend environment identifier.
522 instrumentation_type: BREAKPOINT or PROBE.
523 location_hash: Preferred identifier for an existing configuration.
524 language: Code language for code-location lookup.
525 file_path: Code file path for code-location lookup.
526 code_unit: Optional module/package name for code-location lookup.
527 class_name: Optional class name for code-location lookup.
528 method_name: Optional function/method name for code-location lookup.
529 line_number: Optional 1-based line number for code-location lookup.
530
531 Returns:
532 A human-readable success or failure message describing the deletion target
533 and troubleshooting guidance when lookup or deletion fails.
534 """
535 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
536 if type_error:
537 return OpResult(False, type_error)
538
539 location, location_error = parse_lookup_inputs(
540 normalized_type=normalized_type,
541 location_hash=location_hash,
542 language=language,
543 file_path=file_path,
544 code_unit=code_unit,
545 class_name=class_name,
546 method_name=method_name,
547 line_number=line_number,
548 allow_code_location_lookup=True,
549 )
550 if location_error:
551 if "missing location identifier input" in location_error:
552 return OpResult(False, _render_location_identifier_help("delete"))
553 return OpResult(False, f"ERROR: {location_error}")
554 if location is None:
555 # Defensive: parsers return (loc, None) or (None, error_text). This
556 # branch should be unreachable, but we return a user-facing error
557 # string (not ``raise``) so the tool's "always returns a string"
558 # contract holds even if a future parser bug fires this path.
559 return OpResult(
560 False, "ERROR: Internal error resolving location. Please report this issue."
561 )
562 target_desc = location.describe()
563
564 try:
565 gateway.delete_instrumentation_configuration(
566 InstrumentationType=normalized_type,
567 Service=service,
568 Environment=environment,
569 SignalType=SNAPSHOT_SIGNAL_TYPE,
570 LocationIdentifier=location.to_identifier(),
571 )
572 except gateway.GatewayError as err:
573 return OpResult(
574 False,
575 gateway.render_error(
576 err,
577 action=f"delete {normalized_type} instrumentation",
578 attempted_label="ATTEMPTED TO DELETE:",
579 attempted={
580 "Target": target_desc,
581 "Service": service,
582 "Environment": environment,
583 },
584 possible_causes=[
585 "Instrumentation doesn't exist at this location",
586 "Location parameters don't match exactly",
587 "Wrong service or environment identifier",
588 "Already deleted",
589 ],
590 troubleshooting=["Use list_instrumentations to see exact configuration details"],
591 ),
592 )
593
594 return OpResult(
595 True,
596 f"""Successfully deleted {normalized_type} instrumentation
597
598Target: {target_desc}
599Service: {service}
600Environment: {environment}
601
602TIP: Use list_instrumentations to verify removal.""",
603 )
604
605
606def get_instrumentation(
607 service: str,
608 environment: str,
609 instrumentation_type: str,
610 location_hash: Optional[str] = None,
611 language: Optional[str] = None,
612 file_path: Optional[str] = None,
613 code_unit: Optional[str] = None,
614 class_name: Optional[str] = None,
615 method_name: Optional[str] = None,
616 line_number: Optional[int] = None,
617) -> OpResult:
618 """Get the full backend configuration for a single instrumentation target.
619
620 The target can be resolved by `location_hash` or by a full location
621 description. The target can be resolved by `location_hash` or by a full code
622 location description.
623
624 Args:
625 service: Backend service identifier.
626 environment: Backend environment identifier.
627 instrumentation_type: BREAKPOINT or PROBE.
628 location_hash: Preferred identifier for an existing configuration.
629 language: Code language for code-location lookup.
630 file_path: Code file path for code-location lookup.
631 code_unit: Optional module/package name for code-location lookup.
632 class_name: Optional class name for code-location lookup.
633 method_name: Optional function/method name for code-location lookup.
634 line_number: Optional 1-based line number for code-location lookup.
635
636 Returns:
637 A human-readable configuration report including location details, capture
638 configuration, attribute filters, and backend metadata such as ARN and timestamps.
639 """
640 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
641 if type_error:
642 return OpResult(False, type_error)
643
644 location, location_error = parse_lookup_inputs(
645 normalized_type=normalized_type,
646 location_hash=location_hash,
647 language=language,
648 file_path=file_path,
649 code_unit=code_unit,
650 class_name=class_name,
651 method_name=method_name,
652 line_number=line_number,
653 allow_code_location_lookup=True,
654 )
655 if location_error:
656 if "missing location identifier input" in location_error:
657 return OpResult(False, _render_location_identifier_help("get"))
658 return OpResult(False, f"ERROR: {location_error}")
659 if location is None:
660 # Defensive: parsers return (loc, None) or (None, error_text). This
661 # branch should be unreachable, but we return a user-facing error
662 # string (not ``raise``) so the tool's "always returns a string"
663 # contract holds even if a future parser bug fires this path.
664 return OpResult(
665 False, "ERROR: Internal error resolving location. Please report this issue."
666 )
667 target_desc = location.describe()
668
669 try:
670 data = gateway.get_instrumentation_configuration(
671 InstrumentationType=normalized_type,
672 Service=service,
673 Environment=environment,
674 SignalType=SNAPSHOT_SIGNAL_TYPE,
675 LocationIdentifier=location.to_identifier(),
676 )
677 except gateway.GatewayError as err:
678 return OpResult(
679 False,
680 gateway.render_error(
681 err,
682 action="get instrumentation",
683 attempted_label="ATTEMPTED TO RETRIEVE:",
684 attempted={
685 "Target": target_desc,
686 "Service": service,
687 "Environment": environment,
688 },
689 possible_causes=[
690 "Instrumentation doesn't exist at this location",
691 "Location parameters don't match exactly",
692 "Wrong service or environment identifier",
693 ],
694 troubleshooting=["Use list_instrumentations to see all active instrumentations"],
695 ),
696 )
697
698 config = data.get("Configuration", {}) if isinstance(data, dict) else {}
699 if not config:
700 return OpResult(False, f"No instrumentation found for {target_desc}")
701
702 return OpResult(
703 True,
704 render_get_instrumentation_output(
705 config=config,
706 service=service,
707 environment=environment,
708 ),
709 )