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

Commit

Permalink
Make Stackdriver propagation module consistent with other propagation…
Browse files Browse the repository at this point in the history
…s interface (#436)

* Make Stackdriver propagation module consistent with other propagations

* fix review comments

1. Use replace instead of split-join
2. Add helperGetter function
  • Loading branch information
mayurkale22 committed Mar 21, 2019
1 parent 9c95aa0 commit d3e2d7c
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 5 deletions.
3 changes: 2 additions & 1 deletion packages/opencensus-propagation-stackdriver/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@opencensus/propagation-stackdriver",
"version": "0.0.9",
"description": "",
"description": "Opencensus propagation package for Stackdriver format.",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"repository": "census-instrumentation/opencensus-node",
Expand Down Expand Up @@ -38,6 +38,7 @@
"typescript": "~3.2.0"
},
"dependencies": {
"@opencensus/core": "^0.0.9",
"hex2dec": "^1.0.1",
"uuid": "^3.2.1"
},
Expand Down
7 changes: 7 additions & 0 deletions packages/opencensus-propagation-stackdriver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

export * from './stackdriver-format';

import * as v1API from './v1';

export interface SpanContext {
Expand Down Expand Up @@ -46,6 +48,11 @@ export interface Propagation {
generate(): SpanContext;
}

/**
* @deprecated since version 0.0.10 - use {@link StackdriverFormat} instead
* All other propagation exports a class constructor, while Stackdriver v1
* propagation exports an implementation of Propagation.
*/
export const v1: Propagation = {
extract: v1API.extract,
inject: v1API.inject,
Expand Down
103 changes: 103 additions & 0 deletions packages/opencensus-propagation-stackdriver/src/stackdriver-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright 2019 OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* This file implements propagation for the Stackdriver Trace v1 Trace Context
* format.
*
* The header specification is:
* "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE"
* Where:
* {TRACE_ID} is a 32-character hexadecimal value representing a 128-bit
* number. It should be unique between your requests, unless you
* intentionally want to bundle the requests together.
* {SPAN_ID} is the decimal representation of the (unsigned) span ID. It
* should be 0 for the first span in your trace. For subsequent requests,
* set SPAN_ID to the span ID of the parent request.
* {TRACE_TRUE} must be 1 to trace request. Specify 0 to not trace the request.
*/

import * as crypto from 'crypto';
import {decToHex, hexToDec} from 'hex2dec';
import * as uuid from 'uuid';
import {HeaderGetter, HeaderSetter, Propagation, SpanContext} from './index';

/** Header that carries span context across Google infrastructure. */
export const TRACE_CONTEXT_HEADER_NAME = 'x-cloud-trace-context';
const SPAN_ID_RANDOM_BYTES = 8;
const TRACE_TRUE = 0x1;

/** Propagates span context through Stackdriver Format propagation. */
export class StackdriverFormat implements Propagation {
/**
* Gets the span context from a request headers. If there is no span context
* in the headers, null is returned.
* @param getter
*/
extract(getter: HeaderGetter): SpanContext|null {
const traceContextHeader = getter.getHeader(TRACE_CONTEXT_HEADER_NAME);
if (typeof traceContextHeader !== 'string') {
return null;
}
const matches =
traceContextHeader.match(/^([0-9a-fA-F]+)(?:\/([0-9]+))(?:;o=(.*))?/);
if (!matches || matches.length !== 4 || matches[0] !== traceContextHeader ||
(matches[2] && isNaN(Number(matches[2])))) {
return null;
}
return {
traceId: matches[1],
// strip 0x prefix from hex output from decToHex, and and pad so it's
// always a length-16 hex string
spanId: `0000000000000000${decToHex(matches[2]).slice(2)}`.slice(-16),
options: isNaN(Number(matches[3])) ? TRACE_TRUE : Number(matches[3])
};
}

/**
* Adds a span context in a request headers.
* @param setter
* @param spanContext
*/
inject(setter: HeaderSetter, spanContext: SpanContext): void {
let header = `${spanContext.traceId}/${hexToDec(spanContext.spanId)}`;
if (spanContext.options) {
header += `;o=${spanContext.options}`;
}

setter.setHeader(TRACE_CONTEXT_HEADER_NAME, header);
}

/** Generate SpanContexts */
generate(): SpanContext {
return {
traceId: uuid.v4().replace(/-/g, ''),
spanId: spanRandomBuffer().toString('hex'),
options: TRACE_TRUE
};
}
}

// Use the faster crypto.randomFillSync when available (Node 7+) falling back to
// using crypto.randomBytes.
// TODO(ofrobots): Use alternate logic for the browser where crypto and Buffer
// are not available.
const spanIdBuffer = Buffer.alloc(SPAN_ID_RANDOM_BYTES);
const randomFillSync = crypto.randomFillSync;
const randomBytes = crypto.randomBytes;
const spanRandomBuffer = randomFillSync ?
() => randomFillSync(spanIdBuffer) :
() => randomBytes(SPAN_ID_RANDOM_BYTES);
1 change: 0 additions & 1 deletion packages/opencensus-propagation-stackdriver/src/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import * as crypto from 'crypto';
import {decToHex, hexToDec} from 'hex2dec';
import * as uuid from 'uuid';

import {HeaderGetter, HeaderSetter, SpanContext} from './index';

const TRACE_CONTEXT_HEADER_NAME = 'x-cloud-trace-context';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* Copyright 2019 OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {HeaderGetter, HeaderSetter} from '@opencensus/core';
import * as assert from 'assert';
import {StackdriverFormat, TRACE_CONTEXT_HEADER_NAME} from '../src';

const stackdriverFormat = new StackdriverFormat();

function helperGetter(value: string|string[]|undefined) {
const headers: {[key: string]: string|string[]|undefined} = {};
headers[TRACE_CONTEXT_HEADER_NAME] = value;
const getter: HeaderGetter = {
getHeader(name: string) {
return headers[name];
}
};
return getter;
}

describe('StackdriverPropagation', () => {
describe('extract()', () => {
it('should extract context of a sampled span from headers', () => {
const getter = helperGetter('123456/667;o=1');
assert.deepEqual(
stackdriverFormat.extract(getter),
{traceId: '123456', spanId: '000000000000029b', options: 1});
});

it('should extract context of a span from headers when TRACE_TRUE set to 0',
() => {
const getter =
helperGetter('123456/123456123456123456123456123456123456;o=0');
assert.deepEqual(
stackdriverFormat.extract(getter),
{traceId: '123456', spanId: 'a89bb45f10f2f240', options: 0});
});

it('should extract context of a span from headers when option is undefined',
() => {
const getter = helperGetter('cafef00d/123');
assert.deepEqual(
stackdriverFormat.extract(getter),
{traceId: 'cafef00d', spanId: '000000000000007b', options: 1});
});

const inputs = [
'', undefined, '123456', '123456;o=1', 'o=1;123456', '123;456;o=1',
'123/o=1;456', '123/abc/o=1', 'cafefood/667;o=1'
];
inputs.forEach(s => {
it(`should reject ${s}`, () => {
const getter = helperGetter(s);
const result = stackdriverFormat.extract(getter);
assert.ok(!result);
});
});
});

describe('inject', () => {
it('should inject a context of a sampled span', () => {
const spanContext = stackdriverFormat.generate();
const headers: {[key: string]: string|string[]|undefined} = {};
const setter: HeaderSetter = {
setHeader(name: string, value: string) {
headers[name] = value;
}
};
const getter: HeaderGetter = {
getHeader(name: string) {
return headers[name];
}
};

stackdriverFormat.inject(setter, spanContext);
assert.deepEqual(stackdriverFormat.extract(getter), spanContext);
});

it('should not inject empty spancontext', () => {
const emptySpanContext = {traceId: '', spanId: ''};
const headers: {[key: string]: string|string[]|undefined} = {};
const setter: HeaderSetter = {
setHeader(name: string, value: string) {
headers[name] = value;
}
};
const getter: HeaderGetter = {
getHeader(name: string) {
return headers[name];
}
};

stackdriverFormat.inject(setter, emptySpanContext);
assert.deepEqual(stackdriverFormat.extract(getter), null);
});
});

describe('generate', () => {
const TIMES = 20;

// Generate some span contexts.
const GENERATED = Array.from({length: TIMES})
.fill(0)
.map(_ => stackdriverFormat.generate());

it('should generate unique traceIds', () => {
const traceIds = GENERATED.map(c => c.traceId);
assert.strictEqual((new Set(traceIds)).size, TIMES);
});

it('should generate unique spanIds', () => {
const spanIds = GENERATED.map(c => c.spanId);
assert.strictEqual((new Set(spanIds)).size, TIMES);
});
});
});
2 changes: 0 additions & 2 deletions packages/opencensus-propagation-stackdriver/test/test-v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
*/

import * as assert from 'assert';
import * as mocha from 'mocha';
import {inspect} from 'util';

import {SpanContext} from '../src/index';
import {extract, generate, inject, parseContextFromHeader, serializeSpanContext} from '../src/v1';

Expand Down
7 changes: 6 additions & 1 deletion packages/opencensus-propagation-stackdriver/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
"extends": "./node_modules/gts/tsconfig-google.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build"
"outDir": "build",
"pretty": true,
"module": "commonjs",
"target": "es6",
"strictNullChecks": true,
"noUnusedLocals": true
},
"include": [
"src/*.ts",
Expand Down

0 comments on commit d3e2d7c

Please sign in to comment.