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 May 30, 2019
1 parent 20e4cf0 commit f19c2b4
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 8 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.

- Exporter/Stats/Stackdriver: Add support for exemplar
- exporter-stackdriver: Add support the credentials option used for authentication instead of your application default credentials
- Add support for HTTP tags propagation.

## 0.0.13 - 2019-05-20
- Exporter/Stats/Prometheus: Fix missing tags for HTTP metrics
Expand Down
36 changes: 32 additions & 4 deletions packages/opencensus-instrumentation-http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
* limitations under the License.
*/

import {BasePlugin, CanonicalCode, Func, HeaderGetter, HeaderSetter, MessageEventType, Span, SpanKind, TagMap, TagTtl, TraceOptions} from '@opencensus/core';
import {ClientRequest, ClientResponse, IncomingMessage, request, RequestOptions, ServerResponse} from 'http';
import {BasePlugin, CanonicalCode, deserializeTextFormat, Func, HeaderGetter, HeaderSetter, MessageEventType, serializeTextFormat, Span, SpanKind, TagMap, TagTtl, TraceOptions} from '@opencensus/core';
import {ClientRequest, ClientResponse, IncomingHttpHeaders, IncomingMessage, request, RequestOptions, ServerResponse} from 'http';
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 @@ -35,6 +36,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 @@ -216,7 +219,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 @@ -407,7 +410,18 @@ export class HttpPlugin extends BasePlugin {
const userAgent =
headers ? (headers['user-agent'] || headers['User-Agent']) : null;

const tags = new TagMap();
// 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';
Expand Down Expand Up @@ -518,6 +532,20 @@ export class HttpPlugin extends BasePlugin {
}
}

/**
* 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
107 changes: 103 additions & 4 deletions packages/opencensus-instrumentation-http/test/test-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ 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 * as stats from '../src/http-stats';

Expand Down Expand Up @@ -135,10 +134,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}`});

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 @@ -150,11 +157,18 @@ function assertClientStats(

function assertServerStats(
testExporter: TestExporter, httpStatusCode: number, httpMethod: string,
path: 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});

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 @@ -269,7 +283,6 @@ describe('HttpPlugin', () => {
});
}


it('should create a child span for GET requests', () => {
const testPath = '/outgoing/rootSpan/childs/1';
doNock(urlHost, testPath, 200, 'Ok');
Expand All @@ -290,6 +303,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 @@ -449,6 +516,38 @@ 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=' +
Expand Down

0 comments on commit f19c2b4

Please sign in to comment.