Skip to content

Add Application Signals logs pipeline translator and allow metric routing to otlp endpoint#2111

Merged
jefchien merged 45 commits into
aws:mainfrom
jj22ee:appsignals-logs-translator-pr
May 22, 2026
Merged

Add Application Signals logs pipeline translator and allow metric routing to otlp endpoint#2111
jefchien merged 45 commits into
aws:mainfrom
jj22ee:appsignals-logs-translator-pr

Conversation

@jj22ee
Copy link
Copy Markdown
Contributor

@jj22ee jj22ee commented May 8, 2026

Description of the issue

Application Signals currently collects metrics and traces. This PR updates and refactors the applicationsignals translator to also generate OTel logs and metrics pipelines that route OTLP data to CloudWatch via the CW OTLP endpoints, with automatic per-service log group creation and selective metrics routing.

Customers configure logs.logs_collected.application_signals with optional log_group_name (supporting placeholders like {service.name}) and log_stream_name. The provisioner extension creates log groups/streams on first encounter.

Description of changes

Update the existing translator/translate/otel/pipeline/applicationsignals/ translator with routing connectors for both logs and metrics signals. Each signal uses a 3-pipeline topology (receive → route → export) connected via a routing connector.

Metrics (3 pipelines via routing connector):

  • metrics/application_signals_metrics_route: OTLP receiver → routing connector
  • metrics/application_signals_metrics_logs_destination (default): routing → existing AppSignals processors → EMF exporter
  • metrics/application_signals_metrics_otlp_destination: routing → batch → otlphttp to CW OTLP monitoring endpoint
    • Routes by: attributes["Telemetry.Source"] == "ServiceEvents" (datapoint context)

Logs (3 pipelines via routing connector):

  • logs/application_signals_logs_route: OTLP receiver → transform → attributestocontext → transform → routing connector
    • Routes by: attributes["event.name"] == "aws.service_events.aggregate_profile" (log context)
    • If there are no placeholders, transform and attributestocontext are not used
    • temporary resource attributes (prefixed with temporary_key.) are used to help build the aws.log.group.name/aws.log.stream.name attributes in the first transform, and will be discarded in the second transform.
  • logs/application_signals_logs_batch (default): routing → batch → otlphttp to CW OTLP logs endpoint
  • logs/application_signals_logs_nobatch: routing → otlphttp (no batch, for large payloads)
    • Auth chain: otlphttp → headers_setter → awscloudwatchlogsprovisioner → sigv4auth

Traces (unchanged):

  • Single pipeline with existing processors and X-Ray exporter

Notable behaviors:

  • Logs auto-opt-in: logs pipeline activates when metrics_collected.application_signals is configured. Explicitly disable via: logs.logs_collected.application_signals.disabled: true
  • Multiple placeholders supported in both group and stream (e.g. /{deployment.environment}/prefix/{service.name}) (Default log group: /aws/service-events/{service.name})
  • Customer-set resource attributes for aws.log.group.name/aws.log.stream.name are ignored

New/Updated shared translator packages:

  • extension/awscloudwatchlogsprovisioner — new
  • extension/headerssetter — new
  • processor/attributestocontext — new
  • connector/routing — new
  • exporter/otlphttp — new (generic, signal-aware EndpointConfig)
  • extension/sigv4auth — Updated with WithService option
  • processor/batchprocessor — Updated with WithMetadataKeys option
  • processor/transformprocessor — Updated with NewTranslatorWithLogStatements

License

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Tests

  • Unit tests
  • Integration tests in amazon-cloudwatch-agent-test: <TODO: Make PR>

Requirements

Before commiting your code, please do the following steps.

  1. Run make fmt and make fmt-sh
  2. Run make lint

Integration Tests

To run integration tests against this PR, add the ready for testing label.

@jj22ee jj22ee requested a review from a team as a code owner May 8, 2026 00:09
@jj22ee jj22ee force-pushed the appsignals-logs-translator-pr branch from 83b5680 to 9027c02 Compare May 8, 2026 00:12
@jj22ee jj22ee changed the title Add Translation logic for logs.logs_collected.application_signals Add Application Signals logs pipeline translator with dynamic log group routing May 8, 2026
@jj22ee jj22ee changed the title Add Application Signals logs pipeline translator with dynamic log group routing Add Application Signals logs pipeline translator May 8, 2026
Comment thread go.mod
Comment on lines +53 to +54
// TODO: Update default log group prefix before PR is merged.
defaultLogGroupPrefix = "/aws/telemetry/"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still pending and not final.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will now be /aws/service-events/{service.name}.

defaultLogStreamName = "default"

metadataKeyLogGroup = "aws.cloudwatch.log_group.destination"
metadataKeyLogStream = "aws.cloudwatch.log_stream.destination"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These key values are not following any otel spec, they're just arbitrary attributes created by Transform Processor which are used by AttributesToContext Processor, so we don't expect customers to know about these keys or even set them in their exported logs.

However, if customer does know and set these these attributes in their logs, we'll respect it and won't override them.

@jj22ee jj22ee force-pushed the appsignals-logs-translator-pr branch from 51f3c5d to d454f8f Compare May 8, 2026 02:18
}
// Auto-enable logs.logs_collected.application_signals when metrics is configured
// Must happen before creating confmap since confmap may copy the map
applicationsignalslogs.AutoEnableIfNeeded(m)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You dont need to mutate the json for this.. just rely on the existing AppSignals enabled checks in the pipeline translator.

translators.Merge(containerInsightsTranslators)
translators.Set(applicationsignals.NewTranslator(pipeline.SignalTraces))
translators.Set(applicationsignals.NewTranslator(pipeline.SignalMetrics))
translators.Set(applicationsignalslogs.NewTranslator())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really need to be a new translator? We've been trying to keep one translator per "solution" that can internally create multiple pipelines.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed, moved into the existing applicationsignals translator.

logKey = common.ConfigKey(common.LogsKey, common.LogsCollectedKey)
metricKey = common.ConfigKey(common.MetricsKey, common.MetricsCollectedKey)
skipInputSet = collections.NewSet[string](files.SectionKey, windowsevents.SectionKey)
skipInputSet = collections.NewSet[string](files.SectionKey, windowsevents.SectionKey, common.AppSignals, common.AppSignalsFallback)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What in your changes forced you to add this?
i.e. what complained when you didnt have this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before my change, application_signals only existed under logs.metrics_collected (for metrics pipeline) and traces.traces_collected (for traces pipeline). The adapter never saw it because it only scans logs_collected.

Now that we added logs.logs_collected.application_signals, the adapter scans it and tries to create a telegraf_application_signals receiver which doesn't exist and is why I try to skip it here.


// NewTranslatorWithLogStatements creates a transform processor translator that
// executes the given OTTL statements in the "resource" context for logs.
func NewTranslatorWithLogStatements(name string, statements []string) common.ComponentTranslator {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we follow the same pattern you did with WithMetadataKeys in the batch processor translator instead of creating this?

}

func (t *translator) ID() component.ID {
return component.MustNewID("attributestocontext")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we just do return component.NewIDWithName(t.factory.Type(), t.name)?

}

func (t *translator) ID() component.ID {
return component.NewIDWithName(component.MustNewType("headers_setter"), t.name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Same here too and for the other components - can we just do return component.NewIDWithName(t.factory.Type(), t.name).


var _ common.ComponentTranslator = (*translator)(nil)

func NewTranslatorWithName(name string, additionalAuth component.ID, headers []HeaderMapping) common.ComponentTranslator {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The With might be better suited unless we are saying these are all mandatory.

@jj22ee jj22ee force-pushed the appsignals-logs-translator-pr branch 2 times, most recently from 960cebe to 46cd360 Compare May 16, 2026 02:31
@jj22ee jj22ee force-pushed the appsignals-logs-translator-pr branch from 5e9f489 to d5a4647 Compare May 16, 2026 06:48
Comment thread translator/tocwconfig/sampleConfig/appsignals_and_eks_config.yaml Outdated
Comment thread translator/tocwconfig/sampleConfig/appsignals_and_eks_config.yaml
func NewTranslatorWithName(name string) common.ComponentTranslator {
return &translator{name, sigv4authextension.NewFactory()}
func NewTranslatorWithService(service string) common.ComponentTranslator {
return &translator{name: service, service: service, factory: sigv4authextension.NewFactory()}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Don't need a separate field for name since it's always the same as service.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 6b3465c

AppSignalsLogs = ConfigKey(LogsKey, LogsCollectedKey, AppSignals)
AppSignalsTracesFallback = ConfigKey(TracesKey, TracesCollectedKey, AppSignalsFallback)
AppSignalsMetricsFallback = ConfigKey(LogsKey, MetricsCollectedKey, AppSignalsFallback)
AppSignalsLogsFallback = ConfigKey(LogsKey, LogsCollectedKey, AppSignalsFallback)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Seems unused

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 61ff1d1

translators.Set(NewTranslator(signal, SetVariant(metricsVariantLogDest)))
translators.Set(NewTranslator(signal, SetVariant(metricsVariantOtlpDest)))
case pipeline.SignalLogs:
if isLogsDisabled(conf) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should have a nil check for the conf.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 62723cf

Comment thread translator/config/schema.json Outdated
Comment on lines +665 to +671
"log_group_name": {
"type": "string",
"minLength": 1
},
"log_stream_name": {
"type": "string",
"minLength": 1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maxLength could be set to 512 to match https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html although I'm sure the placeholders will allow it to go past that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9763ae6.

Comment on lines +17 to +18
- condition: attributes["Telemetry.Source"] == "ServiceEvents"
context: datapoint
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this is on the datapoint attributes and not the resource attributes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our SDK, it will set the attribute in on counter.add() attributes, so it's a datapoint attribute. SDK can send datapoints with different kinds of Telemetry.Source so we can't have it on resource.attributes.

Comment on lines +18 to +25
// EndpointConfig specifies the base endpoint and signal-specific endpoint for
// the otlphttp exporter.
type EndpointConfig struct {
BaseEndpoint string // e.g. "https://logs.us-east-1.amazonaws.com"
LogsEndpoint string // e.g. "https://logs.us-east-1.amazonaws.com/v1/logs"
MetricsEndpoint string
TracesEndpoint string
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I'm fine with what you have as well.

error_mode: propagate
statements:
- set(resource.attributes["aws.cloudwatch.log_group.destination"], Concat(["/aws/service-events/", resource.attributes["service.name"]], "")) where resource.attributes["aws.cloudwatch.log_group.destination"] == nil
- replace_pattern(resource.attributes["aws.cloudwatch.log_group.destination"], "<nil>", "undefined")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove this aws.cloudwatch.log_group.destination resource attribute after attributestocontext sets it in the context? If we don't it's going to be included in every single log we send. Could add a transformprocessor for to clean it up before routing or in the export pipeline.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, added a transform processor to cleanup: 5cec422

Comment on lines +60 to +61
metadataKeyLogGroup = "aws.cloudwatch.log_group.destination"
metadataKeyLogStream = "aws.cloudwatch.log_stream.destination"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Consider making the metadata key consistent with the header key.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to use the naming aws.log.group.name / aws.log.stream.name instead of aws.cloudwatch.log_...?

Done in cb34b0a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant x-aws-log-group/x-aws-log-stream like with https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_HTTP_Endpoints_OTLP.html so it's clear what they're for, but I think it's fine either way.

Comment thread translator/translate/otel/pipeline/applicationsignals/translator.go Outdated
Comment thread translator/translate/otel/pipeline/applicationsignals/translator.go Outdated
@jefchien jefchien added the ready for testing Indicates this PR is ready for integration tests to run label May 21, 2026
okankoAMZ
okankoAMZ previously approved these changes May 21, 2026
jefchien
jefchien previously approved these changes May 21, 2026
Comment thread translator/config/schema.json
@jj22ee jj22ee dismissed stale reviews from jefchien and okankoAMZ via ffdf574 May 21, 2026 17:04
@jj22ee jj22ee force-pushed the appsignals-logs-translator-pr branch from 562e8a3 to a02ae36 Compare May 21, 2026 17:50
Comment thread go.mod Outdated
@jefchien jefchien removed the ready for testing Indicates this PR is ready for integration tests to run label May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants