Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit c85ca67

Browse files
mhdawsonmayurkale22
authored andcommitted
Add support for custom attributes (#492)
Add support for custom attributes for http and https spans Fixes: #490
1 parent c30010d commit c85ca67

File tree

6 files changed

+140
-10
lines changed

6 files changed

+140
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
66

77
**This release has a breaking change. Please test your code accordingly after upgrading.**
88

9+
- http-instrumentation: add support for the addition of custom attributes to spans
910
- Remove Span's `startChildSpan(nameOrOptions?: string|SpanOptions, kind?: SpanKind)` interface, now only `SpanOptions` object interface is supported.
1011

1112
## 0.0.12 - 2019-05-13

packages/opencensus-core/src/trace/instrumentation/types.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import {Stats} from '../../stats/types';
18-
import {TracerBase} from '../model/types';
18+
import {Span, TracerBase} from '../model/types';
1919

2020
/** Interface Plugin to apply patch. */
2121
export interface Plugin {
@@ -36,9 +36,19 @@ export interface Plugin {
3636
disable(): void;
3737
}
3838

39+
/**
40+
* Function that can be provided to plugin in order to add custom
41+
* attributes to spans
42+
*/
43+
export interface CustomAttributeFunction {
44+
// tslint:disable-next-line:no-any
45+
(span: Span, ...rest: any[]): void;
46+
}
47+
3948
export type PluginConfig = {
4049
// tslint:disable-next-line:no-any
4150
[key: string]: any;
51+
applyCustomAttributesOnSpan?: CustomAttributeFunction;
4252
};
4353

4454
export type NamedPluginConfig = {

packages/opencensus-instrumentation-http/src/http.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import * as semver from 'semver';
2020
import * as shimmer from 'shimmer';
2121
import * as url from 'url';
2222
import * as stats from './http-stats';
23-
import {IgnoreMatcher} from './types';
23+
import {HttpPluginConfig, IgnoreMatcher} from './types';
2424

2525
export type HttpGetCallback = (res: IncomingMessage) => void;
2626
export type RequestFunction = typeof request;
@@ -52,6 +52,8 @@ export class HttpPlugin extends BasePlugin {
5252
static ATTRIBUTE_HTTP_ERROR_NAME = 'http.error_name';
5353
static ATTRIBUTE_HTTP_ERROR_MESSAGE = 'http.error_message';
5454

55+
options!: HttpPluginConfig;
56+
5557
/** Constructs a new HttpPlugin instance. */
5658
constructor(moduleName: string) {
5759
super(moduleName);
@@ -120,7 +122,7 @@ export class HttpPlugin extends BasePlugin {
120122
* @param list List of ignore patterns
121123
*/
122124
protected isIgnored<T>(
123-
url: string, request: T, list: Array<IgnoreMatcher<T>>): boolean {
125+
url: string, request: T, list?: Array<IgnoreMatcher<T>>): boolean {
124126
if (!list) {
125127
// No ignored urls - trace everything
126128
return false;
@@ -248,6 +250,11 @@ export class HttpPlugin extends BasePlugin {
248250
HttpPlugin.parseResponseStatus(response.statusCode));
249251
rootSpan.addMessageEvent(MessageEventType.RECEIVED, 1);
250252

253+
if (plugin.options.applyCustomAttributesOnSpan) {
254+
plugin.options.applyCustomAttributesOnSpan(
255+
rootSpan, request, response);
256+
}
257+
251258
tags.set(
252259
stats.HTTP_SERVER_METHOD, {value: method},
253260
UNLIMITED_PROPAGATION_MD);
@@ -426,6 +433,10 @@ export class HttpPlugin extends BasePlugin {
426433
}
427434
span.addMessageEvent(MessageEventType.SENT, 1);
428435

436+
if (plugin.options.applyCustomAttributesOnSpan) {
437+
plugin.options.applyCustomAttributesOnSpan(span, request, response);
438+
}
439+
429440
HttpPlugin.recordStats(span.kind, tags, Date.now() - startTime);
430441
span.end();
431442
});

packages/opencensus-instrumentation-http/src/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,19 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {ClientRequest, IncomingMessage} from 'http';
17+
import {CustomAttributeFunction, Span} from '@opencensus/core';
18+
import {ClientRequest, IncomingMessage, ServerResponse} from 'http';
1819

1920
export type IgnoreMatcher<T> =
2021
string|RegExp|((url: string, request: T) => boolean);
2122

23+
export interface HttpCustomAttributeFunction extends CustomAttributeFunction {
24+
(span: Span, request: ClientRequest|IncomingMessage,
25+
response: IncomingMessage|ServerResponse): void;
26+
}
27+
2228
export type HttpPluginConfig = {
2329
ignoreIncomingPaths?: Array<IgnoreMatcher<IncomingMessage>>;
2430
ignoreOutgoingUrls?: Array<IgnoreMatcher<ClientRequest>>;
31+
applyCustomAttributesOnSpan?: HttpCustomAttributeFunction;
2532
};

packages/opencensus-instrumentation-http/test/test-http.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {CoreTracer, globalStats, HeaderGetter, HeaderSetter, logger, Measurement, MessageEventType, Propagation, Span, SpanContext, SpanEventListener, StatsEventListener, TagKey, TagMap, TagValue, View} from '@opencensus/core';
17+
import {CoreTracer, globalStats, HeaderGetter, HeaderSetter, logger, Measurement, MessageEventType, Propagation, Span, SpanContext, SpanEventListener, SpanKind, StatsEventListener, TagKey, TagMap, TagValue, View} from '@opencensus/core';
1818
import * as assert from 'assert';
1919
import * as http from 'http';
2020
import * as nock from 'nock';
@@ -31,6 +31,12 @@ function doNock(
3131
nock(url).get(path).times(i).reply(httpCode, respBody);
3232
}
3333

34+
function customAttributeFunction(
35+
span: Span, request: http.ClientRequest|http.IncomingMessage,
36+
response: http.IncomingMessage|http.ServerResponse): void {
37+
span.addAttribute('span kind', span.kind);
38+
}
39+
3440
class TestExporter implements StatsEventListener {
3541
registeredViews: View[] = [];
3642
recordedMeasurements: Measurement[] = [];
@@ -123,6 +129,11 @@ function assertSpanAttributes(
123129
`${httpStatusCode}`);
124130
}
125131

132+
function assertCustomAttribute(
133+
span: Span, attributeName: string, attributeValue: SpanKind) {
134+
assert.strictEqual(span.attributes[attributeName], attributeValue);
135+
}
136+
126137
function assertClientStats(
127138
testExporter: TestExporter, httpStatusCode: number, httpMethod: string) {
128139
const tags = new TagMap();
@@ -161,8 +172,11 @@ describe('HttpPlugin', () => {
161172
const log = logger.logger();
162173
const tracer = new CoreTracer();
163174
const spanVerifier = new SpanVerifier();
164-
tracer.start(
165-
{samplingRate: 1, logger: log, propagation: new DummyPropagation()});
175+
tracer.start({
176+
samplingRate: 1,
177+
logger: log,
178+
propagation: new DummyPropagation(),
179+
});
166180
const testExporter = new TestExporter();
167181

168182
it('should return a plugin', () => {
@@ -180,7 +194,8 @@ describe('HttpPlugin', () => {
180194
`${urlHost}/ignored/string`,
181195
/^http:\/\/fake\.service\.io\/ignored\/regexp$/,
182196
(url: string) => url === `${urlHost}/ignored/function`
183-
]
197+
],
198+
applyCustomAttributesOnSpan: customAttributeFunction
184199
},
185200
'', globalStats);
186201
tracer.registerSpanEventListener(spanVerifier);
@@ -374,6 +389,19 @@ describe('HttpPlugin', () => {
374389
nock.disableNetConnect();
375390
});
376391

392+
it('custom attributes should show up on client spans', async () => {
393+
nock.enableNetConnect();
394+
assert.strictEqual(spanVerifier.endedSpans.length, 0);
395+
await httpRequest.get(`http://google.fr/`).then((result) => {
396+
assert.strictEqual(spanVerifier.endedSpans.length, 1);
397+
assert.ok(spanVerifier.endedSpans[0].name.indexOf('GET /') >= 0);
398+
399+
const span = spanVerifier.endedSpans[0];
400+
assertCustomAttribute(span, 'span kind', SpanKind.CLIENT);
401+
});
402+
nock.disableNetConnect();
403+
});
404+
377405
it('should create a rootSpan for GET requests and add propagation headers with Expect headers',
378406
async () => {
379407
nock.enableNetConnect();
@@ -450,6 +478,29 @@ describe('HttpPlugin', () => {
450478
});
451479
});
452480

481+
it('custom attributes should show up on server spans', async () => {
482+
const testPath = '/incoming/rootSpan/';
483+
484+
const options = {
485+
host: 'localhost',
486+
path: testPath,
487+
port: serverPort,
488+
headers: {'User-Agent': 'Android'}
489+
};
490+
shimmer.unwrap(http, 'get');
491+
shimmer.unwrap(http, 'request');
492+
nock.enableNetConnect();
493+
494+
assert.strictEqual(spanVerifier.endedSpans.length, 0);
495+
496+
await httpRequest.get(options).then((result) => {
497+
assert.ok(spanVerifier.endedSpans[0].name.indexOf(testPath) >= 0);
498+
assert.strictEqual(spanVerifier.endedSpans.length, 1);
499+
const span = spanVerifier.endedSpans[0];
500+
assertCustomAttribute(span, 'span kind', SpanKind.SERVER);
501+
});
502+
});
503+
453504
for (const ignored of ['string', 'function', 'regexp']) {
454505
it(`should not trace ignored requests with type ${ignored}`, async () => {
455506
const testPath = `/ignored/${ignored}`;

packages/opencensus-instrumentation-https/test/test-https.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {CoreTracer, logger, Span, SpanEventListener} from '@opencensus/core';
17+
import {CoreTracer, logger, Span, SpanEventListener, SpanKind} from '@opencensus/core';
1818
import * as assert from 'assert';
1919
import * as fs from 'fs';
20+
import * as http from 'http';
2021
import * as https from 'https';
2122
import * as nock from 'nock';
2223
import * as shimmer from 'shimmer';
@@ -33,6 +34,12 @@ function doNock(
3334
nock(url).get(path).times(i).reply(httpCode, respBody);
3435
}
3536

37+
function customAttributeFunction(
38+
span: Span, request: http.ClientRequest|http.IncomingMessage,
39+
response: http.IncomingMessage|http.ServerResponse): void {
40+
span.addAttribute('span kind', span.kind);
41+
}
42+
3643
type RequestFunction = typeof https.request|typeof https.get;
3744

3845
const httpRequest = {
@@ -97,6 +104,11 @@ function assertSpanAttributes(
97104
`${httpStatusCode}`);
98105
}
99106

107+
function assertCustomAttribute(
108+
span: Span, attributeName: string, attributeValue: SpanKind) {
109+
assert.strictEqual(span.attributes[attributeName], attributeValue);
110+
}
111+
100112
describe('HttpsPlugin', () => {
101113
const hostName = 'fake.service.io';
102114
const urlHost = `https://${hostName}`;
@@ -123,7 +135,8 @@ describe('HttpsPlugin', () => {
123135
`${urlHost}/ignored/string`,
124136
/^https:\/\/fake\.service\.io\/ignored\/regexp$/,
125137
(url: string) => url === `${urlHost}/ignored/function`
126-
]
138+
],
139+
applyCustomAttributesOnSpan: customAttributeFunction,
127140
},
128141
'');
129142
tracer.registerSpanEventListener(spanVerifier);
@@ -211,6 +224,22 @@ describe('HttpsPlugin', () => {
211224
});
212225
});
213226

227+
it('should create a child span for GET requests', async () => {
228+
const testPath = '/outgoing/rootSpan/childs/1';
229+
doNock(urlHost, testPath, 200, 'Ok');
230+
const options = {name: 'TestRootSpan'};
231+
return tracer.startRootSpan(options, async (root: Span) => {
232+
await requestMethod(`${urlHost}${testPath}`).then((result) => {
233+
assert.ok(root.name.indexOf('TestRootSpan') >= 0);
234+
assert.strictEqual(root.spans.length, 1);
235+
assert.ok(root.spans[0].name.indexOf(testPath) >= 0);
236+
assert.strictEqual(root.traceId, root.spans[0].traceId);
237+
const span = root.spans[0];
238+
assertCustomAttribute(span, 'span kind', SpanKind.CLIENT);
239+
});
240+
});
241+
});
242+
214243
for (let i = 0; i < httpErrorCodes.length; i++) {
215244
it(`should test a child spans for GET requests with http error ${
216245
httpErrorCodes[i]}`,
@@ -320,6 +349,27 @@ describe('HttpsPlugin', () => {
320349
});
321350
});
322351

352+
it('custom attributes should show up on server spans', async () => {
353+
const testPath = '/incoming/rootSpan/';
354+
355+
const options = {
356+
host: 'localhost',
357+
path: testPath,
358+
port: serverPort,
359+
headers: {'User-Agent': 'Android'}
360+
};
361+
nock.enableNetConnect();
362+
363+
assert.strictEqual(spanVerifier.endedSpans.length, 0);
364+
365+
await httpRequest.request(options).then((result) => {
366+
assert.ok(spanVerifier.endedSpans[0].name.indexOf(testPath) >= 0);
367+
assert.strictEqual(spanVerifier.endedSpans.length, 2);
368+
const span = spanVerifier.endedSpans[0];
369+
assertCustomAttribute(span, 'span kind', SpanKind.SERVER);
370+
});
371+
});
372+
323373
for (const ignored of ['string', 'function', 'regexp']) {
324374
it(`should not trace ignored requests with type ${ignored}`, async () => {
325375
const testPath = `/ignored/${ignored}`;

0 commit comments

Comments
 (0)