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

Commit

Permalink
Add support for custom attributes (#492)
Browse files Browse the repository at this point in the history
Add support for custom attributes for http and https spans
Fixes: #490
  • Loading branch information
mhdawson authored and mayurkale22 committed May 17, 2019
1 parent c30010d commit c85ca67
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.

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

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

## 0.0.12 - 2019-05-13
Expand Down
12 changes: 11 additions & 1 deletion packages/opencensus-core/src/trace/instrumentation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import {Stats} from '../../stats/types';
import {TracerBase} from '../model/types';
import {Span, TracerBase} from '../model/types';

/** Interface Plugin to apply patch. */
export interface Plugin {
Expand All @@ -36,9 +36,19 @@ export interface Plugin {
disable(): void;
}

/**
* Function that can be provided to plugin in order to add custom
* attributes to spans
*/
export interface CustomAttributeFunction {
// tslint:disable-next-line:no-any
(span: Span, ...rest: any[]): void;
}

export type PluginConfig = {
// tslint:disable-next-line:no-any
[key: string]: any;
applyCustomAttributesOnSpan?: CustomAttributeFunction;
};

export type NamedPluginConfig = {
Expand Down
15 changes: 13 additions & 2 deletions packages/opencensus-instrumentation-http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as semver from 'semver';
import * as shimmer from 'shimmer';
import * as url from 'url';
import * as stats from './http-stats';
import {IgnoreMatcher} from './types';
import {HttpPluginConfig, IgnoreMatcher} from './types';

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

options!: HttpPluginConfig;

/** Constructs a new HttpPlugin instance. */
constructor(moduleName: string) {
super(moduleName);
Expand Down Expand Up @@ -120,7 +122,7 @@ export class HttpPlugin extends BasePlugin {
* @param list List of ignore patterns
*/
protected isIgnored<T>(
url: string, request: T, list: Array<IgnoreMatcher<T>>): boolean {
url: string, request: T, list?: Array<IgnoreMatcher<T>>): boolean {
if (!list) {
// No ignored urls - trace everything
return false;
Expand Down Expand Up @@ -248,6 +250,11 @@ export class HttpPlugin extends BasePlugin {
HttpPlugin.parseResponseStatus(response.statusCode));
rootSpan.addMessageEvent(MessageEventType.RECEIVED, 1);

if (plugin.options.applyCustomAttributesOnSpan) {
plugin.options.applyCustomAttributesOnSpan(
rootSpan, request, response);
}

tags.set(
stats.HTTP_SERVER_METHOD, {value: method},
UNLIMITED_PROPAGATION_MD);
Expand Down Expand Up @@ -426,6 +433,10 @@ export class HttpPlugin extends BasePlugin {
}
span.addMessageEvent(MessageEventType.SENT, 1);

if (plugin.options.applyCustomAttributesOnSpan) {
plugin.options.applyCustomAttributesOnSpan(span, request, response);
}

HttpPlugin.recordStats(span.kind, tags, Date.now() - startTime);
span.end();
});
Expand Down
9 changes: 8 additions & 1 deletion packages/opencensus-instrumentation-http/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@
* limitations under the License.
*/

import {ClientRequest, IncomingMessage} from 'http';
import {CustomAttributeFunction, Span} from '@opencensus/core';
import {ClientRequest, IncomingMessage, ServerResponse} from 'http';

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

export interface HttpCustomAttributeFunction extends CustomAttributeFunction {
(span: Span, request: ClientRequest|IncomingMessage,
response: IncomingMessage|ServerResponse): void;
}

export type HttpPluginConfig = {
ignoreIncomingPaths?: Array<IgnoreMatcher<IncomingMessage>>;
ignoreOutgoingUrls?: Array<IgnoreMatcher<ClientRequest>>;
applyCustomAttributesOnSpan?: HttpCustomAttributeFunction;
};
59 changes: 55 additions & 4 deletions packages/opencensus-instrumentation-http/test/test-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

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

function customAttributeFunction(
span: Span, request: http.ClientRequest|http.IncomingMessage,
response: http.IncomingMessage|http.ServerResponse): void {
span.addAttribute('span kind', span.kind);
}

class TestExporter implements StatsEventListener {
registeredViews: View[] = [];
recordedMeasurements: Measurement[] = [];
Expand Down Expand Up @@ -123,6 +129,11 @@ function assertSpanAttributes(
`${httpStatusCode}`);
}

function assertCustomAttribute(
span: Span, attributeName: string, attributeValue: SpanKind) {
assert.strictEqual(span.attributes[attributeName], attributeValue);
}

function assertClientStats(
testExporter: TestExporter, httpStatusCode: number, httpMethod: string) {
const tags = new TagMap();
Expand Down Expand Up @@ -161,8 +172,11 @@ describe('HttpPlugin', () => {
const log = logger.logger();
const tracer = new CoreTracer();
const spanVerifier = new SpanVerifier();
tracer.start(
{samplingRate: 1, logger: log, propagation: new DummyPropagation()});
tracer.start({
samplingRate: 1,
logger: log,
propagation: new DummyPropagation(),
});
const testExporter = new TestExporter();

it('should return a plugin', () => {
Expand All @@ -180,7 +194,8 @@ describe('HttpPlugin', () => {
`${urlHost}/ignored/string`,
/^http:\/\/fake\.service\.io\/ignored\/regexp$/,
(url: string) => url === `${urlHost}/ignored/function`
]
],
applyCustomAttributesOnSpan: customAttributeFunction
},
'', globalStats);
tracer.registerSpanEventListener(spanVerifier);
Expand Down Expand Up @@ -374,6 +389,19 @@ describe('HttpPlugin', () => {
nock.disableNetConnect();
});

it('custom attributes should show up on client spans', async () => {
nock.enableNetConnect();
assert.strictEqual(spanVerifier.endedSpans.length, 0);
await httpRequest.get(`http://google.fr/`).then((result) => {
assert.strictEqual(spanVerifier.endedSpans.length, 1);
assert.ok(spanVerifier.endedSpans[0].name.indexOf('GET /') >= 0);

const span = spanVerifier.endedSpans[0];
assertCustomAttribute(span, 'span kind', SpanKind.CLIENT);
});
nock.disableNetConnect();
});

it('should create a rootSpan for GET requests and add propagation headers with Expect headers',
async () => {
nock.enableNetConnect();
Expand Down Expand Up @@ -450,6 +478,29 @@ describe('HttpPlugin', () => {
});
});

it('custom attributes should show up on server spans', async () => {
const testPath = '/incoming/rootSpan/';

const options = {
host: 'localhost',
path: testPath,
port: serverPort,
headers: {'User-Agent': 'Android'}
};
shimmer.unwrap(http, 'get');
shimmer.unwrap(http, 'request');
nock.enableNetConnect();

assert.strictEqual(spanVerifier.endedSpans.length, 0);

await httpRequest.get(options).then((result) => {
assert.ok(spanVerifier.endedSpans[0].name.indexOf(testPath) >= 0);
assert.strictEqual(spanVerifier.endedSpans.length, 1);
const span = spanVerifier.endedSpans[0];
assertCustomAttribute(span, 'span kind', SpanKind.SERVER);
});
});

for (const ignored of ['string', 'function', 'regexp']) {
it(`should not trace ignored requests with type ${ignored}`, async () => {
const testPath = `/ignored/${ignored}`;
Expand Down
54 changes: 52 additions & 2 deletions packages/opencensus-instrumentation-https/test/test-https.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
* limitations under the License.
*/

import {CoreTracer, logger, Span, SpanEventListener} from '@opencensus/core';
import {CoreTracer, logger, Span, SpanEventListener, SpanKind} from '@opencensus/core';
import * as assert from 'assert';
import * as fs from 'fs';
import * as http from 'http';
import * as https from 'https';
import * as nock from 'nock';
import * as shimmer from 'shimmer';
Expand All @@ -33,6 +34,12 @@ function doNock(
nock(url).get(path).times(i).reply(httpCode, respBody);
}

function customAttributeFunction(
span: Span, request: http.ClientRequest|http.IncomingMessage,
response: http.IncomingMessage|http.ServerResponse): void {
span.addAttribute('span kind', span.kind);
}

type RequestFunction = typeof https.request|typeof https.get;

const httpRequest = {
Expand Down Expand Up @@ -97,6 +104,11 @@ function assertSpanAttributes(
`${httpStatusCode}`);
}

function assertCustomAttribute(
span: Span, attributeName: string, attributeValue: SpanKind) {
assert.strictEqual(span.attributes[attributeName], attributeValue);
}

describe('HttpsPlugin', () => {
const hostName = 'fake.service.io';
const urlHost = `https://${hostName}`;
Expand All @@ -123,7 +135,8 @@ describe('HttpsPlugin', () => {
`${urlHost}/ignored/string`,
/^https:\/\/fake\.service\.io\/ignored\/regexp$/,
(url: string) => url === `${urlHost}/ignored/function`
]
],
applyCustomAttributesOnSpan: customAttributeFunction,
},
'');
tracer.registerSpanEventListener(spanVerifier);
Expand Down Expand Up @@ -211,6 +224,22 @@ describe('HttpsPlugin', () => {
});
});

it('should create a child span for GET requests', async () => {
const testPath = '/outgoing/rootSpan/childs/1';
doNock(urlHost, testPath, 200, 'Ok');
const options = {name: 'TestRootSpan'};
return tracer.startRootSpan(options, async (root: Span) => {
await requestMethod(`${urlHost}${testPath}`).then((result) => {
assert.ok(root.name.indexOf('TestRootSpan') >= 0);
assert.strictEqual(root.spans.length, 1);
assert.ok(root.spans[0].name.indexOf(testPath) >= 0);
assert.strictEqual(root.traceId, root.spans[0].traceId);
const span = root.spans[0];
assertCustomAttribute(span, 'span kind', SpanKind.CLIENT);
});
});
});

for (let i = 0; i < httpErrorCodes.length; i++) {
it(`should test a child spans for GET requests with http error ${
httpErrorCodes[i]}`,
Expand Down Expand Up @@ -320,6 +349,27 @@ describe('HttpsPlugin', () => {
});
});

it('custom attributes should show up on server spans', async () => {
const testPath = '/incoming/rootSpan/';

const options = {
host: 'localhost',
path: testPath,
port: serverPort,
headers: {'User-Agent': 'Android'}
};
nock.enableNetConnect();

assert.strictEqual(spanVerifier.endedSpans.length, 0);

await httpRequest.request(options).then((result) => {
assert.ok(spanVerifier.endedSpans[0].name.indexOf(testPath) >= 0);
assert.strictEqual(spanVerifier.endedSpans.length, 2);
const span = spanVerifier.endedSpans[0];
assertCustomAttribute(span, 'span kind', SpanKind.SERVER);
});
});

for (const ignored of ['string', 'function', 'regexp']) {
it(`should not trace ignored requests with type ${ignored}`, async () => {
const testPath = `/ignored/${ignored}`;
Expand Down

0 comments on commit c85ca67

Please sign in to comment.