I tried setting up correlation between traces and logs in CloudWatch Application Signals

I tried setting up correlation between traces and logs in CloudWatch Application Signals

Log correlation can be easily set up
2026.02.12

This page has been translated by machine translation. View original

I want to easily check logs related to traces

Hello, this is nonPi (@non____97).

Have you ever wanted to easily check logs related to traces when using CloudWatch Application Signals? I have.

In Application Signals, when you click on a metrics graph, up to 10 spans are displayed from that point in time.

8.相関するスパン.png

By clicking on the trace ID of these spans, you can check the trace details.

12.障害のトレース.png

Being able to see logs related to traces allows you to check detailed error messages and stack traces immediately without having to search for logs separately, reducing investigation time.

The implementation method is described in the following AWS official documentation.

You can enable trace for log correlation in Application Signals. This automatically inserts trace IDs and span IDs into related application logs. Then, when you open a trace detail page in the Application Signals console, if there are log entries related to the current trace, they're automatically displayed at the bottom of the page.

For example, you might notice a spike in your latency graph. You can select that point on the graph to load diagnostic information for that time point. Then, you can select a related trace to see details. When you view the trace information, you can scroll down to see logs that are associated with that trace. Examining these logs might help you pinpoint an issue that's causing the latency spike, revealing patterns or error codes.

Enable trace for log correlation - Amazon CloudWatch

I tried it out.

Quick summary

  • To enable log correlation, implement the following steps:
    • Embed trace information in logs
      • Be careful whether the package and version you're using is supported by the zero-code instrumentation library
    • Specify the log output destination
      • Specify using aws.log.group.names in OTEL_RESOURCE_ATTRIBUTES
  • CloudWatch Logs Insights works behind the scenes for log correlation
    • It automatically queries log groups starting with /ecs/

Testing it out

Test environment

Here's my test environment.

AWS Distro for OpenTelemetry (ADOT) Collector と ADOT SDKをとでCloudWatch Application Signalsを使ってみた.png

For logs, I'm using AWS FireLens (AWS for Fluent Bit), with error logs going to CloudWatch Logs and all logs to an S3 bucket via Data Firehose.

テレメトリ.png

This is based on the following article.

https://dev.classmethod.jp/articles/cloudwatch-application-signals-with-adot-collector-and-sdk/

All resources are deployed using AWS CDK. The code used is as follows.

https://github.com/non-97/ecs-native-blue-green/tree/v2.0.2

Once deployed, the Open Telemetry and Application Signals related environment variables are as follows.

Key Value
OTEL_AWS_APPLICATION_SIGNALS_ENABLED false
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4318
OTEL_EXPORTER_OTLP_PROTOCOL http/protobuf
OTEL_LOGS_EXPORTER none
OTEL_METRICS_EXPORTER none
OTEL_PROPAGATORS tracecontext,baggage,xray
OTEL_RESOURCE_ATTRIBUTES service.name=ecs-express-app,deployment.environment=ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-4JfsTzadcWYq,aws.log.group.names=EcsNativeBlueGreenStack-FirelensConstructFirelensLogGroupD186C82F-Oieo4YPQifeI
OTEL_TRACES_EXPORTER otlp
OTEL_TRACES_SAMPLER xray
OTEL_TRACES_SAMPLER_ARG endpoint=http://localhost:2000,polling_interval=300

Embedding trace information in logs

First, we need to embed trace information in logs.

The AWS official documentation recommends using zero-code instrumentation libraries for each logger for Node.js.

Node.js

For details on how to enable trace context injection in Node.js to work with Node.js-compatible logging libraries, see documentation about using NPM for auto-instrumentation of either Pino, Winston, or Bunyan in Node.js.

Enable trace for log correlation - Amazon CloudWatch

Since I'm using Pino, I need to use @opentelemetry/instrumentation-pino.

In this case, since I'm using public.ecr.aws/aws-observability/adot-autoinstrumentation-node:v0.8.1 in the init container, I don't need to install @opentelemetry/instrumentation-pino separately.

Checking the releases of aws-observability/aws-otel-js-instrumentation, we can see that this container image includes aws-otel-js-instrumentation:

See ADOT node auto-instrumentation Docker image v0.8.1 in our public ECR repository:
https://gallery.ecr.aws/aws-observability/adot-autoinstrumentation-node

Releases · aws-observability/aws-otel-js-instrumentation

And by checking the package-lock.json of aws-otel-js-instrumentation, we can see that it includes the Pino zero-code instrumentation library version ^0.46.0.

https://github.com/aws-observability/aws-otel-js-instrumentation/blob/v0.8.1/package-lock.json#L4269

The Pino version is important to note.

For zero-code instrumentation library version 0.46.0, supported Pino versions are >=5.14.0 <10.

pino versions >=5.14.0 <10
The "log sending" feature is only supported in pino v7 and later.

@opentelemetry/instrumentation-pino - npm

The latest version of Pino is 10.3.1. Therefore, if you don't specify a version, you'll end up using a Pino version that the zero-code instrumentation library doesn't support.

When Pino's zero-code instrumentation is working properly, trace_id and span_id are added as follows:

When Pino's zero-code instrumentation is working properly
{
    "level": "error",
    "time": "2026-02-11T00:42:49.466Z",
    "pid": 6,
    "hostname": "ip-10-10-8-112.ec2.internal",
    "req": {
        "id": 36,
        "method": "GET",
        "url": "/error/",
        "query": {},
        "params": {},
        "headers": {
            "host": "ecsnat-albco-qzyosxmqsim4-1634003559.us-east-1.elb.amazonaws.com",
            "x-real-ip": "10.10.8.10",
            "x-forwarded-for": "<source IP address>, 10.10.8.10",
            "x-forwarded-proto": "http",
            "connection": "close",
            "x-forwarded-port": "80",
            "x-amzn-trace-id": "Root=1-698bd089-3e85c9ad79fddc8706f7ad7f",
            "cookie": "connect.sid=s%3Aa-rXSzyN-2POI_b5dkH4LkP6ijl68rve.zWuJDO786nBLrcXV3U70DxhwxfrFLqXFjPWwB9ATd%2FQ",
            "user-agent": "curl/8.7.1",
            "accept": "*/*"
        },
        "remoteAddress": "127.0.0.1",
        "remotePort": 57812
    },
    "trace_id": "698bd089673affe7e1c8a2add24be6a9",
    "span_id": "448e1d51d070a746",
    "trace_flags": "01",
    "res": {
        "statusCode": 500,
        "headers": {
            "x-powered-by": "Express",
            "content-type": "application/json; charset=utf-8",
            "content-length": "33",
            "etag": "W/\"21-Fau8GdrOCOyGNNH/IiTxy2DuMu0\""
        }
    },
    "err": {
        "type": "Error",
        "message": "failed with status code 500",
        "stack": "Error: failed with status code 500\n    at onResFinished (/app/node_modules/.pnpm/pino-http@10.5.0/node_modules/pino-http/logger.js:115:39)\n    at ServerResponse.onResponseComplete (/app/node_modules/.pnpm/pino-http@10.5.0/node_modules/pino-http/logger.js:178:14)\n    at /otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js:50:55\n    at AsyncLocalStorage.run (node:internal/async_local_storage/async_context_frame:59:14)\n    at AsyncLocalStorageContextManager.with (/otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AsyncLocalStorageContextManager.js:33:40)\n    at ServerResponse.contextWrapper (/otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js:50:32)\n    at ServerResponse.emit (node:events:520:35)\n    at onFinish (node:_http_outgoing:1026:10)\n    at callback (node:internal/streams/writable:764:21)\n    at afterWrite (node:internal/streams/writable:708:5)"
    },
    "responseTime": 5,
    "msg": "request errored",
    "container_name": "app",
    "source": "stderr",
    "container_id": "6b8cc0c5e5f94ebe888e690dbb051168-0527074092",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/6b8cc0c5e5f94ebe888e690dbb051168",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:95"
}

On the other hand, if Pino version is 10 or higher, trace information is not recorded:

If Pino version is 10 or higher
{
    "level": "error",
    "time": "2026-02-04T07:54:00.739Z",
    "pid": 6,
    "hostname": "ip-10-10-8-103.ec2.internal",
    "req": {
        "id": 63,
        "method": "GET",
        "url": "/error/",
        "query": {},
        "params": {},
        "headers": {
            "host": "ecsnat-albco-fgdpbvnooxwj-1973419334.us-east-1.elb.amazonaws.com",
            "x-real-ip": "10.10.8.57",
            "x-forwarded-for": "<source IP address>, 10.10.8.57",
            "x-forwarded-proto": "http",
            "connection": "close",
            "x-forwarded-port": "80",
            "x-amzn-trace-id": "Root=1-6982fb18-3fb552d8541fd9151d48406f",
            "accept": "*/*",
            "user-agent": "curl/8.7.1"
        },
        "remoteAddress": "127.0.0.1",
        "remotePort": 53378
    },
    "res": {
        "statusCode": 500,
        "headers": {
            "x-powered-by": "Express",
            "content-type": "application/json; charset=utf-8",
            "content-length": "33",
            "etag": "W/\"21-Fau8GdrOCOyGNNH/IiTxy2DuMu0\"",
            "set-cookie": [
                "connect.sid=s%3An3YhhvIDKp6XWpVpDbx0U9F0GeNEHJxg.1HNNiMD4%2BhQEmjNdBO9NbQsADN67pKUccIeBYe%2Fp%2FVc; Path=/; Expires=Thu, 05 Feb 2026 07:54:00 GMT; HttpOnly"
            ]
        }
    },
    "err": {
        "type": "Error",
        "message": "failed with status code 500",
        "stack": "Error: failed with status code 500\n    at onResFinished (/app/node_modules/.pnpm/pino-http@11.0.0/node_modules/pino-http/logger.js:115:39)\n    at ServerResponse.onResponseComplete (/app/node_modules/.pnpm/pino-http@11.0.0/node_modules/pino-http/logger.js:178:14)\n    at /otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js:50:55\n    at AsyncLocalStorage.run (node:internal/async_local_storage/async_context_frame:59:14)\n    at AsyncLocalStorageContextManager.with (/otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AsyncLocalStorageContextManager.js:33:40)\n    at ServerResponse.contextWrapper (/otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js:50:32)\n    at ServerResponse.emit (node:events:520:35)\n    at onFinish (node:_http_outgoing:1026:10)\n    at callback (node:internal/streams/writable:764:21)\n    at afterWrite (node:internal/streams/writable:708:5)"
    },
    "responseTime": 3,
    "msg": "request errored",
    "container_name": "app",
    "source": "stderr",
    "container_id": "0dfd495fff0048de92bd87f68daa8de1-0527074092",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-s5tytpIiJ9VQ",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-s5tytpIiJ9VQ/0dfd495fff0048de92bd87f68daa8de1",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:93"
}

Be careful with Express and other packages as well, making sure they're at versions supported by zero-code instrumentation. The documentation and source code contain supported versions, like this:

https://github.com/open-telemetry/opentelemetry-js-contrib/blob/12adb4354f09ade438cd96340bdfd1f715b5fed3/plugins/node/opentelemetry-instrumentation-express/src/instrumentation.ts#L53-L62

Specifying the log output destination

To correlate traces and logs, you need to specify the log output destination.

Specifically, add aws.log.group.names=<target log group name> to OTEL_RESOURCE_ATTRIBUTES.

(Optional) To enable Application Signals log correlation, additionally set the environment variable aws.log.group.names to the log group name of your application logs. This enables correlation of traces and metrics generated by your application with corresponding log entries in this log group. In this variable, replace $YOUR_APPLICATION_LOG_GROUP with the name of your application log group. If you have multiple log groups, you can use an ampersand (&) to separate them as in this example: aws.log.group.names=log-group-1&log-group-2. Setting this current environment variable is sufficient to enable log correlation for metrics. For more information, see Enable log correlation for metrics. To enable log correlation for traces, you also need to change your application's logging configuration. For more information, see Enable trace for log correlation.

Deploy using the sidecar strategy - Amazon CloudWatch

This will include the specified log group name in the cloudwatch_logs property of the span's aws property.

{
    "ecs.task.arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/015048a8181a46e4bbc15539e33568f7",
    "ecs.task.family": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2",
    "ecs.cluster.arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:cluster/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g",
    "ecs": {
        "container": "ip-10-10-8-74.ec2.internal",
        "container_id": "46e4bbc15539e33568f7/015048a8181a46e4bbc15539e33568f7-0527074092"
    },
    "ecs.container.arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:container/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/015048a8181a46e4bbc15539e33568f7/8059f724-8aeb-4554-afd9-bddf9f15b80e",
    "ecs.launchtype": "fargate",
    "span.kind": "CLIENT",
    "cloudwatch_logs": [
        {
            "log_group": "EcsNativeBlueGreenStack-FirelensConstructFirelensLogGroupD186C82F-wTQ4890Mp8e9"
        }
    ],
    "ecs.task.revision": "100"
},

Note that log correlation won't work if you include resourcedetection with ECS Detectors in the trace pipeline of the Open Telemetry Collector as shown below:

processors:
.
.
(omitted)
.
.
  resourcedetection:
    detectors: [env, ecs]
    timeout: 5s
    override: false
.
.
(omitted)
.
.
service:
  extensions: [sigv4auth, awsproxy]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [filter/exclude_health, resourcedetection, batch]
      exporters: [otlphttp]

Specifically, you'll see an error message like "An error occurred while retrieving data":

1.データの取得中にエラーが発生しました.png

Note: The same result appears whether from "CloudWatch > Transaction Search" or "CloudWatch > Traces"

As mentioned later, CloudWatch Logs Insights runs behind the scenes for log correlation, but in this case, there was no evidence that Logs Insights was executed.

By the way, in this case, the aws property of the span contained information about the log group and log stream of the container with the awslogs log driver in cloudwatch_logs, log.stream.arns, and log.stream.names:

{
  "ecs.task.arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/2a9e9b028b6b4bd0b4edf85023bd9c3b",
  "ecs.task.family": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2",
  "ecs.cluster.arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:cluster/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g",
  "ecs": {
      "container": "ip-10-10-8-125.ec2.internal",
      "container_id": "4bd0b4edf85023bd9c3b/2a9e9b028b6b4bd0b4edf85023bd9c3b-0527074092"
  },
  "ecs.container.arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:container/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/2a9e9b028b6b4bd0b4edf85023bd9c3b/e8984edf-97c0-4e46-a8ea-23018b9537c6",
  "ecs.launchtype": "fargate",
  "span.kind": "CLIENT",
  "cloudwatch_logs": [
      {
          "log_group": "EcsNativeBlueGreenStack-FirelensConstructFirelensLogGroupD186C82F-wTQ4890Mp8e9"
      },
      {
          "log_group": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:EcsNativeBlueGreenStack-EcsConstructTaskDefinitionAdotAutoInstrumentationInitContainerLogGroup8060A51D-TTDVP1HlaLC8"
      },
      {
          "log_group": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:EcsNativeBlueGreenStack-EcsConstructTaskDefinitionlogRouterLogGroup27EC9B3C-OxmB1VpOf8SS"
      }
  ],
  "ecs.task.revision": "100",
  "log.stream.arns": [
      "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:EcsNativeBlueGreenStack-EcsConstructTaskDefinitionAdotAutoInstrumentationInitContainerLogGroup8060A51D-TTDVP1HlaLC8:log-stream:init/init/2a9e9b028b6b4bd0b4edf85023bd9c3b",
      "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:EcsNativeBlueGreenStack-EcsConstructTaskDefinitionlogRouterLogGroup27EC9B3C-OxmB1VpOf8SS:log-stream:firelens/EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2/logRouter/2a9e9b028b6b4bd0b4edf85023bd9c3b"
  ],
  "log.stream.names": [
      "init/init/2a9e9b028b6b4bd0b4edf85023bd9c3b",
      "firelens/EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2/logRouter/2a9e9b028b6b4bd0b4edf85023bd9c3b"
  ],
  "ecs.task.id": "2a9e9b028b6b4bd0b4edf85023bd9c3b"
},

Since the only other addition is ecs.task.id, avoid inserting this in your pipeline.

Verification of Operation

I will verify the operation.

From the Application Signals service, I click on a data point in the metrics graph to display the correlation span.

4.Correlation Span.png

I click on the trace ID within the correlation span.

As a result, I can also see logs associated with the trace as follows:

10.Verification of log correlation.png

{
    "level": "error",
    "time": "2026-02-11T05:39:13.085Z",
    "pid": 8,
    "hostname": "ip-10-10-8-89.ec2.internal",
    "req": {
        "id": 479,
        "method": "GET",
        "url": "/crash/",
        "query": {},
        "params": {},
        "headers": {
            "host": "ecsnat-albco-qzyosxmqsim4-1634003559.us-east-1.elb.amazonaws.com",
            "x-real-ip": "10.10.8.40",
            "x-forwarded-for": "<source IP address>, 10.10.8.40",
            "x-forwarded-proto": "http",
            "connection": "close",
            "x-forwarded-port": "80",
            "x-amzn-trace-id": "Root=1-698c1601-5ec60d115f6377e539ba7666",
            "cookie": "connect.sid=s%3AkdwCTdPnOMbapxgvwkYpR3T2Y1Ykw_eW.cef%2Bw5X7jVYHTqDmz0m8XtFUePmZOE225Vy9JSE%2B4uQ",
            "user-agent": "curl/8.7.1",
            "accept": "*/*"
        },
        "remoteAddress": "127.0.0.1",
        "remotePort": 33256
    },
    "trace_id": "698c160176b2820f3142596543b58e08",
    "span_id": "84537a0cf5432a39",
    "trace_flags": "01",
    "msg": "Crash endpoint triggered - throwing exception",
    "container_id": "26510d3c39984a7cb58a1bf43dda452b-0527074092",
    "container_name": "app",
    "source": "stderr",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/26510d3c39984a7cb58a1bf43dda452b",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:98"
}
{
    "level": "error",
    "time": "2026-02-11T05:39:13.086Z",
    "pid": 8,
    "hostname": "ip-10-10-8-89.ec2.internal",
    "req": {
        "id": 479,
        "method": "GET",
        "url": "/crash/",
        "query": {},
        "params": {},
        "headers": {
            "host": "ecsnat-albco-qzyosxmqsim4-1634003559.us-east-1.elb.amazonaws.com",
            "x-real-ip": "10.10.8.40",
            "x-forwarded-for": "<source IP address>, 10.10.8.40",
            "x-forwarded-proto": "http",
            "connection": "close",
            "x-forwarded-port": "80",
            "x-amzn-trace-id": "Root=1-698c1601-5ec60d115f6377e539ba7666",
            "cookie": "connect.sid=s%3AkdwCTdPnOMbapxgvwkYpR3T2Y1Ykw_eW.cef%2Bw5X7jVYHTqDmz0m8XtFUePmZOE225Vy9JSE%2B4uQ",
            "user-agent": "curl/8.7.1",
            "accept": "*/*"
        },
        "remoteAddress": "127.0.0.1",
        "remotePort": 33256
    },
    "trace_id": "698c160176b2820f3142596543b58e08",
    "span_id": "84537a0cf5432a39",
    "trace_flags": "01",
    "err": {
        "type": "Error",
        "message": "Application crash test - intentional exception",
        "stack": "Error: Application crash test - intentional exception\n    at /app/dist/router.js:218:11\n    at Layer.handle [as handle_request] (/app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/layer.js:95:5)\n    at next (/app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/route.js:149:13)\n    at Route.dispatch (/app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/route.js:119:3)\n    at patched (/otel-auto-instrumentation/node_modules/@opentelemetry/instrumentation-express/build/src/instrumentation.js:210:37)\n    at Layer.handle [as handle_request] (/app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/layer.js:95:5)\n    at /app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/index.js:284:15\n    at router.process_params (/app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/index.js:346:12)\n    at next (/app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/index.js:280:10)\n    at router.handle (/app/node_modules/.pnpm/express@4.22.1/node_modules/express/lib/router/index.js:175:3)"
    },
    "msg": "Error occurred",
    "container_id": "26510d3c39984a7cb58a1bf43dda452b-0527074092",
    "container_name": "app",
    "source": "stderr",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/26510d3c39984a7cb58a1bf43dda452b",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:98"
}
{
    "level": "error",
    "time": "2026-02-11T05:39:13.088Z",
    "pid": 8,
    "hostname": "ip-10-10-8-89.ec2.internal",
    "req": {
        "id": 479,
        "method": "GET",
        "url": "/crash/",
        "query": {},
        "params": {},
        "headers": {
            "host": "ecsnat-albco-qzyosxmqsim4-1634003559.us-east-1.elb.amazonaws.com",
            "x-real-ip": "10.10.8.40",
            "x-forwarded-for": "<source IP address>, 10.10.8.40",
            "x-forwarded-proto": "http",
            "connection": "close",
            "x-forwarded-port": "80",
            "x-amzn-trace-id": "Root=1-698c1601-5ec60d115f6377e539ba7666",
            "cookie": "connect.sid=s%3AkdwCTdPnOMbapxgvwkYpR3T2Y1Ykw_eW.cef%2Bw5X7jVYHTqDmz0m8XtFUePmZOE225Vy9JSE%2B4uQ",
            "user-agent": "curl/8.7.1",
            "accept": "*/*"
        },
        "remoteAddress": "127.0.0.1",
        "remotePort": 33256
    },
    "trace_id": "698c160176b2820f3142596543b58e08",
    "span_id": "84537a0cf5432a39",
    "trace_flags": "01",
    "res": {
        "statusCode": 500,
        "headers": {
            "x-powered-by": "Express",
            "content-type": "application/json; charset=utf-8",
            "content-length": "33",
            "etag": "W/\"21-Fau8GdrOCOyGNNH/IiTxy2DuMu0\""
        }
    },
    "err": {
        "type": "Error",
        "message": "failed with status code 500",
        "stack": "Error: failed with status code 500\n    at onResFinished (/app/node_modules/.pnpm/pino-http@10.5.0/node_modules/pino-http/logger.js:115:39)\n    at ServerResponse.onResponseComplete (/app/node_modules/.pnpm/pino-http@10.5.0/node_modules/pino-http/logger.js:178:14)\n    at /otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js:50:55\n    at AsyncLocalStorage.run (node:internal/async_local_storage/async_context_frame:59:14)\n    at AsyncLocalStorageContextManager.with (/otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AsyncLocalStorageContextManager.js:33:40)\n    at ServerResponse.contextWrapper (/otel-auto-instrumentation/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js:50:32)\n    at ServerResponse.emit (node:events:520:35)\n    at onFinish (node:_http_outgoing:1026:10)\n    at callback (node:internal/streams/writable:764:21)\n    at afterWrite (node:internal/streams/writable:708:5)"
    },
    "responseTime": 5,
    "msg": "request errored",
    "container_id": "26510d3c39984a7cb58a1bf43dda452b-0527074092",
    "container_name": "app",
    "source": "stderr",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-zJnVnD9x7R6g/26510d3c39984a7cb58a1bf43dda452b",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:98"
}

I can also see when dependency availability is low.

12.Redis dependency.png
13.Redis correlation span.png
11.Trace when Redis connection fails.png

The logs at this time are as follows:

{
    "level": "error",
    "time": "2026-02-12T09:08:27.268Z",
    "pid": 7,
    "hostname": "ip-10-10-8-82.ec2.internal",
    "req": {
        "id": 15,
        "method": "GET",
        "url": "/health",
        "query": {},
        "params": {},
        "headers": {
            "host": "localhost:3000",
            "user-agent": "Wget",
            "accept": "*/*",
            "connection": "close"
        },
        "remoteAddress": "127.0.0.1",
        "remotePort": 57732
    },
    "trace_id": "698d988b0f2d820050ca5552c933f32d",
    "span_id": "f9ff425ef6931901",
    "trace_flags": "01",
    "err": {
        "type": "ReplyError",
        "message": "ERR syntax error",
        "stack": "ReplyError: ERR syntax error\n    at parseError (/app/node_modules/.pnpm/redis-parser@3.0.0/node_modules/redis-parser/lib/parser.js:179:12)\n    at parseType (/app/node_modules/.pnpm/redis-parser@3.0.0/node_modules/redis-parser/lib/parser.js:302:14)",
        "command": {
            "name": "set",
            "args": [
                "sess:st2zrsEzLyFlzb5i4XocReBSUhmyjgRz",
                "{\"cookie\":{\"originalMaxAge\":86400000,\"expires\":\"2026-02-13T09:08:27.265Z\",\"secure\":false,\"httpOnly\":true,\"path\":\"/\"}}",
                "[object Object]"
            ]
        }
    },
    "msg": "Error occurred",
    "container_id": "3d5c2659c6ad4d0587a43191759bd918-0527074092",
    "container_name": "app",
    "source": "stderr",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-4JfsTzadcWYq",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWS Account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-4JfsTzadcWYq/3d5c2659c6ad4d0587a43191759bd918",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:103"
}

When I check the Logs Insights execution history at the time of clicking on the trace ID to view trace details, the following queries were executed:

5.After reload.png

aws/spans
fields @message, @log, traceId, spanId | filter traceId = "698c160176b2820f3142596543b58e08" | sort @timestamp desc | limit 10000
aws/spans
fields @message, @log, traceId, spanId, jsonparse(@message) as json_message | unnest json_message.links into link | filter link.traceId = "698c160176b2820f3142596543b58e08" | sort @timestamp desc | limit 10000
aws/spans
fields @message, @log, traceId, spanId | filter traceId = "698c160176b2820f3142596543b58e08" | sort @timestamp desc | limit 10000
aws/spans
fields @message, @log, traceId, spanId, jsonparse(@message) as json_message | unnest json_message.links into link | filter link.traceId = "698c160176b2820f3142596543b58e08" | sort @timestamp desc | limit 10000
/ecs/,/ecs/default-nginx-5d29,/ecs/ecs-aws-firelens-sidecar-container,/ecs/ecs-aws-otel-sidecar-collector,/ecs/nginx,EcsNativeBlueGreenStack-FirelensConstructFirelensLogGroupD186C82F-wTQ4890Mp8e9
fields @log, @timestamp, @message
| filter @message like "698c160176b2820f3142596543b58e08" or @message like "8c1601762820f3142596543b58e08"
| sort @timestamp, @message desc
/ecs/,/ecs/default-nginx-5d29,/ecs/ecs-aws-firelens-sidecar-container,/ecs/ecs-aws-otel-sidecar-collector,/ecs/nginx,EcsNativeBlueGreenStack-FirelensConstructFirelensLogGroupD186C82F-wTQ4890Mp8e9
fields @log, @timestamp, @message
| filter @message like "698c160176b2820f3142596543b58e08" or @message like "8c1601762820f3142596543b58e08"
| sort @timestamp, @message desc

Essentially, three types of queries are being executed twice each.

Judging from the log group names in the queries, the log correlation is likely happening in the last two queries mentioned above.

The CloudTrail event at this time is as follows:

{
    "eventVersion": "1.11",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "<Principal ID>",
        "arn": "arn:aws:sts::<AWS Account ID>:assumed-role/<IAM Role Name>/<Session Name>",
        "accountId": "<AWS Account ID>",
        "accessKeyId": "<Access Key>",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "<Principal ID>",
                "arn": "arn:aws:iam::<AWS Account ID>:role/<IAM Role Name>",
                "accountId": "<AWS Account ID>",
                "userName": "<IAM User Name>"
            },
            "attributes": {
                "creationDate": "2026-02-11T05:38:53Z",
                "mfaAuthenticated": "true"
            }
        }
    },
    "eventTime": "2026-02-11T05:45:59Z",
    "eventSource": "logs.amazonaws.com",
    "eventName": "StartQuery",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "<Source IP Address>",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
    "requestParameters": {
        "logGroupNames": [
            "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/",
            "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/default-nginx-5d29",
            "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/ecs-aws-firelens-sidecar-container",
            "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/ecs-aws-otel-sidecar-collector",
            "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/nginx",
            "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:EcsNativeBlueGreenStack-FirelensConstructFirelensLogGroupD186C82F-wTQ4890Mp8e9"
        ],
        "startTime": 1770788338,
        "endTime": 1770788383,
        "queryString": "fields @log, @timestamp, @message\n| filter @message like \"698c160176b2820f3142596543b58e08\" or @message like \"8c1601762820f3142596543b58e08\"\n| sort @timestamp, @message desc",
        "dryRun": false
    },
    "responseElements": null,
    "additionalEventData": {
        "queryId": "5f79c759-30fa-45e5-adff-e68542d98ab0"
    },
    "requestID": "3f4c9697-9f6f-4213-891d-3f72959a2916",
    "eventID": "cc9165a8-ecda-4992-9498-dbe332ebce96",
    "readOnly": true,
    "resources": [
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/default-nginx-5d29"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/ecs-aws-firelens-sidecar-container"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/ecs-aws-otel-sidecar-collector"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/nginx"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:EcsNativeBlueGreenStack-FirelensConstructFirelensLogGroupD186C82F-wTQ4890Mp8e9"
        }
    ],
    "eventType": "AwsApiCall",
    "apiVersion": "20140328",
    "managementEvent": true,
    "recipientAccountId": "<AWS Account ID>",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "logs.us-east-1.amazonaws.com"
    },
    "sessionCredentialFromConsole": "true"
}

From "startTime": 1770788338 and "endTime": 1770788383, we can see that it's searching for logs within a 45-second period across the specified log groups.

What's concerning is that log groups starting with /ecs/ such as /ecs/ and /ecs/default-nginx-5d29 are also being included as query targets. I didn't specify these conditions.

Looking at CloudTrail, when log correlation was performed, it was searching for log groups starting with /ecs/:

{
    "eventVersion": "1.11",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "<Principal ID>",
        "arn": "arn:aws:sts::<AWS Account ID>:assumed-role/<IAM Role Name>/<Session Name>",
        "accountId": "<AWS Account ID>",
        "accessKeyId": "<Access Key>",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "<Principal ID>",
                "arn": "arn:aws:iam::<AWS Account ID>:role/<IAM Role Name>",
                "accountId": "<AWS Account ID>",
                "userName": "<IAM User Name>"
            },
            "attributes": {
                "creationDate": "2026-02-11T05:38:53Z",
                "mfaAuthenticated": "true"
            }
        }
    },
    "eventTime": "2026-02-11T06:02:54Z",
    "eventSource": "logs.amazonaws.com",
    "eventName": "DescribeLogGroups",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "<Source IP Address>",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
    "requestParameters": {
        "logGroupNamePrefix": "/ecs/",
        "includeLinkedAccounts": true
    },
    "responseElements": null,
    "requestID": "c59cb761-d8cf-4c71-aa7e-24e7ca4a9cb7",
    "eventID": "46c30084-8d1d-4484-a167-0b4821b413fc",
    "readOnly": true,
    "resources": [
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/default-nginx-5d29"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/ecs-aws-firelens-sidecar-container"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/ecs-aws-otel-sidecar-collector"
        },
        {
            "accountId": "<AWS Account ID>",
            "type": "AWS::Logs::LogGroup",
            "ARN": "arn:aws:logs:us-east-1:<AWS Account ID>:log-group:/ecs/nginx"
        }
    ],
    "eventType": "AwsApiCall",
    "apiVersion": "20140328",
    "managementEvent": true,
    "recipientAccountId": "<AWS Account ID>",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "logs.us-east-1.amazonaws.com"
    },
    "sessionCredentialFromConsole": "true"
}

Personally, I'd like to disable this feature.

I'm concerned about Logs Insights billing when there's a large volume of logs flowing through log groups starting with /ecs/. Since it's using filter @message like "<trace ID>" to search the entire log message body, field indexing won't be effective.

If anyone knows how to opt out of including log groups starting with /ecs/ in log correlation, I'd appreciate the information.

Personally, I'm considering avoiding creating log groups that start with /ecs/ as a workaround.

Log Correlation is Easy to Set Up

I tried trace and log correlation with CloudWatch Application Signals.

It was easier than I expected because you just need to use a supported logger and specify the log group name with an environment variable.

By the way, since I'm only sending error logs to CloudWatch Logs this time, log correlation isn't possible when the HTTP status code 200 is returned.

All logs are output to an S3 bucket via Data Firehose. If you want to find logs with HTTP status code 200, search using the trace ID. Logs are recorded as follows:

s3://ecsnativebluegreenstack-firelensconstructfirelensl-xwd5tetsrypf/ecs/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-VoUD00RfbaSW/app/stdout/2026/02/12/01/EcsNativeBlueGreenStack-FirelensConstructDeliverySt-TCVIns4InZMR-1-2026-02-12-01-48-38-aabb7cef-5384-33e4-937c-40c34494cb1c.gz
{
  "level": "info",
  "time": "2026-02-12T01:48:36.868Z",
  "pid": 7,
  "hostname": "ip-10-10-8-104.ec2.internal",
  "req": {
    "id": 55,
    "method": "GET",
    "url": "/",
    "query": {},
    "params": {},
    "headers": {
      "host": "ecsnat-albco-shaasnih0xar-1652870936.us-east-1.elb.amazonaws.com",
      "x-real-ip": "10.10.8.43",
      "x-forwarded-for": "<source IP address>, 10.10.8.43",
      "x-forwarded-proto": "http",
      "connection": "close",
      "x-forwarded-port": "80",
      "x-amzn-trace-id": "Root=1-698d3174-5876cff8075b183978fe1d65",
      "accept": "*/*",
      "user-agent": "curl/8.7.1"
    },
    "remoteAddress": "127.0.0.1",
    "remotePort": 35448
  },
  "trace_id": "698d3174fa6d5a5d85e08988b96c738d",
  "span_id": "6f704133bb94ada4",
  "trace_flags": "01",
  "res": {
    "statusCode": 200,
    "headers": {
      "x-powered-by": "Express",
      "content-type": "application/json; charset=utf-8",
      "content-length": "427",
      "etag": "W/\"1ab-kBv3WE96jXCcAIdDtz3e81LLMik\"",
      "set-cookie": [
        "connect.sid=s%3A_ykD64hKoji4n7SB71zDtgljHN5UDJBx.7y5uIGbxZsGgM4knIOw1Pt7BAOhIo%2FA6NLGKotG6hoU; Path=/; Expires=Fri, 13 Feb 2026 01:48:36 GMT; HttpOnly"
      ]
    }
  },
  "responseTime": 292,
  "msg": "request completed",
  "container_id": "c9979e7d4b08471b8285c6b3ecfd5cfb-0527074092",
  "container_name": "app",
  "source": "stdout",
  "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-VoUD00RfbaSW",
  "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWS account ID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-VoUD00RfbaSW/c9979e7d4b08471b8285c6b3ecfd5cfb",
  "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:102",
  "datetime": "2026-02-12T01:48:36.869Z"
}

I hope this article helps someone.

That's all from NonPi (@non____97) of the Consulting Department, Cloud Business Division!

Share this article

FacebookHatena blogX