1# Copyright 2018, OpenCensus Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15 16from opencensus.trace.span_context import INVALID_SPAN_ID, SpanContext 17from opencensus.trace.trace_options import TraceOptions 18 19_STATE_HEADER_KEY = 'b3' 20_TRACE_ID_KEY = 'x-b3-traceid' 21_SPAN_ID_KEY = 'x-b3-spanid' 22_SAMPLED_KEY = 'x-b3-sampled' 23 24 25class B3FormatPropagator(object): 26 """Propagator for the B3 HTTP header format. 27 28 See: https://github.com/openzipkin/b3-propagation 29 """ 30 31 def from_headers(self, headers): 32 """Generate a SpanContext object from B3 propagation headers. 33 34 :type headers: dict 35 :param headers: HTTP request headers. 36 37 :rtype: :class:`~opencensus.trace.span_context.SpanContext` 38 :returns: SpanContext generated from B3 propagation headers. 39 """ 40 if headers is None: 41 return SpanContext(from_header=False) 42 43 trace_id, span_id, sampled = None, None, None 44 45 state = headers.get(_STATE_HEADER_KEY) 46 if state: 47 fields = state.split('-', 4) 48 49 if len(fields) == 1: 50 sampled = fields[0] 51 elif len(fields) == 2: 52 trace_id, span_id = fields 53 elif len(fields) == 3: 54 trace_id, span_id, sampled = fields 55 elif len(fields) == 4: 56 trace_id, span_id, sampled, _parent_span_id = fields 57 else: 58 return SpanContext(from_header=False) 59 else: 60 trace_id = headers.get(_TRACE_ID_KEY) 61 span_id = headers.get(_SPAN_ID_KEY) 62 sampled = headers.get(_SAMPLED_KEY) 63 64 if sampled is not None: 65 # The specification encodes an enabled tracing decision as "1". 66 # In the wild pre-standard implementations might still send "true". 67 # "d" is set in the single header case when debugging is enabled. 68 sampled = sampled.lower() in ('1', 'd', 'true') 69 else: 70 # If there's no incoming sampling decision, it was deferred to us. 71 # Even though we set it to False here, we might still sample 72 # depending on the tracer configuration. 73 sampled = False 74 75 trace_options = TraceOptions() 76 trace_options.set_enabled(sampled) 77 78 # TraceId and SpanId headers both have to exist 79 if not trace_id or not span_id: 80 return SpanContext(trace_options=trace_options) 81 82 # Convert 64-bit trace ids to 128-bit 83 if len(trace_id) == 16: 84 trace_id = '0'*16 + trace_id 85 86 span_context = SpanContext( 87 trace_id=trace_id, 88 span_id=span_id, 89 trace_options=trace_options, 90 from_header=True 91 ) 92 93 return span_context 94 95 def to_headers(self, span_context): 96 """Convert a SpanContext object to B3 propagation headers. 97 98 :type span_context: 99 :class:`~opencensus.trace.span_context.SpanContext` 100 :param span_context: SpanContext object. 101 102 :rtype: dict 103 :returns: B3 propagation headers. 104 """ 105 106 if not span_context.span_id: 107 span_id = INVALID_SPAN_ID 108 else: 109 span_id = span_context.span_id 110 111 sampled = span_context.trace_options.enabled 112 113 return { 114 _TRACE_ID_KEY: span_context.trace_id, 115 _SPAN_ID_KEY: span_id, 116 _SAMPLED_KEY: '1' if sampled else '0' 117 } 118