1616
1717import { HeaderGetter , HeaderSetter , Propagation , SpanContext } from '@opencensus/core' ;
1818import * as crypto from 'crypto' ;
19- import { isValidOption , isValidSpanId , isValidTraceId , isValidVersion } from './validators' ;
19+ import { isValidSpanId , isValidTraceId , isValidVersion } from './validators' ;
2020
21- // Header names
21+ /** The traceparent header key. */
2222export const TRACE_PARENT = 'traceparent' ;
23+ /** The tracestate header key. */
2324export const TRACE_STATE = 'tracestate' ;
25+ /** The default trace options. This defaults to unsampled. */
2426export const DEFAULT_OPTIONS = 0x0 ;
27+ /** Regular expression that represents a valid traceparent header. */
28+ const TRACE_PARENT_REGEX = / ^ [ \d a - f ] { 2 } - [ \d a - f ] { 32 } - [ \d a - f ] { 16 } - [ \d a - f ] { 2 } $ / ;
29+
30+ /**
31+ * Parses a traceparent header value into a SpanContext object, or null if the
32+ * traceparent value is invalid.
33+ */
34+ function traceParentToSpanContext ( traceParent : string ) : SpanContext | null {
35+ const match = traceParent . match ( TRACE_PARENT_REGEX ) ;
36+ if ( ! match ) return null ;
37+ const parts = traceParent . split ( '-' ) ;
38+ const [ version , traceId , spanId , option ] = parts ;
39+ // tslint:disable-next-line:ban Needed to parse hexadecimal.
40+ const options = parseInt ( option , 16 ) ;
41+
42+ if ( ! isValidVersion ( version ) || ! isValidTraceId ( traceId ) ||
43+ ! isValidSpanId ( spanId ) ) {
44+ return null ;
45+ }
46+ return { traceId, spanId, options} ;
47+ }
48+
49+ /** Converts a headers type to a string. */
50+ function parseHeader ( str : string | string [ ] | undefined ) : string | undefined {
51+ return Array . isArray ( str ) ? str [ 0 ] : str ;
52+ }
2553
2654/**
2755 * Propagates span context through Trace Context format propagation.
2856 *
2957 * Based on the Trace Context specification:
30- * https://w3c.github.io/distributed-tracing/report- trace-context.html
58+ * https://www.w3.org/TR/ trace-context/
3159 *
3260 * Known Limitations:
3361 * - Multiple `tracestate` headers are merged into a single `tracestate`
@@ -37,65 +65,28 @@ export const DEFAULT_OPTIONS = 0x0;
3765export class TraceContextFormat implements Propagation {
3866 /**
3967 * Gets the trace context from a request headers. If there is no trace context
40- * in the headers, or if the parsed `traceId` or `spanId` is invalid, an empty
41- * context is returned.
68+ * in the headers, or if the parsed `traceId` or `spanId` is invalid, null is
69+ * returned.
4270 * @param getter
4371 */
4472 extract ( getter : HeaderGetter ) : SpanContext | null {
45- if ( getter ) {
46- // Construct empty span context that we will fill
47- const spanContext : SpanContext = {
48- traceId : '' ,
49- spanId : '' ,
50- options : DEFAULT_OPTIONS ,
51- traceState : undefined
52- } ;
53-
54- let traceState = getter . getHeader ( TRACE_STATE ) ;
55- if ( Array . isArray ( traceState ) ) {
56- // If more than one `tracestate` header is found, we merge them into a
57- // single header.
58- traceState = traceState . join ( ',' ) ;
59- }
60- spanContext . traceState =
61- typeof traceState === 'string' ? traceState : undefined ;
73+ const traceParentHeader = getter . getHeader ( TRACE_PARENT ) ;
74+ if ( ! traceParentHeader ) return null ;
75+ const traceParent = parseHeader ( traceParentHeader ) ;
76+ if ( ! traceParent ) return null ;
6277
63- // Read headers
64- let traceParent = getter . getHeader ( TRACE_PARENT ) ;
65- if ( Array . isArray ( traceParent ) ) {
66- traceParent = traceParent [ 0 ] ;
67- }
78+ const spanContext = traceParentToSpanContext ( traceParent ) ;
79+ if ( ! spanContext ) return null ;
6880
69- // Parse TraceParent into version, traceId, spanId, and option flags. All
70- // parts of the header should be present or it is considered invalid.
71- const parts = traceParent ? traceParent . split ( '-' ) : [ ] ;
72- if ( parts . length === 4 ) {
73- // Both traceId and spanId must be of valid form for the traceparent
74- // header to be accepted. If either is not valid we simply return the
75- // empty spanContext.
76- const version = parts [ 0 ] ;
77- const traceId = parts [ 1 ] ;
78- const spanId = parts [ 2 ] ;
79- if ( ! isValidVersion ( version ) || ! isValidTraceId ( traceId ) ||
80- ! isValidSpanId ( spanId ) ) {
81- return spanContext ;
82- }
83- spanContext . traceId = traceId ;
84- spanContext . spanId = spanId ;
85-
86- // Validate options. If the options are invalid we simply reset them to
87- // default.
88- let optionsHex = parts [ 3 ] ;
89- if ( ! isValidOption ( optionsHex ) ) {
90- optionsHex = DEFAULT_OPTIONS . toString ( 16 ) ;
91- }
92- const options = Number ( '0x' + optionsHex ) ;
93- spanContext . options = options ;
94- }
95-
96- return spanContext ;
81+ const traceStateHeader = getter . getHeader ( TRACE_STATE ) ;
82+ if ( traceStateHeader ) {
83+ // If more than one `tracestate` header is found, we merge them into a
84+ // single header.
85+ spanContext . traceState = Array . isArray ( traceStateHeader ) ?
86+ traceStateHeader . join ( ',' ) :
87+ traceStateHeader ;
9788 }
98- return null ;
89+ return spanContext ;
9990 }
10091
10192 /**
@@ -104,19 +95,14 @@ export class TraceContextFormat implements Propagation {
10495 * @param spanContext
10596 */
10697 inject ( setter : HeaderSetter , spanContext : SpanContext ) : void {
107- if ( setter && spanContext ) {
108- // Construct traceparent from parts. Make sure the traceId and spanId
109- // contain the proper number of characters.
110- const optionsHex = Buffer . from ( [ spanContext . options ] ) . toString ( 'hex' ) ;
111- const traceIdHex =
112- ( '00000000000000000000000000000000' + spanContext . traceId ) . slice ( - 32 ) ;
113- const spanIdHex = ( '0000000000000000' + spanContext . spanId ) . slice ( - 16 ) ;
114- const traceParent = `00-${ traceIdHex } -${ spanIdHex } -${ optionsHex } ` ;
98+ // Construct traceparent from parts. Make sure the traceId and spanId
99+ // contain the proper number of characters.
100+ const traceParent = `00-${ spanContext . traceId } -${ spanContext . spanId } -0${
101+ ( spanContext . options || DEFAULT_OPTIONS ) . toString ( 16 ) } `;
115102
116- setter . setHeader ( TRACE_PARENT , traceParent ) ;
117- if ( spanContext . traceState ) {
118- setter . setHeader ( TRACE_STATE , spanContext . traceState ) ;
119- }
103+ setter . setHeader ( TRACE_PARENT , traceParent ) ;
104+ if ( spanContext . traceState ) {
105+ setter . setHeader ( TRACE_STATE , spanContext . traceState ) ;
120106 }
121107 }
122108
@@ -130,8 +116,7 @@ export class TraceContextFormat implements Propagation {
130116 return {
131117 traceId : buff . slice ( 0 , 32 ) ,
132118 spanId : buff . slice ( 32 , 48 ) ,
133- options : DEFAULT_OPTIONS ,
134- traceState : undefined
119+ options : DEFAULT_OPTIONS
135120 } ;
136121 }
137122}
0 commit comments