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/collector.md

OTel Collector Setup — shared reference

Use this when the user needs a telemetry destination as well as instrumentation: the app is instrumented (or is being instrumented) and has nowhere to export to.

The shape is always the same on every platform:

app (ADOT SDK) --OTLP--> collector --OTLP/HTTP + SigV4--> destination

The CloudWatch Agent is the primary collector (Step 2); upstream otelcol-contrib is the backup. The app-side wiring is identical either way.

Pick the platform guide for deployment and app wiring:

PlatformGuideCollector runs asApp exports to
EC2collector-ec2.mdsystemd service on the hosthttp://localhost:4318 (or :4317 for gRPC)
ECScollector-ecs.mdsidecar container in the same taskhttp://localhost:4318
EKScollector-eks.mdDaemonSet/Deployment + Service in its own namespacethe collector Service's DNS name on :4318

Lambda is not here. A Lambda execution environment has nowhere to run a collector short of a custom extension, and it does not need one: the ADOT layer exports to the environment's own X-Ray receiver under active tracing. See the lambda-*.md guides.

Step 1: The destination — CloudWatch's own OTLP endpoints, per signal

Omni reads CloudWatch's data; it has no store of its own. Telemetry lands in CloudWatch first, in OTLP form — log groups for logs, aws/spans for traces, and the account's CloudWatch Dataset for metrics. A dataset integration copies logs and traces into the Dataset, and the Space reads the Dataset.

The collector exports to CloudWatch's own OTLP endpoints — three endpoints, one per signal. This is the standard path and what the CloudWatch Agent uses.

SignalEndpointSigV4 serviceIAM action
Traceshttps://xray.<region>.amazonaws.com/v1/tracesxrayxray:PutTraceSegments, xray:PutTelemetryRecords
Metricshttps://monitoring.<region>.amazonaws.com/v1/metricsmonitoringcloudwatch:PutMetricData
Logshttps://logs.<region>.amazonaws.com/v1/logslogslogs:PutLogEvents

CloudWatchAgentServerPolicy covers all three and is what AWS's own collector docs recommend.

cloudwatch:PutMetricData in that table is the IAM action the OTLP metrics endpoint authorizes against — it does not mean the collector calls the PutMetricData API. A metric that arrives through the OTLP endpoint stays OpenTelemetry-native, carries its resource attributes ("@resource.service.name"), and is PromQL-queryable in Omni. A metric published with the PutMetricData API itself, or with EMF log lines, is a CloudWatch metric and is not — see instrumentation.md (§ Custom metrics).

Three things that are easy to get wrong:

  • Transaction Search must be enabled in the Region before traces work. This is a real prerequisite for Omni generally, not just for this endpoint — Omni only understands OpenTelemetry, and Transaction Search is what routes incoming spans into the aws/spans CloudWatch Logs group that Omni reads. Without it the traces endpoint rejects spans. It is an account-level, per-Region change, and the user enables it in two steps, in this order: (1) a CloudWatch Logs resource policy letting xray.amazonaws.com call logs:PutLogEvents on aws/spans, then (2) aws xray update-trace-segment-destination --destination CloudWatchLogs --region <region>, which needs that policy to already exist. Metrics arriving while spans do not is the usual symptom of this step being skipped. That resource policy grants a service principal, so it MUST carry the confused-deputy conditions the Transaction Search documentation shows: aws:SourceAccount equal to the account and aws:SourceArn matched with ArnLike against arn:aws:xray:<region>:<account-id>:* — never a bare grant to the principal. Tell the user to enable it; do not enable it for them. Spans carry request attributes, so treat aws/spans (and any custom trace log group) as a log group holding application data: CloudWatch Logs encrypts it at rest with a service-owned key by default, and a customer who must use their own key associates a symmetric KMS key with aws logs associate-kms-key. That key policy grants the CloudWatch Logs service principal (logs.<region>.amazonaws.com) the encrypt, decrypt, and data-key actions, and — like every service-principal grant in this file — it MUST be scoped, not bare: the CloudWatch Logs documentation's statement conditions it with ArnLike on kms:EncryptionContext:aws:logs:arn matching arn:aws:logs:<region>:<account-id>:log-group:* (or the one log group), so the key can only be used for that account's log groups. Recommend it where the customer's data classification calls for it; do not associate a key or edit a key policy for them.

  • Each signal needs its OWN sigv4auth extension, because the service differs (xray vs monitoring vs logs). One shared extension cannot sign for more than one.

  • The logs endpoint needs x-aws-log-group and x-aws-log-stream headers, and both the log group and the log stream named in x-aws-log-stream must already exist. With the group present but the stream absent, the endpoint returns 400 The specified log stream does not exist., so pre-create the stream with aws logs create-log-stream --log-group-name <group> --log-stream-name <stream> (the sending principal needs logs:CreateLogStream).

Step 2: Choose the collector

The CloudWatch Agent is the primary collector. Since 1.300070 it builds an OTLP pipeline straight from its JSON config via a root-level opentelemetry section, so it needs no hand-written collector YAML at all:

{
  "agent": { "region": "<region>" },
  "opentelemetry": {
    "collect": {
      "otlp": {
        "grpc_endpoint": "0.0.0.0:4317",
        "http_endpoint": "0.0.0.0:4318"
      }
    }
  }
}

That is the whole configuration. The agent derives the three destination endpoints from agent.region and builds the sigv4auth extensions, batch processors and pipelines itself. It also adds resource detection, entity correlation (cloud.resource_id), log-group routing, and retry plus sending queues — none of which the hand-written pipeline below has.

This stays inside Omni's scope. The opentelemetry section pulls in no Application Signals, ServiceEvents or Dynamic Instrumentation components. Those live in the agent's separate logs.metrics_collected.application_signals and traces.traces_collected.application_signals sections, which you MUST NOT add. Leave span_metrics_enabled unset — it defaults to false, and turning it on generates span metrics this reference does not cover.

resource_attributes sits alongside collect if extra resource attributes are needed, but prefer OTEL_RESOURCE_ATTRIBUTES on the application so they travel with the app rather than the host.

Verify the version first. An agent older than 1.300070 reports Configuration validation succeeded, status: running and configstatus: configured while starting no OTLP listener and generating no collector YAML. The only check that catches it is looking for listeners on 4317/4318. Note the Amazon Linux 2023 repo package is currently 1.300069.1, so install the current build from S3 rather than the distro repo.

The host package and the container image use different version schemes. Compare the 1.3000NN middle component, not a full string:

FormExample
Host RPM / -ctl -a status1.3000NN.Pb####1.300072.0b1766
Container tag (public.ecr.aws/cloudwatch-agent/cloudwatch-agent)1.3000NN.Pb####, no v prefix1.300072.0b1766

There is no v-prefixed tag and no 1.300070.1 tag in that repository — the nearest published tag is 1.300070.0b1586. Pin a real tag; do not float :latest. To enumerate tags, use the public-ECR token endpoint plus /v2/<repo>/tags/list (aws ecr-public describe-images does not work against public.ecr.aws without a registry id). On EKS, chart 6.6.0 already defaults agent.image.tag to a build past the floor, so pinning is optional there.

One limit decides when you cannot use it:

  1. Batch sizes are fixed — traces and logs 10000/30s, metrics 1000/10s. Not tunable from JSON. The endpoints derive from agent.region; the JSON schema has no endpoint, URL, token or auth field anywhere under opentelemetry — which is fine, since the per-signal CloudWatch endpoints are exactly what the agent targets.

Backup: upstream OTel Collector Contrib

Use otelcol-contrib when batch sizing matters, or when the customer will not run the CloudWatch Agent. otelcol-contrib on a host, otel/opentelemetry-collector-contrib:<version> as a container.

Contrib, not core. sigv4auth is a contrib component; the core build refuses to start with unknown extension "sigv4auth". Pin a version from opentelemetry-collector-releases rather than floating latest.

Step 3: Write the collector config (otelcol-contrib only)

Skip this entire step if you are using the CloudWatch Agent — its JSON above is the config.

CloudWatch's own endpoints, traces + metrics. Each signal needs its own exporter and its own sigv4auth, because the signing service differs:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    send_batch_size: 200
    timeout: 10s

exporters:
  otlphttp/traces:
    traces_endpoint: https://xray.<region>.amazonaws.com/v1/traces
    compression: gzip
    auth:
      authenticator: sigv4auth/traces
  otlphttp/metrics:
    metrics_endpoint: https://monitoring.<region>.amazonaws.com/v1/metrics
    compression: gzip
    auth:
      authenticator: sigv4auth/metrics

extensions:
  sigv4auth/traces:
    region: "<region>"
    service: "xray"
  sigv4auth/metrics:
    region: "<region>"
    service: "monitoring"

service:
  extensions: [sigv4auth/traces, sigv4auth/metrics]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/traces]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/metrics]
Why the config looks like this
  • otlphttp, never otlp, on the exporter. The CloudWatch OTLP endpoints are HTTP/1.1 only — gRPC is not supported. The otlp exporter speaks gRPC.

  • Both receiver protocols are enabled — gRPC on 4317 and HTTP on 4318. That is the OTel default pair, and it is what the CloudWatch Agent listens on too. It matters here because SDK defaults differ: the Java agent defaults to gRPC on 4317 while the Python, Node.js and .NET distros default to HTTP on 4318. Listening on both means an app works whichever default it has, and OTEL_EXPORTER_OTLP_PROTOCOL becomes a correctness detail rather than a hard requirement. Dropping one protocol is a deliberate narrowing, not a simplification. Note this is the inbound side only — the exporter to CloudWatch is HTTP-only regardless.

  • extensions sits at service.extensions. AWS's "OpenTelemetry Collector" page shows it nested under service.telemetry.extensions, which is not a real config key — the collector fails to start on it. AWS's CloudWatch-agent page has it in the right place. Use service.extensions.

  • send_batch_size: 200 keeps requests under the metrics endpoint's limits: 1 MB and 1,000 data points per request, 500 TPS per account. Traces allow 5 MB and 10,000 spans, so 200 is comfortable for both.

  • compression: gzip — the endpoints accept gzip or none and nothing else.

  • Spans older than 14 days or more than 2 hours in the future are rejected. Only matters if the user replays telemetry.

Step 4: Wire the app to the collector

Set on the application, per the platform guide's mechanism (systemd Environment=, container environment, pod env):

OTEL_EXPORTER_OTLP_ENDPOINT=<collector URL from the table above>
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

Set OTEL_EXPORTER_OTLP_PROTOCOL explicitly. SDK defaults differ — the Java agent defaults to gRPC, so pointing it at 4318 without this produces a port that is listening and telemetry that silently never arrives. This is the single most common failure in this setup.

Nothing else about the instrumentation changes. Do not revisit the loader variables.

IAM: the collector needs permissions, the app still does not

The instrumentation guides say instrumentation adds no IAM, and that stays true — the SDK only talks to the collector. The collector is what calls AWS, so it needs credentials.

What to grant: CloudWatchAgentServerPolicy, which covers all three CloudWatch OTLP signals (xray, monitoring, logs).

Where that grant attaches, per platform:

PlatformIdentityHow
EC2instance profile roleattach CloudWatchAgentServerPolicy
ECStask role (not execution role)attach CloudWatchAgentServerPolicy
EKSservice accountIRSA or EKS Pod Identity — see collector-eks.md

On ECS the execution role additionally needs ssm:GetParameters if the config comes from Parameter Store — a different role for a different reason. Getting these two backwards is the usual cause of an ECS collector that starts and then 403s.

Do NOT

  • Do not use CloudWatch Agent port 4316, and do not install the amazon-cloudwatch-observability EKS add-on. Both wire up Application Signals. This is not a ban on the CloudWatch Agent itself — Step 2 makes it the primary collector, and its opentelemetry section pulls in no Application Signals components. What is banned is the Application Signals configuration, not the agent.

  • Do not enable Application Signals, ServiceEvents, or Dynamic Instrumentation in the collector config.

  • Do not modify application source. This is all IaC and collector config.

  • Do not deploy. Present the diff and let the user apply it.

Verify (optional, and only after the user deploys)

None of this is part of the change you present. Every check here inspects a running collector, so it belongs to the conversation after the user applies — and on EC2 the change takes effect only when the instance is replaced, since UserData runs at first boot only.

Do not run these unprompted, and do not treat them as a gate on completing the task. Hand the relevant few to the user, or run them if they ask you to verify after applying.

The checks differ by collector AND by platform. Pick the row that matches what you deployed. Several checks that work on an EC2 host are impossible in a container, and vice versa.

CloudWatch Agent (primary)
GoalEC2 (host install)ECS FargateEKS
Collector runningamazon-cloudwatch-agent-ctl -a status → running / configuredaws ecs describe-tasks → the cloudwatch-agent container RUNNING, init STOPPED exit 0kubectl -n amazon-cloudwatch get pods
Receiver actually listeningss -lntp | grep -E '4317|4318' — the check that catches a too-old agent, which otherwise reports running and configured while listening on nothingthe collector log stream's Starting HTTP server / Starting GRPC server lineskubectl -n amazon-cloudwatch logs -l app.kubernetes.io/name=cloudwatch-agent | grep -E 'Starting (HTTP|GRPC) server' — use the log, not get endpoints: Endpoints come from ready pods and the Service spec, so they are populated even when a too-old agent is listening on nothing. get endpoints cloudwatch-agent being non-empty is still worth checking for the selector mismatch case
Per-signal export failuresgrep -E 'Exporting failed|dropped_items' /opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.loggrep the same strings in the collector's awslogs streamkubectl -n amazon-cloudwatch logs -l app.kubernetes.io/name=cloudwatch-agent | grep -E 'Exporting failed|dropped_items'

Three things about the agent's own diagnostics:

  • The log is a file only on a host install. There, journalctl -u amazon-cloudwatch-agent is near-empty and will hide the errors, so read the file. In a container the file is never populated — the agent runs with logtarget = "lumberjack" and writes to stdout, so the awslogs stream (ECS) or kubectl logs (EKS) is the log.

  • You cannot exec into the agent container. The image is distroless: no ss, no sh, no printenv. Read the endpoints from the API server instead of poking inside the pod.

  • There is no 8888 endpoint. The agent generates service.telemetry.metrics.level: None and logs Skipped telemetry setup.. On EKS the operator still creates a cloudwatch-agent-monitoring Service on 8888 with endpoints, so the check looks available and then refuses the connection.

Benign noise you will see on a healthy collector — do not grep for E! or Error, grep for Exporting failed / dropped_items:

  • On Fargate: two E! [EC2] Fetch hostname / identity document from EC2 metadata fail lines against 169.254.169.254, followed by I! Detected the instance is ECS. There is no IMDS in a task.

  • Cannot access /etc/cwagentconfig: no such file or directory when the config comes from CW_CONFIG_CONTENT.

  • E! ... "msg":"using deprecated field \blocking`"` with a ~2.5 KB Go stacktrace, once per exporter — an upstream collector deprecation, not your problem.

otelcol-contrib (backup)
GoalCheck
Collector runningsystemctl status otelcol-contrib (host)
Receiver listeningcurl -sS -o /dev/null -w '%{http_code}' http://localhost:4318/v1/traces → 405
Per-signal export failurescurl -s http://localhost:8888/metrics | grep -E 'receiver_accepted|exporter_sent|exporter_send_failed'

otelcol_receiver_accepted_* rising with otelcol_exporter_sent_* at zero means the destination is rejecting; both at zero means the app never sent anything — check OTEL_EXPORTER_OTLP_PROTOCOL.

On ECS Fargate neither curl check is reachable — there is no host shell and no route into the task's network namespace without enableExecuteCommand plus ssmmessages:* on the task role. Use the collector's log stream instead: it dumps its generated pipelines at startup and logs Everything is ready. Begin running and processing data.. See collector-ecs.md.

Optional: confirm telemetry arrived at the destination

Everything above establishes that the collector is healthy, which is this guide's job. Confirming the data landed is a query task and belongs to the aws-observability skill — route there rather than reproducing query recipes here. Three facts a reader needs in order to look in the right place:

  • Traces land in the CloudWatch Logs group aws/spans (via Transaction Search), not in the CloudWatch X-Ray traces view. In the console that is CloudWatch → Transaction Search.

  • In the Space, logs and spans are queryable as logs.default and traces.default only once a dataset integration copies their log groups (including aws/spans) into the Dataset; see data-forwarding-and-centralization.md. Check that one exists before reading an empty traces.default as a collector failure.

  • Metrics land in CloudWatch's Metrics V2 store and are not visible to list-metrics or get-metric-statistics, which only see V1. They are queried with PromQL over https://monitoring.<region>.amazonaws.com.

Allow 1–2 minutes for either to appear. If aws/spans stays empty while the collector logs no export errors, the usual cause is Transaction Search not being enabled on the account.

One trap worth stating, because it invites an account-wide change for no reason: aws xray get-indexing-rules reports a probabilistic DesiredSamplingPercentage that defaults to 1%. It governs X-Ray-side indexing for trace-summary search — it does not reduce what reaches aws/spans, which is unsampled. Do not raise it to debug missing spans.

Also do not treat traces.span.metrics.calls / .duration as evidence the metrics pipeline works. CloudWatch derives those from ingested spans, so they appear even when metric export is failing entirely. They carry @instrumentation.@name="cloudwatch.aws/xray/span-metrics"; the SDK's own metrics carry @instrumentation.cloudwatch.source="cloudwatch-agent".

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