Skip to content

Commit 91c9089

Browse files
feat: report url for lambda invoked via api gateway (#2404)
Co-authored-by: Marc Pichler <[email protected]>
1 parent ba615db commit 91c9089

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

plugins/node/opentelemetry-instrumentation-aws-lambda/src/instrumentation.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ import {
3737
TextMapGetter,
3838
TracerProvider,
3939
ROOT_CONTEXT,
40+
Attributes,
4041
} from '@opentelemetry/api';
4142
import {
43+
ATTR_URL_FULL,
4244
SEMATTRS_FAAS_EXECUTION,
4345
SEMRESATTRS_CLOUD_ACCOUNT_ID,
4446
SEMRESATTRS_FAAS_ID,
@@ -244,6 +246,7 @@ export class AwsLambdaInstrumentation extends InstrumentationBase<AwsLambdaInstr
244246
context.invokedFunctionArn
245247
),
246248
[ATTR_FAAS_COLDSTART]: requestIsColdStart,
249+
...AwsLambdaInstrumentation._extractOtherEventFields(event),
247250
},
248251
},
249252
parent
@@ -426,6 +429,52 @@ export class AwsLambdaInstrumentation extends InstrumentationBase<AwsLambdaInstr
426429
return propagation.extract(otelContext.active(), httpHeaders, headerGetter);
427430
}
428431

432+
private static _extractOtherEventFields(event: any): Attributes {
433+
const answer: Attributes = {};
434+
const fullUrl = this._extractFullUrl(event);
435+
if (fullUrl) {
436+
answer[ATTR_URL_FULL] = fullUrl;
437+
}
438+
return answer;
439+
}
440+
441+
private static _extractFullUrl(event: any): string | undefined {
442+
// API gateway encodes a lot of url information in various places to recompute this
443+
if (!event.headers) {
444+
return undefined;
445+
}
446+
// Helper function to deal with case variations (instead of making a tolower() copy of the headers)
447+
function findAny(
448+
event: any,
449+
key1: string,
450+
key2: string
451+
): string | undefined {
452+
return event.headers[key1] ?? event.headers[key2];
453+
}
454+
const host = findAny(event, 'host', 'Host');
455+
const proto = findAny(event, 'x-forwarded-proto', 'X-Forwarded-Proto');
456+
const port = findAny(event, 'x-forwarded-port', 'X-Forwarded-Port');
457+
if (!(proto && host && (event.path || event.rawPath))) {
458+
return undefined;
459+
}
460+
let answer = proto + '://' + host;
461+
if (port) {
462+
answer += ':' + port;
463+
}
464+
answer += event.path ?? event.rawPath;
465+
if (event.queryStringParameters) {
466+
let first = true;
467+
for (const key in event.queryStringParameters) {
468+
answer += first ? '?' : '&';
469+
answer += encodeURIComponent(key);
470+
answer += '=';
471+
answer += encodeURIComponent(event.queryStringParameters[key]);
472+
first = false;
473+
}
474+
}
475+
return answer;
476+
}
477+
429478
private static _determineParent(
430479
event: any,
431480
context: Context,

plugins/node/opentelemetry-instrumentation-aws-lambda/test/integrations/lambda-handler.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
3333
import { Context } from 'aws-lambda';
3434
import * as assert from 'assert';
3535
import {
36+
ATTR_URL_FULL,
3637
SEMATTRS_EXCEPTION_MESSAGE,
3738
SEMATTRS_FAAS_COLDSTART,
3839
SEMATTRS_FAAS_EXECUTION,
@@ -768,4 +769,52 @@ describe('lambda handler', () => {
768769
assert.strictEqual(span.parentSpanId, undefined);
769770
});
770771
});
772+
773+
describe('url parsing', () => {
774+
it('pulls url from api gateway rest events', async () => {
775+
initializeHandler('lambda-test/sync.handler');
776+
const event = {
777+
path: '/lambda/test/path',
778+
headers: {
779+
Host: 'www.example.com',
780+
'X-Forwarded-Proto': 'http',
781+
'X-Forwarded-Port': 1234,
782+
},
783+
queryStringParameters: {
784+
key: 'value',
785+
key2: 'value2',
786+
},
787+
};
788+
789+
await lambdaRequire('lambda-test/sync').handler(event, ctx, () => {});
790+
const [span] = memoryExporter.getFinishedSpans();
791+
assert.ok(
792+
span.attributes[ATTR_URL_FULL] ===
793+
'http://www.example.com:1234/lambda/test/path?key=value&key2=value2' ||
794+
span.attributes[ATTR_URL_FULL] ===
795+
'http://www.example.com:1234/lambda/test/path?key2=value2&key=value'
796+
);
797+
});
798+
it('pulls url from api gateway http events', async () => {
799+
initializeHandler('lambda-test/sync.handler');
800+
const event = {
801+
rawPath: '/lambda/test/path',
802+
headers: {
803+
host: 'www.example.com',
804+
'x-forwarded-proto': 'http',
805+
'x-forwarded-port': 1234,
806+
},
807+
queryStringParameters: {
808+
key: 'value',
809+
},
810+
};
811+
812+
await lambdaRequire('lambda-test/sync').handler(event, ctx, () => {});
813+
const [span] = memoryExporter.getFinishedSpans();
814+
assert.strictEqual(
815+
span.attributes[ATTR_URL_FULL],
816+
'http://www.example.com:1234/lambda/test/path?key=value'
817+
);
818+
});
819+
});
771820
});

0 commit comments

Comments
 (0)