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

Commit

Permalink
set Correlation-Context header
Browse files Browse the repository at this point in the history
  • Loading branch information
mayurkale22 committed Jun 7, 2019
1 parent ef5712f commit 5b9283e
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.

## Unreleased

- Add support for HTTP tags propagation.

## 0.0.14 - 2019-06-04
- Exporter/Stats/Stackdriver: Add support for exemplar
Expand Down
37 changes: 34 additions & 3 deletions packages/opencensus-instrumentation-http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ import {
TagMap,
TagTtl,
TraceOptions,
serializeTextFormat,
deserializeTextFormat
} from '@opencensus/core';
import {
ClientRequest,
IncomingHttpHeaders,
IncomingMessage,
request,
RequestOptions,
Expand All @@ -37,6 +40,7 @@ import {
import * as semver from 'semver';
import * as shimmer from 'shimmer';
import * as url from 'url';

import * as stats from './http-stats';
import { HttpPluginConfig, IgnoreMatcher } from './types';

Expand All @@ -56,6 +60,8 @@ const UNLIMITED_PROPAGATION_MD = {
};

const TAG_VALUE_MAX_LENGTH = 255;
/** A correlation context header under which TagMap is stored as a text value */
export const CORRELATION_CONTEXT = 'Correlation-Context';

/** Http instrumentation plugin for Opencensus */
export class HttpPlugin extends BasePlugin {
Expand Down Expand Up @@ -261,7 +267,7 @@ export class HttpPlugin extends BasePlugin {
const host = headers.host || 'localhost';
const userAgent = (headers['user-agent'] ||
headers['User-Agent']) as string;
const tags = new TagMap();
const tags = HttpPlugin.getTagContext(headers) || new TagMap();

rootSpan.addAttribute(
HttpPlugin.ATTRIBUTE_HTTP_HOST,
Expand Down Expand Up @@ -483,8 +489,19 @@ export class HttpPlugin extends BasePlugin {
? headers['user-agent'] || headers['User-Agent']
: null;

const tags = new TagMap();
tags.set(stats.HTTP_CLIENT_METHOD, { value: method });
// record stats: new RPCs on client-side inherit the tag context from
// the current Context.
const tags =
plugin.stats ? plugin.stats.getCurrentTagContext() : new TagMap();
if (tags.tags.size > 0) {
if (plugin.hasExpectHeader(options) && options.headers) {
options.headers[CORRELATION_CONTEXT] = serializeTextFormat(tags);
} else {
request.setHeader(CORRELATION_CONTEXT, serializeTextFormat(tags));
}
}

tags.set(stats.HTTP_CLIENT_METHOD, {value: method});

const host = options.hostname || options.host || 'localhost';
span.addAttribute(HttpPlugin.ATTRIBUTE_HTTP_HOST, host);
Expand Down Expand Up @@ -602,6 +619,20 @@ export class HttpPlugin extends BasePlugin {
} catch (ignore) {}
}

/**
* Returns a TagMap on incoming HTTP header if it exists and is well-formed,
* or null otherwise.
* @param headers The incoming HTTP header object from which TagMap should be
* retrieved.
*/
static getTagContext(headers: IncomingHttpHeaders): TagMap|null {
const contextValue = (headers[CORRELATION_CONTEXT.toLocaleLowerCase()] ||
headers[CORRELATION_CONTEXT]) as string;
// Entry doesn't exist.
if (!contextValue) return null;
return deserializeTextFormat(contextValue);
}

/**
* Returns whether the Expect header is on the given options object.
* @param options Options for http.request.
Expand Down
154 changes: 138 additions & 16 deletions packages/opencensus-instrumentation-http/test/test-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ import * as http from 'http';
import * as nock from 'nock';
import * as shimmer from 'shimmer';
import * as url from 'url';

import { HttpPlugin, plugin } from '../src/';
import {HttpPlugin, plugin} from '../src/';
import * as stats from '../src/http-stats';

function doNock(
Expand Down Expand Up @@ -181,13 +180,18 @@ function assertCustomAttribute(
}

function assertClientStats(
testExporter: TestExporter,
httpStatusCode: number,
httpMethod: string
) {
testExporter: TestExporter, httpStatusCode: number, httpMethod: string,
tagCtx?: TagMap) {
const tags = new TagMap();
tags.set(stats.HTTP_CLIENT_METHOD, { value: httpMethod });
tags.set(stats.HTTP_CLIENT_STATUS, { value: `${httpStatusCode}` });
tags.set(stats.HTTP_CLIENT_METHOD, {value: httpMethod});
tags.set(stats.HTTP_CLIENT_STATUS, {value: `${httpStatusCode}`});

if (tagCtx) {
tagCtx.tags.forEach((tagValue: TagValue, tagKey: TagKey) => {
tags.set(tagKey, tagValue);
});
}

assert.strictEqual(testExporter.registeredViews.length, 8);
assert.strictEqual(testExporter.recordedMeasurements.length, 1);
assert.strictEqual(
Expand All @@ -199,15 +203,19 @@ function assertClientStats(
}

function assertServerStats(
testExporter: TestExporter,
httpStatusCode: number,
httpMethod: string,
path: string
) {
testExporter: TestExporter, httpStatusCode: number, httpMethod: string,
path: string, tagCtx?: TagMap) {
const tags = new TagMap();
tags.set(stats.HTTP_SERVER_METHOD, { value: httpMethod });
tags.set(stats.HTTP_SERVER_STATUS, { value: `${httpStatusCode}` });
tags.set(stats.HTTP_SERVER_ROUTE, { value: path });
tags.set(stats.HTTP_SERVER_METHOD, {value: httpMethod});
tags.set(stats.HTTP_SERVER_STATUS, {value: `${httpStatusCode}`});
tags.set(stats.HTTP_SERVER_ROUTE, {value: path});

if (tagCtx) {
tagCtx.tags.forEach((tagValue: TagValue, tagKey: TagKey) => {
tags.set(tagKey, tagValue);
});
}

assert.strictEqual(testExporter.registeredViews.length, 8);
assert.strictEqual(testExporter.recordedMeasurements.length, 1);
assert.strictEqual(
Expand Down Expand Up @@ -357,6 +365,60 @@ describe('HttpPlugin', () => {
});
});

it('should create a child span for GET requests with tag context', () => {
const testPath = '/outgoing/rootSpan/childs/1';
doNock(urlHost, testPath, 200, 'Ok');
const tags = new TagMap();
tags.set({name: 'testKey1'}, {value: 'value1'});
tags.set({name: 'testKey2'}, {value: 'value2'});
return globalStats.withTagContext(tags, async () => {
return tracer.startRootSpan(
{name: 'TestRootSpan'}, async (root: Span) => {
await httpRequest.get(`${urlHost}${testPath}`).then((result) => {
assert.ok(root.name.indexOf('TestRootSpan') >= 0);
assert.strictEqual(root.spans.length, 1);
const [span] = root.spans;
assert.ok(span.name.indexOf(testPath) >= 0);
assert.strictEqual(root.traceId, span.traceId);
assertSpanAttributes(span, 200, 'GET', hostName, testPath);
assert.strictEqual(span.messageEvents.length, 1);
const [messageEvent] = span.messageEvents;
assert.strictEqual(messageEvent.type, MessageEventType.SENT);
assert.strictEqual(messageEvent.id, 1);
assertClientStats(testExporter, 200, 'GET', tags);
});
});
});
});

it('should create a child span for GET requests with empty tag context',
() => {
const testPath = '/outgoing/rootSpan/childs/1';
doNock(urlHost, testPath, 200, 'Ok');
const tags = new TagMap();
return globalStats.withTagContext(tags, async () => {
return tracer.startRootSpan(
{name: 'TestRootSpan'}, async (root: Span) => {
await httpRequest.get(`${urlHost}${testPath}`)
.then((result) => {
assert.ok(root.name.indexOf('TestRootSpan') >= 0);
assert.strictEqual(root.spans.length, 1);
const [span] = root.spans;
assert.ok(span.name.indexOf(testPath) >= 0);
assert.strictEqual(root.traceId, span.traceId);
assertSpanAttributes(
span, 200, 'GET', hostName, testPath);
assert.strictEqual(span.messageEvents.length, 1);
const [messageEvent] = span.messageEvents;
assert.strictEqual(
messageEvent.type, MessageEventType.SENT);
assert.strictEqual(messageEvent.id, 1);
assertClientStats(testExporter, 200, 'GET');
});
});
});
});

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 @@ -563,6 +625,66 @@ describe('HttpPlugin', () => {
);
});
});
it('should create a root span for incoming requests with Correlation Context header',
async () => {
const testPath = '/incoming/rootSpan/';
const options = {
host: 'localhost',
path: testPath,
port: serverPort,
headers:
{'User-Agent': 'Android', 'Correlation-Context': 'k1=v1,k2=v2'}
};

const expectedTagsFromHeaders = new TagMap();
expectedTagsFromHeaders.set({name: 'k1'}, {value: 'v1'});
expectedTagsFromHeaders.set({name: 'k2'}, {value: 'v2'});

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;
assertSpanAttributes(
span, 200, 'GET', 'localhost', testPath, 'Android');
assertServerStats(
testExporter, 200, 'GET', testPath, expectedTagsFromHeaders);
});
});

it('should handle incoming requests with long request url path',
async () => {
const testPath = '/test&code=' +
'a'.repeat(300);
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.strictEqual(spanVerifier.endedSpans.length, 1);
assert.ok(spanVerifier.endedSpans[0].name.indexOf(testPath) >= 0);
const [span] = spanVerifier.endedSpans;
assertSpanAttributes(
span, 200, 'GET', 'localhost', testPath, 'Android');
assertServerStats(
testExporter, 200, 'GET',
'/test&code=' +
'a'.repeat(244));
});
});

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

0 comments on commit 5b9283e

Please sign in to comment.