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

Commit 7f015e7

Browse files
committed
set Correlation-Context header
1 parent 039c695 commit 7f015e7

File tree

3 files changed

+136
-8
lines changed

3 files changed

+136
-8
lines changed

CHANGELOG.md

+1
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
- Exporter/Stats/Stackdriver: Add support for exemplar
88
- exporter-stackdriver: Add support the credentials option used for authentication instead of your application default credentials
9+
- Add support for HTTP tags propagation.
910

1011
## 0.0.13 - 2019-05-20
1112
- Exporter/Stats/Prometheus: Fix missing tags for HTTP metrics

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

+32-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {BasePlugin, CanonicalCode, Func, HeaderGetter, HeaderSetter, MessageEventType, Span, SpanKind, TagMap, TagTtl, TraceOptions} from '@opencensus/core';
18-
import {ClientRequest, ClientResponse, IncomingMessage, request, RequestOptions, ServerResponse} from 'http';
17+
import {BasePlugin, CanonicalCode, deserializeTextFormat, Func, HeaderGetter, HeaderSetter, MessageEventType, serializeTextFormat, Span, SpanKind, TagMap, TagTtl, TraceOptions} from '@opencensus/core';
18+
import {ClientRequest, ClientResponse, IncomingHttpHeaders, IncomingMessage, request, RequestOptions, ServerResponse} from 'http';
1919
import * as semver from 'semver';
2020
import * as shimmer from 'shimmer';
2121
import * as url from 'url';
22+
2223
import * as stats from './http-stats';
2324
import {HttpPluginConfig, IgnoreMatcher} from './types';
2425

@@ -35,6 +36,8 @@ const UNLIMITED_PROPAGATION_MD = {
3536
};
3637

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

3942
/** Http instrumentation plugin for Opencensus */
4043
export class HttpPlugin extends BasePlugin {
@@ -216,7 +219,7 @@ export class HttpPlugin extends BasePlugin {
216219
const host = headers.host || 'localhost';
217220
const userAgent =
218221
(headers['user-agent'] || headers['User-Agent']) as string;
219-
const tags = new TagMap();
222+
const tags = HttpPlugin.getTagContext(headers) || new TagMap();
220223

221224
rootSpan.addAttribute(
222225
HttpPlugin.ATTRIBUTE_HTTP_HOST,
@@ -407,7 +410,18 @@ export class HttpPlugin extends BasePlugin {
407410
const userAgent =
408411
headers ? (headers['user-agent'] || headers['User-Agent']) : null;
409412

410-
const tags = new TagMap();
413+
// record stats: new RPCs on client-side inherit the tag context from
414+
// the current Context.
415+
const tags =
416+
plugin.stats ? plugin.stats.getCurrentTagContext() : new TagMap();
417+
if (tags.tags.size > 0) {
418+
if (plugin.hasExpectHeader(options) && options.headers) {
419+
options.headers[CORRELATION_CONTEXT] = serializeTextFormat(tags);
420+
} else {
421+
request.setHeader(CORRELATION_CONTEXT, serializeTextFormat(tags));
422+
}
423+
}
424+
411425
tags.set(stats.HTTP_CLIENT_METHOD, {value: method});
412426

413427
const host = options.hostname || options.host || 'localhost';
@@ -518,6 +532,20 @@ export class HttpPlugin extends BasePlugin {
518532
}
519533
}
520534

535+
/**
536+
* Returns a TagMap on incoming HTTP header if it exists and is well-formed,
537+
* or null otherwise.
538+
* @param headers The incoming HTTP header object from which TagMap should be
539+
* retrieved.
540+
*/
541+
static getTagContext(headers: IncomingHttpHeaders): TagMap|null {
542+
const contextValue = (headers[CORRELATION_CONTEXT.toLocaleLowerCase()] ||
543+
headers[CORRELATION_CONTEXT]) as string;
544+
// Entry doesn't exist.
545+
if (!contextValue) return null;
546+
return deserializeTextFormat(contextValue);
547+
}
548+
521549
/**
522550
* Returns whether the Expect header is on the given options object.
523551
* @param options Options for http.request.

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

+103-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import * as http from 'http';
2020
import * as nock from 'nock';
2121
import * as shimmer from 'shimmer';
2222
import * as url from 'url';
23-
2423
import {HttpPlugin, plugin} from '../src/';
2524
import * as stats from '../src/http-stats';
2625

@@ -135,10 +134,18 @@ function assertCustomAttribute(
135134
}
136135

137136
function assertClientStats(
138-
testExporter: TestExporter, httpStatusCode: number, httpMethod: string) {
137+
testExporter: TestExporter, httpStatusCode: number, httpMethod: string,
138+
tagCtx?: TagMap) {
139139
const tags = new TagMap();
140140
tags.set(stats.HTTP_CLIENT_METHOD, {value: httpMethod});
141141
tags.set(stats.HTTP_CLIENT_STATUS, {value: `${httpStatusCode}`});
142+
143+
if (tagCtx) {
144+
tagCtx.tags.forEach((tagValue: TagValue, tagKey: TagKey) => {
145+
tags.set(tagKey, tagValue);
146+
});
147+
}
148+
142149
assert.strictEqual(testExporter.registeredViews.length, 8);
143150
assert.strictEqual(testExporter.recordedMeasurements.length, 1);
144151
assert.strictEqual(
@@ -150,11 +157,18 @@ function assertClientStats(
150157

151158
function assertServerStats(
152159
testExporter: TestExporter, httpStatusCode: number, httpMethod: string,
153-
path: string) {
160+
path: string, tagCtx?: TagMap) {
154161
const tags = new TagMap();
155162
tags.set(stats.HTTP_SERVER_METHOD, {value: httpMethod});
156163
tags.set(stats.HTTP_SERVER_STATUS, {value: `${httpStatusCode}`});
157164
tags.set(stats.HTTP_SERVER_ROUTE, {value: path});
165+
166+
if (tagCtx) {
167+
tagCtx.tags.forEach((tagValue: TagValue, tagKey: TagKey) => {
168+
tags.set(tagKey, tagValue);
169+
});
170+
}
171+
158172
assert.strictEqual(testExporter.registeredViews.length, 8);
159173
assert.strictEqual(testExporter.recordedMeasurements.length, 1);
160174
assert.strictEqual(
@@ -269,7 +283,6 @@ describe('HttpPlugin', () => {
269283
});
270284
}
271285

272-
273286
it('should create a child span for GET requests', () => {
274287
const testPath = '/outgoing/rootSpan/childs/1';
275288
doNock(urlHost, testPath, 200, 'Ok');
@@ -290,6 +303,60 @@ describe('HttpPlugin', () => {
290303
});
291304
});
292305

306+
it('should create a child span for GET requests with tag context', () => {
307+
const testPath = '/outgoing/rootSpan/childs/1';
308+
doNock(urlHost, testPath, 200, 'Ok');
309+
const tags = new TagMap();
310+
tags.set({name: 'testKey1'}, {value: 'value1'});
311+
tags.set({name: 'testKey2'}, {value: 'value2'});
312+
return globalStats.withTagContext(tags, async () => {
313+
return tracer.startRootSpan(
314+
{name: 'TestRootSpan'}, async (root: Span) => {
315+
await httpRequest.get(`${urlHost}${testPath}`).then((result) => {
316+
assert.ok(root.name.indexOf('TestRootSpan') >= 0);
317+
assert.strictEqual(root.spans.length, 1);
318+
const [span] = root.spans;
319+
assert.ok(span.name.indexOf(testPath) >= 0);
320+
assert.strictEqual(root.traceId, span.traceId);
321+
assertSpanAttributes(span, 200, 'GET', hostName, testPath);
322+
assert.strictEqual(span.messageEvents.length, 1);
323+
const [messageEvent] = span.messageEvents;
324+
assert.strictEqual(messageEvent.type, MessageEventType.SENT);
325+
assert.strictEqual(messageEvent.id, 1);
326+
assertClientStats(testExporter, 200, 'GET', tags);
327+
});
328+
});
329+
});
330+
});
331+
332+
it('should create a child span for GET requests with empty tag context',
333+
() => {
334+
const testPath = '/outgoing/rootSpan/childs/1';
335+
doNock(urlHost, testPath, 200, 'Ok');
336+
const tags = new TagMap();
337+
return globalStats.withTagContext(tags, async () => {
338+
return tracer.startRootSpan(
339+
{name: 'TestRootSpan'}, async (root: Span) => {
340+
await httpRequest.get(`${urlHost}${testPath}`)
341+
.then((result) => {
342+
assert.ok(root.name.indexOf('TestRootSpan') >= 0);
343+
assert.strictEqual(root.spans.length, 1);
344+
const [span] = root.spans;
345+
assert.ok(span.name.indexOf(testPath) >= 0);
346+
assert.strictEqual(root.traceId, span.traceId);
347+
assertSpanAttributes(
348+
span, 200, 'GET', hostName, testPath);
349+
assert.strictEqual(span.messageEvents.length, 1);
350+
const [messageEvent] = span.messageEvents;
351+
assert.strictEqual(
352+
messageEvent.type, MessageEventType.SENT);
353+
assert.strictEqual(messageEvent.id, 1);
354+
assertClientStats(testExporter, 200, 'GET');
355+
});
356+
});
357+
});
358+
});
359+
293360
for (let i = 0; i < httpErrorCodes.length; i++) {
294361
it(`should test a child spans for GET requests with http error ${
295362
httpErrorCodes[i]}`,
@@ -449,6 +516,38 @@ describe('HttpPlugin', () => {
449516
});
450517
});
451518

519+
it('should create a root span for incoming requests with Correlation Context header',
520+
async () => {
521+
const testPath = '/incoming/rootSpan/';
522+
const options = {
523+
host: 'localhost',
524+
path: testPath,
525+
port: serverPort,
526+
headers:
527+
{'User-Agent': 'Android', 'Correlation-Context': 'k1=v1,k2=v2'}
528+
};
529+
530+
const expectedTagsFromHeaders = new TagMap();
531+
expectedTagsFromHeaders.set({name: 'k1'}, {value: 'v1'});
532+
expectedTagsFromHeaders.set({name: 'k2'}, {value: 'v2'});
533+
534+
shimmer.unwrap(http, 'get');
535+
shimmer.unwrap(http, 'request');
536+
nock.enableNetConnect();
537+
538+
assert.strictEqual(spanVerifier.endedSpans.length, 0);
539+
540+
await httpRequest.get(options).then((result) => {
541+
assert.ok(spanVerifier.endedSpans[0].name.indexOf(testPath) >= 0);
542+
assert.strictEqual(spanVerifier.endedSpans.length, 1);
543+
const [span] = spanVerifier.endedSpans;
544+
assertSpanAttributes(
545+
span, 200, 'GET', 'localhost', testPath, 'Android');
546+
assertServerStats(
547+
testExporter, 200, 'GET', testPath, expectedTagsFromHeaders);
548+
});
549+
});
550+
452551
it('should handle incoming requests with long request url path',
453552
async () => {
454553
const testPath = '/test&code=' +

0 commit comments

Comments
 (0)