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

Commit 2652f20

Browse files
authored
Propagation-Jaeger: Enforce strictNullChecks and noUnusedLocals (#433)
* OpenCensus-Propagation-Jaeger: Enforce strictNullChecks and noUnusedLocals * fix review comments
1 parent 4eaf539 commit 2652f20

File tree

5 files changed

+177
-60
lines changed

5 files changed

+177
-60
lines changed

packages/opencensus-propagation-jaeger/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# OpenCensus Jaeger Format Propagation for Node.js
1+
# OpenCensus Jaeger Format Propagation
22
[![Gitter chat][gitter-image]][gitter-url]
33

4-
OpenCensus Jaeger Format Propagation sends a span context on the wire in an HTTP request, allowing other services to create spans with the right context.
4+
OpenCensus [Jaeger Format Propagation](https://www.jaegertracing.io/docs/1.10/client-libraries/#propagation-format) sends a span context on the wire in an HTTP request, allowing other services to create spans with the right context.
55

66
This project is still at an early stage of development. It's subject to change.
77

packages/opencensus-propagation-jaeger/src/jaeger-format.ts

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@
1515
*/
1616

1717
import {HeaderGetter, HeaderSetter, Propagation, SpanContext} from '@opencensus/core';
18-
1918
import * as crypto from 'crypto';
2019
import * as uuid from 'uuid';
20+
import {isValidSpanId, isValidTraceId} from './validators';
21+
22+
// TRACER_STATE_HEADER_NAME is the header key used for a span's serialized
23+
// context.
24+
export const TRACER_STATE_HEADER_NAME = 'uber-trace-id';
2125

22-
const TRACE_ID_HEADER = 'uber-trace-id';
23-
const DEBUG_ID_HEADER = 'jaeger-debug-id';
26+
// JAEGER_DEBUG_HEADER is the name of an HTTP header or a TextMap carrier key
27+
// which, if found in the carrier, forces the trace to be sampled as "debug"
28+
// trace.
29+
const JAEGER_DEBUG_HEADER = 'jaeger-debug-id';
2430

2531
const DEBUG_VALUE = 2;
26-
const SAMPLED_VALUE = 1;
32+
export const SAMPLED_VALUE = 1;
2733

2834
/**
2935
* Propagates span context through Jaeger trace-id propagation.
@@ -37,39 +43,26 @@ export class JaegerFormat implements Propagation {
3743
* @param getter
3844
*/
3945
extract(getter: HeaderGetter): SpanContext|null {
40-
if (getter) {
41-
let debug = 0;
42-
if (getter.getHeader(DEBUG_ID_HEADER)) {
43-
debug = SAMPLED_VALUE;
44-
}
45-
46-
const spanContext = {traceId: '', spanId: '', options: debug};
47-
48-
let header = getter.getHeader(TRACE_ID_HEADER);
49-
if (!header) {
50-
return spanContext;
51-
}
52-
if (header instanceof Array) {
53-
header = header[0];
54-
}
55-
const parts = header.split(':');
56-
if (parts.length !== 4) {
57-
return spanContext;
58-
}
59-
60-
spanContext.traceId = parts[0];
61-
spanContext.spanId = parts[1];
62-
63-
const jflags = Number('0x' + parts[3]);
64-
const sampled = jflags & SAMPLED_VALUE;
46+
const debugId = this.parseHeader(getter.getHeader(JAEGER_DEBUG_HEADER));
47+
const tracerStateHeader =
48+
this.parseHeader(getter.getHeader(TRACER_STATE_HEADER_NAME));
6549

66-
debug = (jflags & DEBUG_VALUE) || debug;
50+
if (!tracerStateHeader) return null;
51+
const tracerStateHeaderParts = tracerStateHeader.split(':');
52+
if (tracerStateHeaderParts.length !== 4) return null;
6753

68-
spanContext.options = (sampled || debug) ? SAMPLED_VALUE : 0;
54+
const traceId = tracerStateHeaderParts[0];
55+
const spanId = tracerStateHeaderParts[1];
56+
const jflags = Number(
57+
'0x' +
58+
(isNaN(Number(tracerStateHeaderParts[3])) ?
59+
SAMPLED_VALUE :
60+
Number(tracerStateHeaderParts[3])));
61+
const sampled = jflags & SAMPLED_VALUE;
62+
const debug = (jflags & DEBUG_VALUE) || (debugId ? SAMPLED_VALUE : 0);
63+
const options = (sampled || debug) ? SAMPLED_VALUE : 0;
6964

70-
return spanContext;
71-
}
72-
return null;
65+
return {traceId, spanId, options};
7366
}
7467

7568
/**
@@ -78,17 +71,23 @@ export class JaegerFormat implements Propagation {
7871
* @param spanContext
7972
*/
8073
inject(setter: HeaderSetter, spanContext: SpanContext): void {
81-
if (setter) {
82-
let flags = '0';
83-
if (spanContext.options) {
84-
flags = (spanContext.options & SAMPLED_VALUE ? SAMPLED_VALUE : 0)
85-
.toString(16);
86-
}
74+
if (!spanContext || !isValidTraceId(spanContext.traceId) ||
75+
!isValidSpanId(spanContext.spanId)) {
76+
return;
77+
}
8778

88-
const header =
89-
[spanContext.traceId, spanContext.spanId, '', flags].join(':');
90-
setter.setHeader(TRACE_ID_HEADER, header);
79+
let flags = '0';
80+
if (spanContext.options) {
81+
flags = ((spanContext.options & SAMPLED_VALUE) ? SAMPLED_VALUE : 0)
82+
.toString(16);
9183
}
84+
85+
// {parent-span-id} Deprecated, most Jaeger clients ignore on the receiving
86+
// side, but still include it on the sending side.
87+
const header = [
88+
spanContext.traceId, spanContext.spanId, /** parent-span-id */ '', flags
89+
].join(':');
90+
setter.setHeader(TRACER_STATE_HEADER_NAME, header);
9291
}
9392

9493
/**
@@ -99,6 +98,14 @@ export class JaegerFormat implements Propagation {
9998
traceId: uuid.v4().split('-').join(''),
10099
spanId: crypto.randomBytes(8).toString('hex'),
101100
options: SAMPLED_VALUE
102-
} as SpanContext;
101+
};
102+
}
103+
104+
/** Converts a headers type to a string. */
105+
private parseHeader(str: string|string[]|undefined): string|undefined {
106+
if (Array.isArray(str)) {
107+
return str[0];
108+
}
109+
return str;
103110
}
104111
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Copyright 2019, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
type ValidationFn = (value: string) => boolean;
18+
19+
/**
20+
* Determines if the given hex string is truely a hex value. False if value is
21+
* null.
22+
* @param value
23+
*/
24+
const isHex: ValidationFn = (value: string): boolean => {
25+
return typeof value === 'string' && /^[0-9A-F]*$/i.test(value);
26+
};
27+
28+
/**
29+
* Determines if the given hex string is all zeros. False if value is null.
30+
* @param value
31+
*/
32+
const isNotAllZeros: ValidationFn = (value: string): boolean => {
33+
return typeof value === 'string' && !/^[0]*$/i.test(value);
34+
};
35+
36+
/**
37+
* Determines if the given hex string is of the given length. False if value is
38+
* null.
39+
* @param value
40+
*/
41+
const isLength = (length: number): ValidationFn => {
42+
return (value: string): boolean => {
43+
return typeof value === 'string' && value.length === length;
44+
};
45+
};
46+
47+
/**
48+
* Compose a set of validation functions into a single validation call.
49+
*/
50+
const compose = (...fns: ValidationFn[]): ValidationFn => {
51+
return (value: string) => {
52+
return fns.reduce((isValid, fn) => isValid && fn(value), true);
53+
};
54+
};
55+
56+
/**
57+
* Determines if the given traceId is valid based on section 2.2.2.1 of the
58+
* Trace Context spec.
59+
*/
60+
export const isValidTraceId = compose(isHex, isNotAllZeros, isLength(32));
61+
62+
/**
63+
* Determines if the given spanId is valid based on section 2.2.2.2 of the Trace
64+
* Context spec.
65+
*/
66+
export const isValidSpanId = compose(isHex, isNotAllZeros, isLength(16));
67+
68+
/**
69+
* Determines if the given option is valid based on section 2.2.3 of the Trace
70+
* Context spec.
71+
*/
72+
export const isValidOption = compose(isHex, isLength(2));

packages/opencensus-propagation-jaeger/test/test-jaeger-format.ts

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,56 @@
1616

1717
import {HeaderGetter, HeaderSetter} from '@opencensus/core';
1818
import * as assert from 'assert';
19+
import {JaegerFormat, SAMPLED_VALUE, TRACER_STATE_HEADER_NAME} from '../src/';
1920

20-
import {JaegerFormat} from '../src/';
21-
22-
const TRACE_ID_HEADER = 'uber-trace-id';
23-
24-
const SAMPLED_VALUE = 0x1;
25-
const NOT_SAMPLED_VALUE = 0x0;
21+
function helperGetter(value: string|string[]|undefined) {
22+
const headers: {[key: string]: string|string[]|undefined} = {};
23+
headers[TRACER_STATE_HEADER_NAME] = value;
24+
const getter: HeaderGetter = {
25+
getHeader(name: string) {
26+
return headers[name];
27+
}
28+
};
29+
return getter;
30+
}
2631

2732
const jaegerFormat = new JaegerFormat();
2833

2934
describe('JaegerPropagation', () => {
3035
describe('extract()', () => {
3136
it('should extract context of a sampled span from headers', () => {
3237
const spanContext = jaegerFormat.generate();
33-
// disable-next-line to disable no-any check
34-
// tslint:disable-next-line
35-
const headers = {} as any;
36-
headers[TRACE_ID_HEADER] = `${spanContext.traceId}:${
37-
spanContext.spanId}::${spanContext.options}`;
38+
const getter = helperGetter(`${spanContext.traceId}:${
39+
spanContext.spanId}::${spanContext.options}`);
40+
41+
assert.deepEqual(jaegerFormat.extract(getter), spanContext);
42+
});
43+
44+
it('should return null when header is undefined', () => {
45+
const headers: {[key: string]: string|string[]|undefined} = {};
46+
headers[TRACER_STATE_HEADER_NAME] = undefined;
3847

3948
const getter: HeaderGetter = {
4049
getHeader(name: string) {
4150
return headers[name];
4251
}
4352
};
4453

54+
assert.deepEqual(jaegerFormat.extract(getter), null);
55+
});
56+
57+
it('should extract data from an array', () => {
58+
const spanContext = jaegerFormat.generate();
59+
const getter = helperGetter(`${spanContext.traceId}:${
60+
spanContext.spanId}::${spanContext.options}`);
4561
assert.deepEqual(jaegerFormat.extract(getter), spanContext);
4662
});
4763
});
4864

4965
describe('inject', () => {
5066
it('should inject a context of a sampled span', () => {
5167
const spanContext = jaegerFormat.generate();
52-
// disable-next-line to disable no-any check
53-
// tslint:disable-next-line
54-
const headers = {} as any;
68+
const headers: {[key: string]: string|string[]|undefined} = {};
5569
const setter: HeaderSetter = {
5670
setHeader(name: string, value: string) {
5771
headers[name] = value;
@@ -66,6 +80,28 @@ describe('JaegerPropagation', () => {
6680
jaegerFormat.inject(setter, spanContext);
6781
assert.deepEqual(jaegerFormat.extract(getter), spanContext);
6882
});
83+
84+
it('should not inject empty spancontext', () => {
85+
const emptySpanContext = {
86+
traceId: '',
87+
spanId: '',
88+
options: SAMPLED_VALUE,
89+
};
90+
const headers: {[key: string]: string|string[]|undefined} = {};
91+
const setter: HeaderSetter = {
92+
setHeader(name: string, value: string) {
93+
headers[name] = value;
94+
}
95+
};
96+
const getter: HeaderGetter = {
97+
getHeader(name: string) {
98+
return headers[name];
99+
}
100+
};
101+
102+
jaegerFormat.inject(setter, emptySpanContext);
103+
assert.deepEqual(jaegerFormat.extract(getter), null);
104+
});
69105
});
70106

71107

packages/opencensus-propagation-jaeger/tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"pretty": true,
77
"module": "commonjs",
88
"target": "es6",
9+
"strictNullChecks": true,
10+
"noUnusedLocals": true
911
},
1012
"include": [
1113
"src/**/*.ts",

0 commit comments

Comments
 (0)