1#
2# Licensed to the Apache Software Foundation (ASF) under one
3# or more contributor license agreements. See the NOTICE file
4# distributed with this work for additional information
5# regarding copyright ownership. The ASF licenses this file
6# to you under the Apache License, Version 2.0 (the
7# "License"); you may not use this file except in compliance
8# with the License. You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing,
13# software distributed under the License is distributed on an
14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15# KIND, either express or implied. See the License for the
16# specific language governing permissions and limitations
17# under the License.
18from __future__ import annotations
19
20from typing import TYPE_CHECKING, Any, Protocol
21
22import structlog
23
24if TYPE_CHECKING:
25 from .. import Self
26
27log = structlog.getLogger(__name__)
28
29
30def gen_context(trace_id, span_id):
31 """Generate span context from trace_id and span_id."""
32 from .otel_tracer import gen_context as otel_gen_context
33
34 return otel_gen_context(trace_id, span_id)
35
36
37def gen_links_from_kv_list(list):
38 """Generate links from kv list of {trace_id:int, span_id:int}."""
39 from .otel_tracer import gen_links_from_kv_list
40
41 return gen_links_from_kv_list(list)
42
43
44class EmptyContext:
45 """If no Tracer is configured, EmptyContext is used as a fallback."""
46
47 trace_id = 1
48 span_id = 1
49
50
51class EmptySpan:
52 """If no Tracer is configured, EmptySpan is used as a fallback."""
53
54 def __enter__(self) -> Self:
55 """Enter."""
56 return self
57
58 def __exit__(self, *args, **kwargs):
59 """Exit."""
60 pass
61
62 def __call__(self, obj):
63 """Call."""
64 return obj
65
66 def get_span_context(self):
67 """Get span context."""
68 return EMPTY_CTX
69
70 def set_attribute(self, key, value) -> None:
71 """Set an attribute to the span."""
72 pass
73
74 def set_attributes(self, attributes) -> None:
75 """Set multiple attributes at once."""
76 pass
77
78 def is_recording(self):
79 return False
80
81 def add_event(
82 self,
83 name: str,
84 attributes: Any | None = None,
85 timestamp: int | None = None,
86 ) -> None:
87 """Add event to span."""
88 pass
89
90 def add_link(
91 self,
92 context: Any,
93 attributes: Any | None = None,
94 ) -> None:
95 """Add link to the span."""
96 pass
97
98 def end(self, end_time=None, *args, **kwargs) -> None:
99 """End."""
100 pass
101
102
103EMPTY_SPAN = EmptySpan()
104EMPTY_CTX = EmptyContext()
105
106
107class Tracer(Protocol):
108 """This class is only used for TypeChecking (for IDEs, mypy, etc)."""
109
110 instance: Tracer | EmptyTrace | None = None
111
112 @classmethod
113 def get_tracer(cls, component):
114 """Get a tracer."""
115 raise NotImplementedError()
116
117 @classmethod
118 def start_span(
119 cls,
120 span_name: str,
121 component: str | None = None,
122 parent_sc=None,
123 span_id=None,
124 links=None,
125 start_time=None,
126 ):
127 """Start a span."""
128 raise NotImplementedError()
129
130 @classmethod
131 def use_span(cls, span):
132 """Use a span as current."""
133 raise NotImplementedError()
134
135 @classmethod
136 def get_current_span(self):
137 raise NotImplementedError()
138
139 @classmethod
140 def start_root_span(cls, span_name=None, component=None, start_time=None, start_as_current=True):
141 """Start a root span."""
142 raise NotImplementedError()
143
144 @classmethod
145 def start_child_span(
146 cls,
147 span_name=None,
148 parent_context=None,
149 component=None,
150 links=None,
151 start_time=None,
152 start_as_current=True,
153 ):
154 """Start a child span."""
155 raise NotImplementedError()
156
157 @classmethod
158 def inject(cls) -> dict:
159 """Inject the current span context into a carrier and return it."""
160 raise NotImplementedError()
161
162 @classmethod
163 def extract(cls, carrier) -> EmptyContext:
164 """Extract the span context from a provided carrier."""
165 raise NotImplementedError()
166
167
168class EmptyTrace:
169 """If no Tracer is configured, EmptyTracer is used as a fallback."""
170
171 @classmethod
172 def get_tracer(
173 cls,
174 component: str,
175 trace_id: int | None = None,
176 span_id: int | None = None,
177 ):
178 """Get a tracer using provided node id and trace id."""
179 return cls
180
181 @classmethod
182 def start_span(
183 cls,
184 span_name: str,
185 component: str | None = None,
186 parent_sc=None,
187 span_id=None,
188 links=None,
189 start_time=None,
190 ) -> EmptySpan:
191 """Start a span."""
192 return EMPTY_SPAN
193
194 @classmethod
195 def use_span(cls, span) -> EmptySpan:
196 """Use a span as current."""
197 return EMPTY_SPAN
198
199 @classmethod
200 def get_current_span(self) -> EmptySpan:
201 """Get the current span."""
202 return EMPTY_SPAN
203
204 @classmethod
205 def start_root_span(
206 cls, span_name=None, component=None, start_time=None, start_as_current=True
207 ) -> EmptySpan:
208 """Start a root span."""
209 return EMPTY_SPAN
210
211 @classmethod
212 def start_child_span(
213 cls,
214 span_name=None,
215 parent_context=None,
216 component=None,
217 links=None,
218 start_time=None,
219 start_as_current=True,
220 ) -> EmptySpan:
221 """Start a child span."""
222 return EMPTY_SPAN
223
224 @classmethod
225 def inject(cls):
226 """Inject the current span context into a carrier and return it."""
227 return {}
228
229 @classmethod
230 def extract(cls, carrier) -> EmptyContext:
231 """Extract the span context from a provided carrier."""
232 return EMPTY_CTX