Skip to content

Commit ec86475

Browse files
committed
add s3 and kinesis service extensions for aws-sdk instrumentation
1 parent 6e8989d commit ec86475

File tree

7 files changed

+286
-0
lines changed

7 files changed

+286
-0
lines changed

plugins/node/opentelemetry-instrumentation-aws-sdk/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
},
5353
"devDependencies": {
5454
"@aws-sdk/client-dynamodb": "3.85.0",
55+
"@aws-sdk/client-kinesis": "3.85.0",
5556
"@aws-sdk/client-lambda": "3.85.0",
5657
"@aws-sdk/client-s3": "3.85.0",
5758
"@aws-sdk/client-sns": "3.85.0",

plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/ServicesExtensions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
import { DynamodbServiceExtension } from './dynamodb';
2525
import { SnsServiceExtension } from './sns';
2626
import { LambdaServiceExtension } from './lambda';
27+
import { S3ServiceExtension } from './s3';
28+
import { KinesisServiceExtension } from './kinesis';
2729

2830
export class ServicesExtensions implements ServiceExtension {
2931
services: Map<string, ServiceExtension> = new Map();
@@ -33,6 +35,8 @@ export class ServicesExtensions implements ServiceExtension {
3335
this.services.set('SNS', new SnsServiceExtension());
3436
this.services.set('DynamoDB', new DynamodbServiceExtension());
3537
this.services.set('Lambda', new LambdaServiceExtension());
38+
this.services.set('S3', new S3ServiceExtension());
39+
this.services.set('Kinesis', new KinesisServiceExtension());
3640
}
3741

3842
requestPreSpanHook(
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { Attributes, Span, SpanKind, Tracer } from '@opentelemetry/api';
17+
import {
18+
AwsSdkInstrumentationConfig,
19+
NormalizedRequest,
20+
NormalizedResponse,
21+
} from '../types';
22+
import { _AWS_KINESIS_STREAM_NAME } from '../utils';
23+
import { RequestMetadata, ServiceExtension } from './ServiceExtension';
24+
25+
export class KinesisServiceExtension implements ServiceExtension {
26+
requestPreSpanHook(
27+
request: NormalizedRequest,
28+
_config: AwsSdkInstrumentationConfig
29+
): RequestMetadata {
30+
const streamName = request.commandInput.StreamName;
31+
32+
const spanKind: SpanKind = SpanKind.CLIENT;
33+
let spanName: string | undefined;
34+
35+
const spanAttributes: Attributes = {};
36+
37+
if (streamName) {
38+
spanAttributes[_AWS_KINESIS_STREAM_NAME] = streamName;
39+
}
40+
41+
const isIncoming = false;
42+
43+
return {
44+
isIncoming,
45+
spanAttributes,
46+
spanKind,
47+
spanName,
48+
};
49+
}
50+
51+
requestPostSpanHook = (request: NormalizedRequest) => {};
52+
53+
responseHook = (
54+
response: NormalizedResponse,
55+
span: Span,
56+
tracer: Tracer,
57+
config: AwsSdkInstrumentationConfig
58+
) => {};
59+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { Attributes, Span, SpanKind, Tracer } from '@opentelemetry/api';
17+
import {
18+
AwsSdkInstrumentationConfig,
19+
NormalizedRequest,
20+
NormalizedResponse,
21+
} from '../types';
22+
import { _AWS_S3_BUCKET } from '../utils';
23+
import { RequestMetadata, ServiceExtension } from './ServiceExtension';
24+
25+
export class S3ServiceExtension implements ServiceExtension {
26+
requestPreSpanHook(
27+
request: NormalizedRequest,
28+
_config: AwsSdkInstrumentationConfig
29+
): RequestMetadata {
30+
const bucketName = request.commandInput.Bucket;
31+
32+
const spanKind: SpanKind = SpanKind.CLIENT;
33+
let spanName: string | undefined;
34+
35+
const spanAttributes: Attributes = {};
36+
37+
if (bucketName) {
38+
spanAttributes[_AWS_S3_BUCKET] = bucketName;
39+
}
40+
41+
const isIncoming = false;
42+
43+
return {
44+
isIncoming,
45+
spanAttributes,
46+
spanKind,
47+
spanName,
48+
};
49+
}
50+
51+
requestPostSpanHook = (request: NormalizedRequest) => {};
52+
53+
responseHook = (
54+
response: NormalizedResponse,
55+
span: Span,
56+
tracer: Tracer,
57+
config: AwsSdkInstrumentationConfig
58+
) => {};
59+
}

plugins/node/opentelemetry-instrumentation-aws-sdk/src/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import {
2222
} from '@opentelemetry/semantic-conventions';
2323
import { AttributeNames } from './enums';
2424

25+
// TODO: Add these semantic attributes to:
26+
// - https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts
27+
// For S3, see specification: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/object-stores/s3.md
28+
export const _AWS_S3_BUCKET = 'aws.s3.bucket';
29+
export const _AWS_KINESIS_STREAM_NAME = 'aws.kinesis.stream.name';
30+
2531
const toPascalCase = (str: string): string =>
2632
typeof str === 'string' ? str.charAt(0).toUpperCase() + str.slice(1) : str;
2733

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {
18+
getTestSpans,
19+
registerInstrumentationTesting,
20+
} from '@opentelemetry/contrib-test-utils';
21+
import { AwsInstrumentation } from '../src';
22+
registerInstrumentationTesting(new AwsInstrumentation());
23+
24+
import * as AWS from 'aws-sdk';
25+
import { AWSError } from 'aws-sdk';
26+
import * as nock from 'nock';
27+
28+
import { SpanKind } from '@opentelemetry/api';
29+
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
30+
import { expect } from 'expect';
31+
import { _AWS_KINESIS_STREAM_NAME } from '../src/utils';
32+
33+
const region = 'us-east-1';
34+
35+
describe('Kinesis', () => {
36+
let kinesis: AWS.Kinesis;
37+
beforeEach(() => {
38+
AWS.config.credentials = {
39+
accessKeyId: 'test key id',
40+
expired: false,
41+
expireTime: new Date(),
42+
secretAccessKey: 'test acc key',
43+
sessionToken: 'test token',
44+
};
45+
});
46+
47+
describe('CreateStream', () => {
48+
it('adds Stream Name', async () => {
49+
kinesis = new AWS.Kinesis({ region: region });
50+
const dummyStreamName = 'dummy-stream-name';
51+
52+
nock(`https://kinesis.${region}.amazonaws.com`)
53+
.get('/')
54+
.reply(200, 'null');
55+
56+
await kinesis
57+
.createStream(
58+
{
59+
StreamName: dummyStreamName,
60+
ShardCount: 3,
61+
},
62+
(err: AWSError) => {
63+
expect(err).toBeFalsy();
64+
}
65+
)
66+
.promise();
67+
68+
const testSpans = getTestSpans();
69+
console.log(testSpans.length);
70+
const creationSpans = testSpans.filter((s: ReadableSpan) => {
71+
return s.name === 'Kinesis.CreateStream';
72+
});
73+
expect(creationSpans.length).toBe(1);
74+
const publishSpan = creationSpans[0];
75+
expect(publishSpan.attributes[_AWS_KINESIS_STREAM_NAME]).toBe(
76+
dummyStreamName
77+
);
78+
expect(publishSpan.kind).toBe(SpanKind.CLIENT);
79+
});
80+
});
81+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {
18+
getTestSpans,
19+
registerInstrumentationTesting,
20+
} from '@opentelemetry/contrib-test-utils';
21+
import { AwsInstrumentation } from '../src';
22+
registerInstrumentationTesting(new AwsInstrumentation());
23+
24+
import * as AWS from 'aws-sdk';
25+
import { AWSError } from 'aws-sdk';
26+
import * as nock from 'nock';
27+
28+
import { SpanKind } from '@opentelemetry/api';
29+
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
30+
import { expect } from 'expect';
31+
import { _AWS_S3_BUCKET } from '../src/utils';
32+
33+
const region = 'us-east-1';
34+
35+
describe('S3', () => {
36+
let s3: AWS.S3;
37+
beforeEach(() => {
38+
AWS.config.credentials = {
39+
accessKeyId: 'test key id',
40+
expired: false,
41+
expireTime: new Date(),
42+
secretAccessKey: 'test acc key',
43+
sessionToken: 'test token',
44+
};
45+
});
46+
47+
describe('ListObjects', () => {
48+
it('adds bucket Name', async () => {
49+
s3 = new AWS.S3({ region: region });
50+
const dummyBucketName = 'dummy-bucket-name';
51+
52+
nock(`https://s3.${region}.amazonaws.com`).get('/').reply(200, 'null');
53+
54+
await s3
55+
.listObjects(
56+
{
57+
Bucket: dummyBucketName,
58+
},
59+
(err: AWSError) => {
60+
expect(err).toBeFalsy();
61+
}
62+
)
63+
.promise();
64+
65+
const testSpans = getTestSpans();
66+
console.log(testSpans.length);
67+
const creationSpans = testSpans.filter((s: ReadableSpan) => {
68+
return s.name === 'S3.ListObjects';
69+
});
70+
expect(creationSpans.length).toBe(1);
71+
const publishSpan = creationSpans[0];
72+
expect(publishSpan.attributes[_AWS_S3_BUCKET]).toBe(dummyBucketName);
73+
expect(publishSpan.kind).toBe(SpanKind.CLIENT);
74+
});
75+
});
76+
});

0 commit comments

Comments
 (0)