Skip to content

Commit 6fb6d7f

Browse files
committed
add faas attributes
1 parent afed09e commit 6fb6d7f

File tree

4 files changed

+131
-27
lines changed

4 files changed

+131
-27
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010
- Make sure flushing is not canceled
1111

1212
The latest layer is: `arn:aws:lambda:${your-region-here}:097948374213:layer:baselime-node:9`
13+
14+
The latest layer is: `arn:aws:lambda:${your-region-here}:374211872663:layer:baselime-node:17`
15+
16+
17+
The latest layer is: `arn:aws:lambda:${your-region-here}:374211872663:layer:baselime-node:18`
18+
1319
## [0.1.12] 2023-06-28
1420

1521
- publish via CI

multiRegion.json

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
{
22
"regions": [
3-
"eu-west-1",
4-
"eu-west-2",
5-
"us-east-1",
6-
"us-east-2",
7-
"us-west-2",
8-
"eu-central-1",
9-
"ap-south-1",
10-
"ap-southeast-1",
11-
"ap-southeast-2",
12-
"ap-northeast-1",
13-
"ca-central-1"
3+
"eu-west-2"
144
],
155
"output": "md"
166
}

src/index.ts

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import api, { trace, context, propagation, Context as OtelContext, ROOT_CONTEXT, Attributes } from "@opentelemetry/api";
2-
import { Handler } from "aws-lambda";
2+
import { Handler, DynamoDBStreamEvent, S3Event } from "aws-lambda";
33
import { flattenObject } from './utils';
44
import { Context } from 'aws-lambda';
55
export * as logger from './logger';
@@ -8,29 +8,50 @@ declare const global : {
88
baselimeLambdaFlush: () => Promise<void>;
99
}
1010

11+
type FaasDocument = {
12+
collection: string
13+
operation: string
14+
time: string
15+
name: string
16+
}
17+
let coldstart = true;
18+
1119
export function wrap(handler: Handler) {
1220
return async (event: any, lambda_context: Context) => {
1321
const tracer = trace.getTracer('@baselime/baselime-lambda-wrapper', '1');
22+
const service = detectService(event);
23+
const trigger = triggerToServiceType(service);
24+
const parent = determinParent(event, service);
25+
let document: FaasDocument | null = null;
26+
if(trigger === 'datasource') {
27+
if(service === 'dynamodb') {
28+
document = getDynamodbStreamDocumentAttributes(event);
29+
}
1430

15-
const parent = determinParent(event);
31+
if(service === 's3') {
32+
document = getS3DocumentAttributes(event);
33+
}
34+
}
1635
const span = tracer.startSpan(lambda_context.functionName, {
1736
attributes: flattenObject({
1837
event,
1938
context: lambda_context,
2039
faas: {
2140
execution: lambda_context.awsRequestId,
22-
name: lambda_context.functionName,
2341
runtime: 'nodejs',
42+
trigger,
43+
document,
44+
invoked_by: service,
2445
id: lambda_context.invokedFunctionArn,
25-
46+
coldstart,
2647
},
2748
cloud: {
28-
account: {
29-
id: lambda_context.invokedFunctionArn.split(":")[4],
30-
}
49+
resource_id: lambda_context.invokedFunctionArn,
50+
account_id: lambda_context.invokedFunctionArn.split(":")[4],
3151
}
3252
}) as Attributes,
3353
}, parent);
54+
coldstart = false;
3455
const ctx = trace.setSpan(context.active(), span);
3556

3657
try {
@@ -55,16 +76,56 @@ export function wrap(handler: Handler) {
5576

5677
function detectService(event: any) {
5778
if (event.requestContext?.apiId) {
58-
return "api";
79+
return "api-gateway";
5980
}
6081

82+
if(event.requestContext?.apiId && event.version === "2.0") {
83+
return "api-gateway-v2";
84+
}
85+
6186
if (event.Records && event.Records[0]?.EventSource === "aws:sns") {
6287
return "sns";
6388
}
6489

90+
if(event.Records && event.Records[0]?.eventSource === "aws:sqs") {
91+
return "sqs";
92+
}
93+
94+
if(event.Records && event.Records[0]?.eventSource === "aws:kinesis") {
95+
return "kinesis";
96+
}
97+
98+
if(event.Records && event.Records[0]?.eventSource === "aws:dynamodb") {
99+
return "dynamodb";
100+
}
101+
102+
if(event.Records && event.Records[0]?.eventSource === "aws:s3") {
103+
return "s3";
104+
}
105+
65106
return 'unknown'
66107
}
67108

109+
function triggerToServiceType(service: string) {
110+
switch (service) {
111+
case "api":
112+
case "api-gateway":
113+
case "api-gateway-v2":
114+
case "function-url":
115+
return "http";
116+
case "sns":
117+
case "sqs":
118+
case "kinesis":
119+
case "eventbridge":
120+
return "pubsub";
121+
case "dynamodb":
122+
case "s3":
123+
return "datasource"
124+
default:
125+
return "other";
126+
}
127+
}
128+
68129
const headerGetter = {
69130
keys(carrier: Object): string[] {
70131
return Object.keys(carrier);
@@ -83,10 +144,10 @@ const snsGetter = {
83144
},
84145
};
85146

86-
function determinParent(event: any): OtelContext {
147+
function determinParent(event: any, service: string): OtelContext {
87148
let parent: OtelContext | undefined = undefined;
88149

89-
const extractedContext = extractContext(event);
150+
const extractedContext = extractContext(event, service);
90151

91152
if (trace.getSpan(extractedContext)?.spanContext()) {
92153
return extractedContext;
@@ -98,9 +159,12 @@ function determinParent(event: any): OtelContext {
98159
return parent;
99160
}
100161

101-
function extractContext(event: any) {
102-
switch (detectService(event)) {
162+
function extractContext(event: any, service: string) {
163+
switch (service) {
103164
case "api":
165+
case "api-gateway":
166+
case "api-gateway-v2":
167+
case "function-url":
104168
const httpHeaders = event.headers || {};
105169
return propagation.extract(
106170
api.context.active(),
@@ -116,3 +180,39 @@ function extractContext(event: any) {
116180
}
117181
return propagation.extract(api.context.active(), {}, headerGetter);
118182
}
183+
184+
const DynamodbEventToDocumentOperations = {
185+
INSERT: 'insert',
186+
MODIFY: 'update',
187+
REMOVE: 'delete',
188+
default: ''
189+
};
190+
191+
function getDynamodbStreamDocumentAttributes(event: DynamoDBStreamEvent): FaasDocument {
192+
const unixTime = event?.Records[0]?.dynamodb?.ApproximateCreationDateTime || Date.now() / 1000;
193+
return {
194+
// TODO we could do better for collection (infer from single table design patterns?)
195+
collection: (event?.Records[0]?.eventSourceARN || '').split("/")[1],
196+
name: (event?.Records[0]?.eventSourceARN || '').split("/")[1],
197+
operation: DynamodbEventToDocumentOperations[event?.Records[0]?.eventName || "default"],
198+
time: new Date(unixTime).toUTCString(),
199+
}
200+
}
201+
202+
function getS3DocumentAttributes(event: S3Event): FaasDocument {
203+
let operation = 'unkown';
204+
205+
if(event.Records[0].eventName.startsWith('ObjectCreated')) {
206+
operation = 'insert';
207+
}
208+
209+
if(event.Records[0].eventName.startsWith('ObjectRemoved')) {
210+
operation = 'delete';
211+
}
212+
return {
213+
collection: event.Records[0].s3.bucket.name,
214+
name: event.Records[0].s3.object.key,
215+
operation,
216+
time: event.Records[0].eventTime,
217+
}
218+
}

src/lambda-wrapper.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import api, { Attributes, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
22
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
33
import {
4-
OTLPTraceExporter,
4+
OTLPTraceExporter,
55
} from "@opentelemetry/exporter-trace-otlp-http";
66
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
77
import { Instrumentation, registerInstrumentations } from "@opentelemetry/instrumentation";
@@ -12,7 +12,7 @@ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
1212
import { Resource } from "@opentelemetry/resources";
1313
import { flattenObject } from "./utils";
1414
import { existsSync } from "node:fs";
15-
15+
import { arch } from "node:os"
1616
if (process.env.OTEL_LOG_LEVEL === "debug") {
1717
api.diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL);
1818
}
@@ -21,7 +21,11 @@ const provider = new NodeTracerProvider({
2121
resource: new Resource({
2222
"service.name": process.env.BASELIME_SERVICE,
2323
"faas.name": process.env.AWS_LAMBDA_FUNCTION_NAME,
24-
'aws.region': process.env.AWS_REGION || 'unknown',
24+
"faas.max_memory": process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE,
25+
"faas.architecture": arch(),
26+
"faas.version": process.env.AWS_LAMBDA_FUNCTION_VERSION,
27+
'cloud.region': process.env.AWS_REGION || 'unknown',
28+
"cloud.provider": "aws",
2529
}),
2630
});
2731

@@ -31,9 +35,13 @@ if (existsSync('/opt/extensions/baselime')) {
3135
collectorURL = 'http://sandbox:4323';
3236
}
3337

38+
enum CompressionAlgorithm {
39+
GZIP = "gzip",
40+
}
3441
const spanProcessor = new BatchSpanProcessor(
3542
new OTLPTraceExporter({
36-
url: collectorURL,
43+
url: `${collectorURL}/${process.env.AWS_LAMBDA_FUNCTION_NAME}`,
44+
compression: CompressionAlgorithm.GZIP,
3745
headers: {
3846
"x-api-key": process.env.BASELIME_KEY || process.env.BASELIME_OTEL_KEY,
3947
},

0 commit comments

Comments
 (0)