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

Commit cdaa308

Browse files
committed
OpenCensus-Propagation-Jaeger: Enforce strictNullChecks and noUnusedLocals
1 parent 234c395 commit cdaa308

File tree

5 files changed

+179
-54
lines changed

5 files changed

+179
-54
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: 47 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,22 @@ 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('0x' + tracerStateHeaderParts[3]);
57+
const sampled = jflags & SAMPLED_VALUE;
58+
const debug = (jflags & DEBUG_VALUE) || (debugId ? SAMPLED_VALUE : 0);
59+
const options = (sampled || debug) ? SAMPLED_VALUE : 0;
6960

70-
return spanContext;
71-
}
72-
return null;
61+
return {traceId, spanId, options};
7362
}
7463

7564
/**
@@ -78,17 +67,23 @@ export class JaegerFormat implements Propagation {
7867
* @param spanContext
7968
*/
8069
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-
}
70+
if (!spanContext || !isValidTraceId(spanContext.traceId) ||
71+
!isValidSpanId(spanContext.spanId)) {
72+
return;
73+
}
8774

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

9489
/**
@@ -99,6 +94,14 @@ export class JaegerFormat implements Propagation {
9994
traceId: uuid.v4().split('-').join(''),
10095
spanId: crypto.randomBytes(8).toString('hex'),
10196
options: SAMPLED_VALUE
102-
} as SpanContext;
97+
};
98+
}
99+
100+
/** Converts a headers type to a string. */
101+
private parseHeader(str: string|string[]|undefined): string|undefined {
102+
if (Array.isArray(str)) {
103+
return str[0];
104+
}
105+
return str;
103106
}
104107
}
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: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@
1616

1717
import {HeaderGetter, HeaderSetter} from '@opencensus/core';
1818
import * as assert from 'assert';
19-
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;
19+
import {JaegerFormat, SAMPLED_VALUE, TRACER_STATE_HEADER_NAME} from '../src/';
2620

2721
const jaegerFormat = new JaegerFormat();
2822

@@ -33,7 +27,7 @@ describe('JaegerPropagation', () => {
3327
// disable-next-line to disable no-any check
3428
// tslint:disable-next-line
3529
const headers = {} as any;
36-
headers[TRACE_ID_HEADER] = `${spanContext.traceId}:${
30+
headers[TRACER_STATE_HEADER_NAME] = `${spanContext.traceId}:${
3731
spanContext.spanId}::${spanContext.options}`;
3832

3933
const getter: HeaderGetter = {
@@ -44,6 +38,37 @@ describe('JaegerPropagation', () => {
4438

4539
assert.deepEqual(jaegerFormat.extract(getter), spanContext);
4640
});
41+
42+
it('should return null when header is undefined', () => {
43+
// tslint:disable-next-line
44+
const headers = {} as any;
45+
headers[TRACER_STATE_HEADER_NAME] = undefined;
46+
47+
const getter: HeaderGetter = {
48+
getHeader(name: string) {
49+
return headers[name];
50+
}
51+
};
52+
53+
assert.deepEqual(jaegerFormat.extract(getter), null);
54+
});
55+
56+
it('should extract data from an array', () => {
57+
const spanContext = jaegerFormat.generate();
58+
// tslint:disable-next-line
59+
const headers = {} as any;
60+
headers[TRACER_STATE_HEADER_NAME] = [
61+
`${spanContext.traceId}:${spanContext.spanId}::${spanContext.options}`
62+
];
63+
64+
const getter: HeaderGetter = {
65+
getHeader(name: string) {
66+
return headers[name];
67+
}
68+
};
69+
70+
assert.deepEqual(jaegerFormat.extract(getter), spanContext);
71+
});
4772
});
4873

4974
describe('inject', () => {
@@ -66,6 +91,29 @@ describe('JaegerPropagation', () => {
6691
jaegerFormat.inject(setter, spanContext);
6792
assert.deepEqual(jaegerFormat.extract(getter), spanContext);
6893
});
94+
95+
it('should not inject empty spancontext', () => {
96+
const emptySpanContext = {
97+
traceId: '',
98+
spanId: '',
99+
options: SAMPLED_VALUE,
100+
};
101+
// tslint:disable-next-line
102+
const headers = {} as any;
103+
const setter: HeaderSetter = {
104+
setHeader(name: string, value: string) {
105+
headers[name] = value;
106+
}
107+
};
108+
const getter: HeaderGetter = {
109+
getHeader(name: string) {
110+
return headers[name];
111+
}
112+
};
113+
114+
jaegerFormat.inject(setter, emptySpanContext);
115+
assert.deepEqual(jaegerFormat.extract(getter), null);
116+
});
69117
});
70118

71119

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)