Generated banner for Setting Up Cloudwatch Observability
Generated logo for Setting Up Cloudwatch Observability

Setting Up Cloudwatch Observability

AWSOfficialPublished by AWS

  • Skill
  • Tool integration
  • Cloud and DevOps
  • Apache-2.0
  • Medium risk

From the author

Sets up CloudWatch observability for the first time - Omni (CloudWatch Application Observability) and classic CloudWatch. Omni: creating a Space or Domain; access grants (who has access, at what level), access profiles bounding alerts, integrations, agents; instrumenting an app or AI agent with the plain ADOT SDK so traces reach Omni (Python/Node/Java/.NET on EC2/ECS/EKS/Lambda), incl. no-image-rebuild, CoreCLR vars; ingesting Azure or Google Cloud telemetry via the CloudWatch agent (VM/AKS, GCE/GKE); connecting Slack or the AWS DevOps Agent to a Space; whether GitHub or a custom MCP tool server (HTTP/stdio) can connect. CloudWatch: onboarding a service to Application Signals: ADOT auto-instrumentation, amazon-cloudwatch-observability add-on, monitored service, ServiceEvents, CI/CD metadata, Terraform. For using what is set up - queries, dashboards, alarms, Omni alerts, X-Ray, synthetics, Dynamic Instrumentation, AI agent evaluation, detecting Omni vs classic - use aws-observability.
— the description by AWS

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides the end-to-end setup procedures for two distinct CloudWatch observability products: CloudWatch Application Observability (Omni) and classic CloudWatch Application Signals. It acts as a central routing hub, helping users identify the correct product based on their requirements and guiding them through the specific onboarding, instrumentation, and configuration steps for each. It covers resource creation, access management, and telemetry ingestion, while clearly delineating the boundaries between the two products to ensure correct implementation.

What it does

  • Routes setup requests to the correct product path based on user intent and environment.
  • Guides users through CloudWatch Omni Domain and Space creation, access grants, and profile configuration.
  • Provides instructions for instrumenting applications and AI agents with the ADOT SDK for Omni.
  • Details the onboarding process for classic CloudWatch Application Signals, including auto-instrumentation and add-ons.
  • Supports telemetry ingestion configuration for Azure and Google Cloud environments.
  • Manages integration setups for Slack and the AWS DevOps Agent.

When to use it

  • When you need to set up CloudWatch Application Observability (Omni) for the first time.
  • When you need to onboard a service to classic CloudWatch Application Signals.
  • When you are unsure which CloudWatch observability product is appropriate for your workload.
  • When you need to configure access grants, profiles, or integrations for an existing Space.
  • When you need to instrument an application or AI agent to export telemetry to CloudWatch.

Advantages

  • Prevents configuration conflicts by enforcing mutually exclusive setup paths for Omni and Application Signals.
  • Provides a structured, step-by-step sequence for complex resource hierarchies like Domains and Spaces.
  • Clarifies the distinction between setup tasks and day-to-day operational tasks.
  • Ensures correct principal resolution and permission scoping for access grants and profiles.

Usage examples

“How do I start setting up CloudWatch Application Observability?”
The agent will explain the setup order (Domain, Space, grants, telemetry) and direct you to the app-basics reference to begin.
“I need to onboard my EKS service to Application Signals.”
The agent will route you to the Application Signals onboarding guide and the specific EKS-language instrumentation steps.
“How do I give a team access to my CloudWatch Omni Space?”
The agent will guide you through resolving the principal and using the access-grants reference to configure the appropriate permission level.
“Can I connect my GitHub repository to CloudWatch Omni?”
The agent will clarify that Omni does not support GitHub integration and stop the setup process.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/aws/setting-up-cloudwatch-observability/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip setting-up-cloudwatch-observability.zip -d .agents/skills

Documentation by AWS

Setting Up CloudWatch Observability

Scope: First-time CloudWatch observability setup, for both products — a CloudWatch Application Observability (Omni) Space from creation through first traces flowing, and onboarding a service to classic CloudWatch Application Signals. For using what is already set up (queries, dashboards, alarms, Omni alerts, evaluations, live debugging), route to aws-observability.

Two products, two reference folders

This skill owns setup for two products that share the CloudWatch name but are separate services. The folder a reference lives in is the signal for which product it belongs to — the same convention aws-observability uses:

FolderProductWhat setup means there
references/cloudwatch-omni/CloudWatch Omni (Application / Agent Observability)Domain → Space → grants → telemetry in → plain ADOT SDK instrumentation. No add-on, no CloudWatch Agent application_signals config, no port 4316.
references/cloudwatch/Classic CloudWatchOnboarding a service to Application Signals: ADOT auto-instrumentation, the amazon-cloudwatch-observability EKS add-on, the CloudWatch Agent, monitored service, ServiceEvents, CI/CD git/deployment metadata, port 4316.

The two instrumentation paths are mutually exclusive on a given workload — the Omni path explicitly forbids the Application Signals env vars and the add-on, and vice versa. Decide the product first (Routing Rules 1–4 below), then stay inside that folder. Enabling one does not replace the other; an account may run both, on different services.

Works best with the AWS MCP server — enables running AWS CLI commands directly. All guidance also works with standard AWS CLI access (aws cloudwatchomni ... on the Omni path; aws eks, aws iam, and aws application-signals on the Application Signals path).

Concepts — Omni (references/cloudwatch-omni/)

These are Omni's resources. None of them exist on the Application Signals path — that one has no Domain, Space, grant, or Dataset; its unit is a monitored service, and access is plain IAM. Do not ask an Application Signals onboarding customer for a spaceId.

TermWhat it is
DomainThe identity boundary. Carries the authorization provider (IAM or Identity Center) and owns the endpoint URL customers reach Omni through. One per account, or one shared across an AWS Organization.
SpaceA workspace holding telemetry, in exactly one account and one Region. Created under a Domain. At most one per account per Region. Region rule: under an IAM-only Domain a Space may sit in a Region other than the Domain's; under an account-scoped Identity Center Domain the Space must be in the Domain's own Region; under an org-scoped Identity Center Domain it must be in a Region the instance is replicated to.
Access grantAttaches a principal — person, group, IAM identity, or async workload — to one Space at a permission level. The only way anyone reaches data through a Space — it does not restrict the source CloudWatch log groups, which stay readable under their own IAM.
Access ProfileA named boundary for async workloads (alerts, integrations, agents) that act without a person in the loop. It is only a named container: create-access-profile takes a Space, a name, and a description and nothing else — no permission, action, or scope input. It does something only once two separate sets of grants exist (what the profile may do; which workloads may assume it) and a workload names it.
DatasetWhat queries run against. Telemetry arrives through the CloudWatch OTLP endpoints, or by forwarding what is already in CloudWatch log groups.

Omni setup order from nothing: Domain → Space → grants → telemetry in → instrumentation. (The Application Signals equivalent is much shorter and has no prerequisite resources — add-on/agent, IAM, then the per-platform enablement change; see references/cloudwatch/application-signals-onboarding.md.) "Telemetry in" (a collector exporting to CloudWatch's OTLP endpoints, plus dataset forwarding for what is already in CloudWatch) is its own step, separate from instrumenting the workloads. Access Profiles are conditional, not a step in the sequence — only when async workloads (alerts, integrations, agents) are involved. Whenever you give this sequence, also say that instrumentation or forwarding started before a Space exists appears to succeed while delivering telemetry nowhere the customer can see — a customer who checks Omni first reads a working, empty Space as a failure. For the concept relationships and the full arc, see references/cloudwatch-omni/app-basics.md.

This is a routing skill. Classify the user's setup request and delegate to the correct reference. Omni references live under references/cloudwatch-omni/; classic-CloudWatch (Application Signals) references live under references/cloudwatch/.

User intentReference
Onboard a service to Application Signals (auto-instrumentation, the amazon-cloudwatch-observability EKS add-on, CloudWatch Agent IAM for Application Signals, monitored service, reporting telemetry to Application Signals, ServiceEvents, the two onboarding tiers)references/cloudwatch/application-signals-onboarding.md
Propagate ServiceEvents git/deployment metadata through CI/CD (the 5 OTEL_AWS_SERVICE_EVENTS_* vars, per-provider patterns)references/cloudwatch/application-signals-cicd-metadata.md
Per-platform × per-language Application Signals enablement steps once platform and language are knownThe matching references/cloudwatch/appsignals-guides/<platform>-<language>.md (e.g. references/cloudwatch/appsignals-guides/eks-python.md)
Turn on Dynamic Instrumentation for a service at onboarding time (the OTEL_AWS_DYNAMIC_INSTRUMENTATION_* vars and their IAM)references/cloudwatch/application-signals-onboarding.md (Step 5d). Using it to debug is aws-observability
Understand what Omni is, its concepts, or where to startreferences/cloudwatch-omni/app-basics.md
Instrument an AI agent (ADOT, OpenInference, framework detection, trace verification), or deploy an agent to production and get traces flowing to CloudWatch — env vars per platform (AgentCore, Lambda, or other platforms such as ECS/EC2/EKS), routing spans to a custom trace log group, IAM permissions needed, how the export authenticates (AWS credentials, no API token), ADOT version requirementsreferences/cloudwatch-omni/omni-agents-instrumentation/omni-agents-instrumentation.md (§ Production deployment for the deploy case)
Per-framework OpenInference guide (LangChain, LangGraph, Strands, CrewAI, OpenAI Agents, Vercel AI) once the agent framework is knownreferences/cloudwatch-omni/omni-agents-instrumentation/openinference-framework-guide.md, then the matching references/cloudwatch-omni/omni-agents-instrumentation/instrument-<framework>.md
Instrument an application (ADOT SDK on EC2/ECS/EKS/Lambda — Python, Node.js, Java, .NET)references/cloudwatch-omni/instrumentation/instrumentation.md
Emit a custom application or agent metric so it is queryable in Omni (why OTLP and not PutMetricData/EMF)references/cloudwatch-omni/instrumentation/instrumentation.md (§ Custom metrics)
Create an account-scoped Space or Domainreferences/cloudwatch-omni/spaces-and-domains.md
Whether a Space can be in a different Region from its Domainreferences/cloudwatch-omni/spaces-and-domains.md (Prerequisites → Region rules)
The AgentCore evaluation role that create-space asks for — what it is, whether to create onereferences/cloudwatch-omni/spaces-and-domains.md (Step 3 → Also resolve the AgentCore evaluation role)
Let Omni run Bedrock models for a Space (Agent Playground, evaluations run from Omni), or a Bedrock AccessDeniedException from those featuresreferences/cloudwatch-omni/spaces-and-domains.md (Step 3 → Optional: let Omni invoke Bedrock models)
A Space that was created successfully but returns an authorization error when usedreferences/cloudwatch-omni/spaces-and-domains.md (Troubleshooting → If the Space was created but cannot be used) — a space access role trust-policy problem, not a grant problem
Create a Domain shared across an AWS Organizationreferences/cloudwatch-omni/org-domains.md
Configure access grants for people or IAM identities or alertsreferences/cloudwatch-omni/access-grants.md
An aws cloudwatchomni command reporting that the service is not supported, or a request to repair the customer's CLI or SDK so Omni commands workNot a setup task — a local client-version problem. STOP and route to aws-observability
A programmatic caller denied on a Space it already had working — a CI role, SDK client, CloudFormation role, coding agent, or a workload under an Access Profilereferences/cloudwatch-omni/access-grants.md (Grants, IAM, and Access Profiles), and aws-observability for the API-side view
Bound an alert, integration, or agent with an Access Profilereferences/cloudwatch-omni/access-profiles.md
Deploy an OTel Collector so an instrumented app has somewhere to export to (EC2/ECS/EKS) — it exports to CloudWatch's own per-signal OTLP endpointsreferences/cloudwatch-omni/instrumentation/collector.md
An OTLP collector already running (a CloudWatch Agent with an opentelemetry section or OTLP on 4317/4318, or otelcol-contrib) whose telemetry does not arrive, in whole or for one signal such as tracesreferences/cloudwatch-omni/instrumentation/collector.md (Verify), then the platform collector guide it links for the troubleshooting table
Enable Transaction Search so traces reach a Space (spans land in aws/spans only once it is on — per account, per Region)references/cloudwatch-omni/instrumentation/collector.md (Step 1)
Forward telemetry already in CloudWatch into the Datasetreferences/cloudwatch-omni/data-forwarding-and-centralization.md
Send your application's own telemetry from Azure (the logs/metrics/traces your service emits, via the CloudWatch agent on an Azure VM/AKS)references/cloudwatch-omni/azure-ingestion/azure-ingestion.md (intent triage), then references/cloudwatch-omni/azure-ingestion/custom-telemetry.md (the CloudWatch-agent-on-VM/AKS procedure)
Send your application's own telemetry from Google Cloud (the logs/metrics/traces your service emits, via the CloudWatch agent on a GCE instance/GKE cluster)references/cloudwatch-omni/gcp-ingestion/gcp-ingestion.md (intent triage), then references/cloudwatch-omni/gcp-ingestion/custom-telemetry.md (the CloudWatch-agent-on-GCE/GKE procedure)
Connect Slack to a Space for the first timereferences/cloudwatch-omni/slack-integration.md
Connect the AWS DevOps Agent to a Space (Settings → Integrations → AWS DevOps Agent), an empty agent-space picker, or Investigations reporting the AWS DevOps Agent is not connectedreferences/cloudwatch-omni/devops-agent-integration.md. Creating the DevOps Agent space itself and running or chatting with investigations belong to the aws-agents-for-devsecops plugin
Whether a custom MCP tool server can be registered (Omni has none)references/cloudwatch-omni/custom-mcp-integration.md
Enable or configure CloudWatch Omni Intelligence for a Space — attach an optional customer-managed KMS key (put-intelligence-configuration), connect an intelligence integration (AWS_INTEGRATION, AWS_CONFIG_SLREC, EXTERNAL_AGENT), check current config, or troubleshoot enablement (404, AccessDenied, empty graph)references/cloudwatch-omni/intelligence-setup.md

Routing Rules

  1. If the user is asking what Omni is, what a Domain or Space or grant means, where to start, or the end-to-end order of steps from nothing — rather than asking to perform one setup step — route to references/cloudwatch-omni/app-basics.md and answer the sequence from its "Setup order" section (not from a single procedure file such as collector.md, which covers one step). It also carries the boundary against aws-observability. A question about one concept's rules — a Space's Region relative to its Domain, the roles create-space needs — belongs to the procedure file for that concept (spaces-and-domains.md), which the routing table names.

  2. If the request is about instrumenting an AI agent project (adding OTel to a framework such as LangChain, LangGraph, Strands, CrewAI, OpenAI Agents, or Vercel AI), it is Omni — route to references/cloudwatch-omni/omni-agents-instrumentation/omni-agents-instrumentation.md. There is no Application Signals path for an agent framework.

  3. If the request is about instrumenting a general application (a service on EC2, ECS, EKS, or Lambda in Python, Node.js, Java, or .NET — not an AI-agent framework), decide the product before opening any procedure file — the two paths are mutually exclusive on a workload:

    • Names Application Signals, ServiceEvents, a "monitored service", the service map, the amazon-cloudwatch-observability add-on, the CloudWatch Agent's application_signals configuration, or port 4316 → the CloudWatch path: references/cloudwatch/application-signals-onboarding.md, then the matching references/cloudwatch/appsignals-guides/<platform>-<language>.md.

    • Names Omni, Application Observability, a Space, a Domain, or a Dataset — or explicitly rules Application Signals out ("plain OTel", "no add-on", "no agent sidecar") → the Omni path: references/cloudwatch-omni/instrumentation/instrumentation.md.

    • Naming the CloudWatch Agent alone decides nothing: it is also the Omni path's default collector. An agent with an opentelemetry section or OTLP receivers on 4317/4318 is the Omni collector, and its delivery problems belong to rule 6.

    • Names neither → rule 4.

  4. If an instrumentation, ADOT, or collector request names neither product (neither Omni or a Space, nor Application Signals, ServiceEvents, or the amazon-cloudwatch-observability add-on), probe for a Space in the target Region before choosing. list-spaces is account-global: the --region flag only selects the endpoint, and the response lists every Space in the account, each with its own region, so filter to the target Region rather than trusting a non-empty list: aws cloudwatchomni list-spaces --region <region> --query "items[?region=='<region>']". A Space in that Region → the Omni path, references/cloudwatch-omni/instrumentation/instrumentation.md. An empty filtered list → the customer has not adopted Omni in that Region, so Application Signals is the live product for them: references/cloudwatch/application-signals-onboarding.md. Either way the request stays in this skill — the probe picks the folder, not the skill. If the probe errors with an unknown service, that is the CLI model, not evidence Omni is absent — fall back to the customer's wording and ask only if still inconclusive.

  5. If the request is about Space/Domain creation or configuration, route to the matching setup reference. An authorization error that appears the moment a just-created Space is used is part of this — it is a space access role trust-policy problem (create-space never verifies the role can be assumed), so route to references/cloudwatch-omni/spaces-and-domains.md → "If the Space was created but cannot be used", not to the access-grants reference.

  6. If the user asks about getting telemetry to a destination — telemetry not yet reaching CloudWatch — deploy an OTel Collector on EC2/ECS/EKS so an instrumented workload has somewhere to send OTLP, and wire the app's OTEL_EXPORTER_OTLP_ENDPOINT to it: references/cloudwatch-omni/instrumentation/collector.md. The same file covers an OTLP collector that is already deployed but whose telemetry does not arrive, in whole or for one signal (its Verify section and the platform guide's troubleshooting table). The collector exports straight to CloudWatch's own per-signal OTLP endpoints. If instead the telemetry is already in CloudWatch log groups and needs forwarding into the Dataset, route to references/cloudwatch-omni/data-forwarding-and-centralization.md. Both the collector's OTLP export and CloudWatch's OTLP endpoints authenticate with SigV4 (AWS credentials), including OIDC-federated credentials for a workload outside AWS (see references/cloudwatch-omni/azure-ingestion/custom-telemetry.md or references/cloudwatch-omni/gcp-ingestion/custom-telemetry.md). A purely token-based ingestion path for a sender that cannot obtain AWS credentials at all is not covered by these skills; do not improvise one, and say so plainly.

  7. If the request is about getting Azure or Google Cloud telemetry into CloudWatch, route by cloud — references/cloudwatch-omni/azure-ingestion/azure-ingestion.md for Azure, references/cloudwatch-omni/gcp-ingestion/gcp-ingestion.md for Google Cloud — and decide by intent. If the customer wants the telemetry their own application produces (the logs/metrics/traces from their code), that is supported via the CloudWatch agent on an Azure VM or AKS cluster, or on a GCE instance or GKE cluster — follow the reference. If they want telemetry their cloud's resources emit on their own (the equivalent of AWS VPC flow logs / Route 53 logs: Azure resource and platform logs, or Google Cloud Audit Logs), that is not supported — say so and do not attempt a setup. If the request names no cloud, ask which one before giving any steps.

  8. If the user asks to connect, enable, or authorize Slack for a Space for the first time (including granting the operator permission to use it), route to references/cloudwatch-omni/slack-integration.md. Using Slack after it is connected (posting findings to a channel, mentioning the assistant, or searching Slack) happens in Slack and the console and is not covered by these skills; Slack as an alert notification target is covered by aws-observability's Omni alerts reference.

  9. Application Signals, ServiceEvents, and Dynamic Instrumentation are CloudWatch features, and they split by setup versus use — not by skill. Enabling them on a service that does not have them yet is setup and belongs here, under references/cloudwatch/: Application Signals onboarding and ServiceEvents in references/cloudwatch/application-signals-onboarding.md (with the CI/CD metadata chain in references/cloudwatch/application-signals-cicd-metadata.md), and switching Dynamic Instrumentation on at instrumentation time in that same file's Step 5d. They must never appear in an Omni instrumentation change — for that they remain out of scope, and rule 3 is how you tell the two paths apart. Using them once the service reports — reading the service map, alarming on Application Signals metrics, or placing breakpoints and reading snapshots with Dynamic Instrumentation — is day-to-day work: STOP and route to aws-observability.

  10. If the user already has a working Space, or a service already reporting to Application Signals, and is asking about queries/dashboards/alarms/alerts/investigation, STOP and route to aws-observability.

  11. If the user asks to register their own MCP tool server, connect a custom MCP server, or set up MCP authentication: CloudWatch Omni does not let customers add a custom MCP server. There is no console screen for it and the integration type is not enabled for customer use, so the attempt is refused. Say so plainly and stop; do not describe a setup flow, and do not offer a substitute. See references/cloudwatch-omni/custom-mcp-integration.md.

  12. If the user asks to connect GitHub, link a GitHub organization or repositories, or enrich the application map from source code: CloudWatch Omni has no GitHub integration. Say so plainly and stop — do not walk through a setup. Do not offer a substitute: the Application Signals GitHub Action and the CloudWatch Logs GitHub audit-log source are separate CloudWatch features, not a way to connect GitHub to Omni, and a custom MCP server is not a GitHub integration. A GitHub-related feature found in public documentation is not evidence of an Omni GitHub integration.

  13. If the user asks to enable or configure CloudWatch Omni Intelligence — attach or remove a customer-managed KMS key for intelligence data, add an intelligence integration (AWS_INTEGRATION, AWS_CONFIG_SLREC, or EXTERNAL_AGENT), read the current intelligence configuration, or troubleshoot why it will not turn on (a 404 UnknownOperationException, an AccessDeniedException, or an empty context graph) — route to references/cloudwatch-omni/intelligence-setup.md. That reference is the source of truth for the KMS ARN rules (full key ARN only, kms:Decrypt/kms:GenerateDataKey, exactly one of --kms-key-arn/--remove-kms-key, and that revoking the key makes intelligence data unrecoverable), the per-integration extra IAM, the two AccessDenied causes, and why "not enabled" is a 404 rather than an AccessDenied. Reading or interpreting the intelligence data once it is on is day-to-day use: STOP and route to the aws-observability skill — triaging insights, observations, or anomalies goes to its intelligence.md reference, which covers the ListInsights / ListObservations / GetContextGraph operations and the 404-vs-403-vs-local error rule; a pure topology or blast-radius question goes to its context-graph.md reference. For Slack specifically, route to references/cloudwatch-omni/slack-integration.md; for a custom MCP server, route to references/cloudwatch-omni/custom-mcp-integration.md (not supported). Do not offer GitHub, PagerDuty, Teams, MODEL_PROVIDER, or CUSTOM_MCP as intelligence integration types.

  14. If the request names neither product — "add observability to my API", "how should I begin monitoring my workload", with no mention of Omni, a Space, a Domain, or of a CloudWatch feature — do not assume either one. Which product a customer wants to set up is their intent, not something the account's current state tells you, so ask, framed product versus product: CloudWatch Omni (Application/Agent Observability — Domains, Spaces, grants, a Dataset) or CloudWatch (log groups, alarms, Log Insights, Application Signals). Both answers are served here, from different reference folders, so this is a question about which product to set up — not about which skill to use, and not a reason to hand the request to aws-observability. In the same message, explain the boundary so they can choose — it is setup versus use: creating or configuring a Domain, Space, grant, or Access Profile, getting telemetry flowing for the first time, instrumenting an application or agent for Omni, and onboarding a service to Application Signals are all first-time setup and belong here; querying telemetry, building dashboards, configuring alarms or Omni alerts, investigating a live problem, and debugging with Dynamic Instrumentation are day-to-day use and belong to aws-observability. Do not start walking through Domain/Space creation or Application Signals onboarding until they confirm which product; having asked, stop and wait.

  15. If the user asks to connect the AWS DevOps Agent to a Space, or the connect card or Investigations shows it is not connected, route to references/cloudwatch-omni/devops-agent-integration.md. These skills cover only that Omni-side connection. Creating or configuring the DevOps Agent space, and running, following, or chatting with its investigations from a coding agent, belong to the agent's dedicated aws-agents-for-devsecops plugin; point the customer there rather than reconstructing that workflow.

Files

references/cloudwatch-omni/instrumentation/instrumentation.md

Instrument an Application with ADOT (CloudWatch Application Observability)

Instrument an application that is not yet instrumented so it emits OpenTelemetry traces and metrics, using the AWS Distro for OpenTelemetry (ADOT) auto-instrumentation SDKs. This is plain OTel instrumentation — the ADOT SDK is wired into the workload and exports over OTLP, to the SDK's default endpoint unless the Scope table below calls for a collector or an existing destination.

Before you start: confirm a Space exists in the target Region. list-spaces is account-global (the --region flag only selects the endpoint; the response lists every Space in the account, each with its own region), so filter to the Region rather than trusting a non-empty list: aws cloudwatchomni list-spaces --region <region> --query "items[?region=='<region>']". If that filtered list is empty, stop and run ../spaces-and-domains.md first — instrumentation started before a Space exists appears to succeed while delivering telemetry nowhere the customer can see.

Never modify application source code (.py, .js, .ts, .java, .cs). Only edit infrastructure-as-code, Dockerfiles, CI/CD workflows, dependency files, and deployment manifests. Make the minimum change needed and preserve existing configuration. Present changes for the user to review; do not run terraform apply, cdk deploy, kubectl apply, helm install/helm upgrade, kubectl annotate, kubectl delete, or aws ecs update-service automatically. That applies to the collector step too: some of it is imperative commands rather than a diff, and those still belong to the user.

Scope

The job is always instrumentation — getting the ADOT SDK loaded into the running workload and emitting telemetry. Whether it also includes a destination (a collector the SDK exports to) depends on what the user said and what already exists:

The requestDo
Says a destination already exists or isn't part of this change, or asks for instrumentation onlyInstrumentation only
Asks for a destination, or says nothing receives its telemetry yetInstrumentation, then a collector via collector.md
Says neither — for example "set up tracing on this service so we can see it in our Space"Check the files you were given for an existing destination (below). Found → instrumentation only, pointed at it. None → the answer includes the collector changes from collector.md alongside the instrumentation, in the same diff — not offered as a later step, since without one the telemetry the user asked for never arrives

What counts as an existing destination: a collector container, sidecar, Deployment, or DaemonSet (cloudwatch-agent, aws-otel-collector, otelcol-contrib, otel-collector); a CloudWatch Agent installed in UserData or a launch template with an opentelemetry receiver in its config; or OTEL_EXPORTER_OTLP_ENDPOINT already set on the workload. A CloudWatch Agent configured only for Application Signals (port 4316) is not one — it has no OTLP receiver for this path. When you add a collector because none was found, say which files you checked, and that the collector step can be dropped if one already runs outside them (a cluster-wide collector on EKS, an agent baked into an AMI).

Pointing the SDK at the destination. The SDK falls back to its spec defaults (http://localhost:4318 for OTLP/HTTP, http://localhost:4317 for OTLP/gRPC). Which of the two applies depends on the SDK: the Java agent defaults to OTLP/gRPC on 4317, while the Python, Node.js and .NET distros default to OTLP/HTTP on 4318. A collector on the same host or in the same task that listens on that port needs no endpoint setting; set OTEL_EXPORTER_OTLP_ENDPOINT (and OTEL_EXPORTER_OTLP_PROTOCOL if it differs) only to reach one elsewhere, and only to an address the files show. Never invent an endpoint.

In scopeOut of scope
Installing / injecting the ADOT auto-instrumentation SDKApplication Signals, ServiceEvents, Dynamic Instrumentation
Setting the standard OTel SDK environment variablesApplication Signals wiring of any kind: the amazon-cloudwatch-observability EKS add-on, the CloudWatch Agent's application_signals config sections, and port 4316
Service name and resource attributes
A collector — the CloudWatch Agent by default, or upstream otelcol-contrib when the user will not run the agent — when the table above calls for one, see collector.md

The collector exports straight to CloudWatch's own per-signal OTLP endpoints, with upstream otelcol-contrib as the backup. ../data-forwarding-and-centralization.md is not an alternative here — it accepts no telemetry over OTLP, it forwards what is already in CloudWatch log groups into a Dataset.

Lambda is outside this check: the ADOT layer exports to the function's own X-Ray receiver (see "IAM follows the destination" below), so a Lambda change never adds a collector.

Do NOT add these

These belong to CloudWatch Application Signals and must not appear in an Omni instrumentation change. If the user explicitly wants Application Signals instead, stop and switch to the sibling CloudWatch path in this same skill: references/cloudwatch/application-signals-onboarding.md, then the matching references/cloudwatch/appsignals-guides/<platform>-<language>.md. That is a folder switch, not a skill switch — do not hand the request to aws-observability, which holds no onboarding procedure.

  • OTEL_AWS_APPLICATION_SIGNALS_ENABLED=true, OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT

  • OTEL_AWS_SERVICE_EVENTS_* (git/deployment metadata, PACKAGES_INCLUDE)

  • OTEL_AWS_OTLP_LOGS_ENDPOINT, OTEL_AWS_OTLP_METRICS_ENDPOINT

  • OTEL_AWS_DYNAMIC_INSTRUMENTATION_*

  • The amazon-cloudwatch-observability EKS add-on, and the instrumentation.opentelemetry.io/inject-* annotation that depends on it

  • Any endpoint on the CloudWatch Agent's port 4316 (a CloudWatch-specific port, not an OTel default)

One exception, on Lambda only: set OTEL_AWS_APPLICATION_SIGNALS_ENABLED=false explicitly. This is required, not stylistic — the layer's otel-instrument wrapper defaults the variable to true when it is unset, so omitting it silently ENABLES Application Signals. See the Lambda guides.

A benign ServiceEvents WARN on ADOT Java 2.30.0. Even with none of these variables set, the Java agent logs OTEL_AWS_SERVICE_EVENTS_FUNCTION_INSTRUMENT_ENABLED=true but OTEL_AWS_SERVICE_EVENTS_PACKAGES_INCLUDE ... around four times during startup, interleaved with [SERVICE_EVENTS] INFO lines such as ServiceEventsInstrumentation initialized at startup that read as though it were enabled, and then ServiceEventsSpanProcessor not registered (serviceevents disabled). It defaults the flag internally and self-disables; nothing leaks. Expect the warning and leave it alone — do not "fix" it by setting OTEL_AWS_SERVICE_EVENTS_FUNCTION_INSTRUMENT_ENABLED=false, which is one of the variables above.

IAM follows the destination, not the instrumentation

On EC2, ECS, and EKS, instrumentation itself needs no IAM changes — loading the SDK into a workload grants nothing, and the SDK is left pointing at its default local endpoint. Do not add CloudWatchAgentServerPolicy, AWSXRayDaemonWriteAccess, or application-signals:* permissions to those workloads.

That is not the same as saying the workload will never need permissions — it means instrumentation does not. Permissions follow whatever ends up talking to AWS:

  • A destination the user already has — the permission belongs with it. Do not add a policy speculatively.

  • A collector deployed via collector.md — the collector is the thing calling AWS, so it does get IAM: CloudWatchAgentServerPolicy on the instance profile (EC2), the task role (ECS), or an IRSA role (EKS). The application still gets nothing.

Lambda is different, and does get a policy here. The ADOT layer exports to the execution environment's X-Ray receiver under active tracing, so the destination is already known and lives inside the function's own configuration. The Lambda guides therefore grant the execution role xray:PutTraceSegments and xray:PutTelemetryRecords — via AWSXRayDaemonWriteAccess, or the inline grant CDK adds when tracing is enabled. CloudWatchLambdaApplicationSignalsExecutionRolePolicy is still out of scope, since Application Signals is disabled.

Step 1: Determine platform and language

Detect from the IaC and application code, and confirm with the user if ambiguous.

  • EKS: k8s Deployment manifests (kind: Deployment), Helm charts, kubectl in scripts, Terraform aws_eks_*

  • ECS: ECS task definitions, containerDefinitions, Terraform aws_ecs_*

  • EC2: EC2 instances, userdata scripts, launch templates, Terraform aws_instance

  • Language: requirements.txt/pyproject.toml/*.py → Python; package.json/*.ts/*.js → Node.js; pom.xml/build.gradle/*.java → Java; *.csproj/*.sln/*.cs → .NET

If the workload is an AI agent built on a framework (LangChain, LangGraph, Strands, CrewAI, OpenAI Agents, Vercel AI), stop — use ../omni-agents-instrumentation/omni-agents-instrumentation.md instead. That path instruments at the framework layer.

Step 2: Read the matching guide

When the Scope table calls for a collector, continue to collector.md — it is language-agnostic and covers EC2, ECS, and EKS. Lambda is excluded: there is nowhere to run a collector in it, and its layer already exports to the execution environment's X-Ray receiver.

How the SDK gets in

The mechanism differs by platform, but on ECS and EKS it is the same idea — an init container copies the SDK into a shared volume at startup, so the application image never changes:

PlatformMechanismTouches the image?
EKSinitContainers + emptyDir volume, env vars on the containerNo
ECSInit container in the task definition + shared volumeNo
EC2Install in userdata; env vars on the process / systemd unitNo
LambdaADOT Lambda layer + AWS_LAMBDA_EXEC_WRAPPERNo

For instrumentation alone, no cluster-level components are installed on EKS — no add-on and no operator; injection is written directly into the Deployment manifest. (When the Scope table calls for a collector, collector.md deploys the CloudWatch Agent by Helm, which necessarily brings its operator. That is expected there, and is not the amazon-cloudwatch-observability add-on.)

Lambda is the odd one out: the execution environment supplies an X-Ray receiver that the layer exports to, so unlike the other three platforms its export path is already wired. That is why the Lambda guides look different and why OTEL_AWS_APPLICATION_SIGNALS_ENABLED=false is set explicitly there.

Verifying without a receiver

When the change is instrumentation only and no destination exists yet, the SDK exports to its default local endpoint (localhost:4318, or localhost:4317 for Java) where nothing is listening. OTLP connection errors in the logs after instrumenting are expected and are not a failure — they mean the SDK loaded and is trying to export. Tell the user the errors will clear once a receiver exists.

Do not rely on "the SDK announces itself" as the check — it only works for Java. What a working deployment actually logs, by language:

LanguageWhat the app log actually shows
Javaopentelemetry-javaagent - version: <x> — a reliable signal
Node.jsautomatic instrumentation started successfully. Grep that string, not opentelemetry, which also matches benign warnings containing "Failed"
PythonNo line containing "opentelemetry". The real one is configurator already loaded
.NETNothing in the application log at the default level — a log grep there reports failure on a healthy app. But the SDK writes its own file at the default level: /tmp/otel-dotnet-auto-<pid>-<App>-Managed-<date>.log. Read that instead. The profiler has been initialized with NN direct definitions is the attach signal. The export lines in it depend on whether a receiver exists yet — Export succeeded for .../v1/traces once a collector is running, export failures before then, which is expected

For Python and .NET, verify by other means: the init container exiting 0, the loader variables present in the running process (printenv, or tr '\0' '\n' < /proc/1/environ — use PID 1 in a single-process container, since slim images often lack pgrep), and ultimately spans at the destination.

For .NET the strongest check is the SDK's own Managed log above. If you also check paths, expand the variable inside the container — kubectl exec <pod> -- sh -c 'test -f "$CORECLR_PROFILER_PATH"'; unwrapped, it expands in your local shell where it is unset and always reports failure. Note DOTNET_ADDITIONAL_DEPS and DOTNET_SHARED_STORE legitimately dangle in the container image.

If a collector was deployed in the same change, these errors should be absent — and on the CloudWatch Agent, all of them. The agent builds traces, metrics and logs pipelines from its opentelemetry section, so a correctly wired app logs no OTLP errors at all — a GET probe of /v1/logs returns 405 (method not allowed on a path that IS registered), not 404. Treat any OTLP error on the agent path as a real problem.

On the otelcol-contrib backup only: the hand-written Option A config in collector.md defines only traces and metrics pipelines, while the SDKs default OTEL_LOGS_EXPORTER=otlp — so the receiver returns 404 on /v1/logs and the app logs that permanently. That is expected there and is not a connectivity failure; traces flow fine alongside it. Either add a logs pipeline or set OTEL_LOGS_EXPORTER=none. Do not set OTEL_LOGS_EXPORTER=none on the CloudWatch Agent path — it would discard a signal the agent delivers for free.

Step 3: Set the service identity

Every guide sets these two, and nothing else about export:

VariableValue
OTEL_SERVICE_NAMEThe service's name as it should appear in Omni
OTEL_RESOURCE_ATTRIBUTESOptional additional resource attributes, e.g. deployment.environment=production

Derive OTEL_SERVICE_NAME from the existing workload name in the IaC (deployment name, ECS service name, or the application name) rather than inventing one. If it cannot be determined, ask.

Custom metrics: what makes a metric queryable in Omni

Auto-instrumentation produces the standard request metrics on its own. A custom metric — a latency histogram, a queue depth, a business counter — is a code change the customer makes in the application, and this reference never edits source; so when the question is "how do I get my own metric to show up in Omni", hand them the rule and the instrument to use rather than a file diff.

Omni serves metrics from a single OpenTelemetry-native, PromQL-queryable surface. A metric is on that surface when it arrives as an OTLP metric and carries the workload's OTel resource attributes — service.name above all, which Omni exposes as the label "@resource.service.name" and uses to tie the metric to the service. Two paths put a metric there, and they are the same two the rest of this file wires:

  • OTel SDK in the application — create the instrument through the OTel Metrics API (Meter → Histogram for latency, Counter for counts, Gauge for levels) and record to it. The ADOT SDK the guides above load already runs a MeterProvider exporting over OTLP, so a custom instrument rides the same exporter, endpoint, and resource as the auto-instrumented telemetry — no extra configuration in the app.

  • An OTel Collector the application exports OTLP to — the SDK sends to the collector, which exports to CloudWatch's OTLP metrics endpoint (https://monitoring.<region>.amazonaws.com/v1/metrics, collector.md).

Either way the exporter must reach a destination that delivers into the account's Dataset in a Region with a Space; an SDK left at its localhost default records the metric and delivers it nowhere.

On Lambda, neither path is wired. The layer's export goes to the execution environment's X-Ray receiver, which takes traces, not metrics, and there is nowhere in the function to run a collector. This reference has no verified configuration that sends a custom OTLP metric from a function to the OTLP metrics endpoint, so say that plainly: give the rule above, and do not present the layer as already exporting the metric or invent exporter variables for it.

What does not work, and why customers try it: the CloudWatch PutMetricData API and Embedded Metric Format (EMF) log lines are the familiar CloudWatch ways to publish a custom metric — and they produce a CloudWatch metric in a CloudWatch namespace, which is a different surface. CloudWatch metrics are not queryable in Omni PromQL. The one bridge between the surfaces, OTel enrichment, projects AWS-vended service metrics (EC2, Lambda, RDS, ...) onto the OTel surface and does not apply to a custom application metric, so EMF or PutMetricData on its own never makes a custom metric visible in Omni. (The IAM action the OTLP metrics endpoint authorizes against is also named cloudwatch:PutMetricData; that is the permission, not the API — a metric sent through the OTLP endpoint stays OTel-native.)

Constraints:

  • You MUST NOT present PutMetricData or EMF as a way to get a custom metric into Omni. If the customer already publishes that way, the answer is to emit the metric through the OTel SDK (or a collector) as well — not to look for a setting that makes the CloudWatch metric appear.

  • When the customer already publishes a custom metric with EMF or PutMetricData, you MUST also rule out, by name, the two mechanisms they will expect to pick it up. The dataset integration copies log and trace records out of CloudWatch Logs and has no metric path; OTel enrichment is limited to the metrics AWS services publish about their own resources. A customer with either one switched on otherwise assumes it covers their own metric as well.

  • You MUST NOT invent a metric namespace, a CloudWatch-side setting, or an exporter option for this. OTLP metrics have no CloudWatch namespace; the SDK's standard OTEL_EXPORTER_OTLP_* variables and the resource attributes from Step 3 are the whole configuration.

  • For latency, use a Histogram, so percentiles can be computed at query time; a gauge of the last value cannot be turned back into a distribution.

  • Metric attributes become queryable labels stored in the Dataset. Keep customer identifiers, tokens, request payloads, and other sensitive or personal data out of metric attributes and resource attributes — use coarse dimensions such as route, method, or status — and keep cardinality bounded, since every distinct attribute value is a separate series. CloudWatch encrypts metric data at rest; the Space's own encryption (service-owned or a customer managed KMS key) is chosen at create-space (../spaces-and-domains.md).

Step 4: Review

Summarize the changes grouped by file, state the platform and language, and list the environment variables that will reach the application at runtime. State where telemetry now goes, per the branch of the Scope table you followed: for instrumentation only with no destination, say explicitly that no exporter endpoint was configured and telemetry goes to the SDK default until a receiver is set up; for an existing destination, name it; if a collector was added, state the endpoint the app now exports to and where the collector forwards it. Present the changes for review; do not deploy.

Component versions

Use the latest published ADOT auto-instrumentation version for the language unless the user pins one. Look the current version up from the source of truth rather than hardcoding from this guide:

LanguageReleasesContainer image (ECS / EKS init container)
PythonreleasesECR
Node.jsreleasesECR
JavareleasesECR
.NETreleasesECR

Constraints

  • Only IaC, Dockerfiles, CI/CD workflows, dependency files, and deployment manifests — never application source code.

  • Do not deploy. Stage the changes and let the user review and apply them.

  • Configure an OTLP endpoint, collector, or agent only where the Scope table calls for one — the user asked for a destination, or the request left delivery open and the files show none. Use collector.md, where the CloudWatch Agent is the primary collector.

  • Do not add Application Signals, ServiceEvents, or Dynamic Instrumentation configuration.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Python

Security

What our scanner noticed· Medium risk

  • References to credential files
  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
AWS
Publisher
AWS (Official)
Source
aws/agent-toolkit-for-aws · skills/specialized-skills/operations-skills/setting-up-cloudwatch-observability · commit 0a218f8
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • NOTICE (added by AutoGineer from NOTICE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by AWS.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download setting-up-cloudwatch-observability.zip