Skip to content

SigV4 Authentication Support for OTLP HTTP Logs Exporter #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

liustve
Copy link
Contributor

@liustve liustve commented May 12, 2025

Issue #, if available:
Supporting ADOT JS auto instrumentation to automatically inject SigV4 authentication headers for outgoing log requests to the allow exporting to the AWS Logs OTLP endpoint. Users will need to configure the following environment variables in order to enable and properly run this exporter:

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs.[AWS-REGION].amazonaws.com/v1/logs; required
OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-aws-log-group=[CW-LOG-GROUP-NAME],x-aws-log-stream=[CW-LOG-STREAM-NAME] required
OTEL_LOGS_EXPORTER=otlp required or do not set env variable
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf required or do not set env variable
OTEL_METRICS_EXPORTER=none

This feature currently supports only 2 logging libraries by auto-instrumentation, Bunyan and Winston:
https://docs.honeycomb.io/send-data/logs/opentelemetry/sdk/javascript/

Description of changes:

  1. Add new AwsAuthenticator class used by both OtlpAwsLogExporter and OtlpAwsSpanExporter which extends the upstream OTLPProtoLogExporter to inject Sigv4 headers directly into the headers.

  2. Modified ADOT JS auto instrumentation to automatically detect if a user is exporting to CW Logs OTLP Logs endpoint by checking if the environment variable OTEL_EXPORTER_OTLP_LOGS_ENDPOINT is configured to match this url pattern: https://logs.[AWS-REGION].amazonaws.com/v1/logs

Testing:

  1. E2E test done in an empty EC2 environment without configuring .aws credentials config file or setting AWS credentials in the environment variable
  2. Manual testing was done by configuring the above environment variables and setting up the sample app locally with ADOT auto instrumentation and verified the logs in CW Logs.
  3. Unit tests were added to verify functionality of OtlpAwsLogsExporter

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

Example log in CW Logs:

{
    "resource": {
        "attributes": {
            "service.name": "unknown_service:/home/ec2-user/.local/share/mise/installs/node/22.14.0/bin/node",
            "process.command_args": [
                "/home/ec2-user/.local/share/mise/installs/node/22.14.0/bin/node",
                "--experimental-network-inspection",
                "--require",
                "@aws/aws-distro-opentelemetry-node-autoinstrumentation/register",
                "/home/ec2-user/aws-otel-js-instrumentation/sample-applications/simple-express-server/sample-app-express-server.js"
            ],
            "process.runtime.version": "22.14.0",
            "process.pid": 1599620,
            "process.executable.name": "/home/ec2-user/.local/share/mise/installs/node/22.14.0/bin/node",
            "telemetry.sdk.name": "opentelemetry",
            "process.owner": "ec2-user",
            "telemetry.sdk.language": "nodejs",
            "process.runtime.name": "nodejs",
            "process.executable.path": "/home/ec2-user/.local/share/mise/installs/node/22.14.0/bin/node",
            "host.arch": "amd64",
            "telemetry.sdk.version": "1.30.1",
            "process.command": "/home/ec2-user/aws-otel-js-instrumentation/sample-applications/simple-express-server/sample-app-express-server.js",
            "host.name": "ip-172-31-7-29.us-west-2.compute.internal",
            "process.runtime.description": "Node.js",
            "telemetry.auto.version": "0.6.0-dev0-aws",
            "host.id": "ec2ccd3acc52be039f977d9b1de7c64d"
        }
    },
    "scope": {
        "name": "default"
    },
    "timeUnixNano": 1748220324612000000,
    "observedTimeUnixNano": 1748220324612000000,
    "severityNumber": 9,
    "severityText": "INFO",
    "body": "Received request to /rolldice",
    "attributes": {
        "endpoint": "/rolldice"
    },
    "flags": 1,
    "traceId": "6833b9a4cccf4d9bfb82b11686fd8f63",
    "spanId": "bd19f2fcbc107755"
}

@liustve liustve requested a review from a team as a code owner May 12, 2025 20:17
@liustve liustve marked this pull request as draft May 12, 2025 20:17
@liustve liustve self-assigned this May 12, 2025
@liustve liustve closed this May 27, 2025
@liustve liustve changed the title [WIP] SigV4 Authentication Support for OTLP HTTP Logs Exporter SigV4 Authentication Support for OTLP HTTP Logs Exporter May 28, 2025
@liustve liustve reopened this May 28, 2025
@liustve liustve marked this pull request as ready for review May 28, 2025 00:28
@liustve liustve requested review from srprash and jj22ee May 28, 2025 00:28
@jj22ee
Copy link
Contributor

jj22ee commented Jun 12, 2025

This feature currently supports only 2 logging libraries by auto-instrumentation, Bunyan and Winston:
https://docs.honeycomb.io/send-data/logs/opentelemetry/sdk/javascript/

Can we support Pino as well? We mention it in our docs and is considered top 2 popular logging libraries for Node.js.

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Application-Signals-TraceLogCorrelation.html

The Pino, Winston, or Bunyan auto-instrumentations for Node.js.

@@ -83,6 +84,9 @@ try {
diag.info('Setting TraceProvider for instrumentations at the end of initialization');
for (const instrumentation of instrumentations) {
instrumentation.setTracerProvider(trace.getTracerProvider());
if (instrumentation.setLoggerProvider) {
instrumentation.setLoggerProvider(logs.getLoggerProvider());
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering if this is required? Since Global Logger Provider is also set here: https://github.com/open-telemetry/opentelemetry-js/blob/v1.30.1/experimental/packages/opentelemetry-sdk-node/src/sdk.ts#L401

Does this feature without this setLoggerProvider code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep confirmed this works without the setLoggerProvider

* sending it to the endpoint. Otherwise, we will skip signing.
*/
public override async export(items: ReadableSpan[], resultCallback: (result: ExportResult) => void): Promise<void> {
const serializedSpans: Uint8Array | undefined = ProtobufTraceSerializer.serializeRequest(items);
Copy link
Contributor

Choose a reason for hiding this comment

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

Previously, we return right away and do nothing if serializedSpans was undefined, but not anymore in the new change. Is there a reason for this, was it because it was redundant because the super call would also do nothing (I didn't check the super method logic)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added the immediate return back if serializedSpans is undefined

// Cleans up Sigv4 from headers to avoid accidentally copying them to the new headers
private removeSigV4Headers(headers: Record<string, string>) {
const newHeaders: Record<string, string> = {};
const sigV4Headers = ['x-amz-date', 'authorization', 'x-amz-content-sha256', 'x-amz-security-token'];
Copy link
Contributor

Choose a reason for hiding this comment

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

You could move this to be a constant outside of this class.

Comment on lines +60 to +65
const signer = new SignatureV4({
credentials: defaultProvider(),
region: this.region,
service: this.service,
sha256: Sha256,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be a class member, no need to create this object for every authentication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe we need to since defaultProvider() needs to be called in every export to ensure we're passing in the latest credentials.


for (const pair of logsHeaders.split(',')) {
if (pair.includes('=')) {
const [key, value] = pair.split('=', 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

fyi, this makes it so that 'a=b=c'.split('=', 2) become ["a", "b"], not ["a", "b=c"]

You may want to consider this approach if this is a concern:

for (const arg of args) {
const equalIndex: number = arg.indexOf('=');
if (equalIndex === -1) {
continue;
}

But based on the logic, you don't care about the value, so it may not be a concern.

if (filteredLogHeadersCount !== 2) {
diag.warn(
'Improper configuration: Please configure the environment variable OTEL_EXPORTER_OTLP_LOGS_HEADERS ' +
'to have values for x-aws-log-group and x-aws-log-stream'
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
'to have values for x-aws-log-group and x-aws-log-stream'
`to have values for ${AWS_OTLP_LOGS_GROUP_HEADER} and ${AWS_OTLP_LOGS_STREAM_HEADER}`

}
}

if (filteredLogHeadersCount !== 2) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Technically, a user can just set x-aws-log-group twice, and that would cause validateLogsHeaders() to be true. To get around this, you can just split the (key === AWS_OTLP_LOGS_GROUP_HEADER || key === AWS_OTLP_LOGS_STREAM_HEADER) check into an if-elseif statement and just validate that both statements are entered.

@liustve
Copy link
Contributor Author

liustve commented Jun 20, 2025

This feature currently supports only 2 logging libraries by auto-instrumentation, Bunyan and Winston:
https://docs.honeycomb.io/send-data/logs/opentelemetry/sdk/javascript/

Can we support Pino as well? We mention it in our docs and is considered top 2 popular logging libraries for Node.js.

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Application-Signals-TraceLogCorrelation.html

The Pino, Winston, or Bunyan auto-instrumentations for Node.js.

Yep should also support Pino as well, I missed that in the description.

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.

2 participants