1717import datetime as dt
1818import io
1919import os
20-
20+ import random
2121from fdk import constants
2222from fdk import headers as hs
2323from fdk import log
24+ from collections import namedtuple
2425
2526
2627class InvokeContext (object ):
2728
28- def __init__ (self , app_id , fn_id , call_id ,
29+ def __init__ (self , app_id , app_name , fn_id , fn_name , call_id ,
2930 content_type = "application/octet-stream" ,
3031 deadline = None , config = None ,
3132 headers = None , request_url = None ,
32- method = "POST" , fn_format = None ):
33+ method = "POST" , fn_format = None ,
34+ tracing_context = None ):
3335 """
3436 Request context here to be a placeholder
3537 for request-specific attributes
3638 :param app_id: Fn App ID
3739 :type app_id: str
40+ :param app_name: Fn App name
41+ :type app_name: str
3842 :param fn_id: Fn App Fn ID
3943 :type fn_id: str
44+ :param fn_name: Fn name
45+ :type fn_name: str
4046 :param call_id: Fn call ID
4147 :type call_id: str
4248 :param content_type: request content type
@@ -53,6 +59,8 @@ def __init__(self, app_id, fn_id, call_id,
5359 :type method: str
5460 :param fn_format: function format
5561 :type fn_format: str
62+ :param tracing_context: tracing context
63+ :type tracing_context: TracingContext
5664 """
5765 self .__app_id = app_id
5866 self .__fn_id = fn_id
@@ -66,6 +74,9 @@ def __init__(self, app_id, fn_id, call_id,
6674 self ._method = method
6775 self .__response_headers = {}
6876 self .__fn_format = fn_format
77+ self .__app_name = app_name
78+ self .__fn_name = fn_name
79+ self .__tracing_context = tracing_context if tracing_context else None
6980
7081 log .log ("request headers. gateway: {0} {1}"
7182 .format (self .__is_gateway (), headers ))
@@ -77,9 +88,15 @@ def __init__(self, app_id, fn_id, call_id,
7788 def AppID (self ):
7889 return self .__app_id
7990
91+ def AppName (self ):
92+ return self .__app_name
93+
8094 def FnID (self ):
8195 return self .__fn_id
8296
97+ def FnName (self ):
98+ return self .__fn_name
99+
83100 def CallID (self ):
84101 return self .__call_id
85102
@@ -95,6 +112,9 @@ def HTTPHeaders(self):
95112 def Format (self ):
96113 return self .__fn_format
97114
115+ def TracingContext (self ):
116+ return self .__tracing_context
117+
98118 def Deadline (self ):
99119 if self .__deadline is None :
100120 now = dt .datetime .now (dt .timezone .utc ).astimezone ()
@@ -125,6 +145,114 @@ def __is_gateway(self):
125145 == constants .INTENT_HTTP_REQUEST )
126146
127147
148+ class TracingContext (object ):
149+
150+ def __init__ (self , is_tracing_enabled , trace_collector_url ,
151+ trace_id , span_id , parent_span_id ,
152+ is_sampled , flags ):
153+ """
154+ Tracing context here to be a placeholder
155+ for tracing-specific attributes
156+ :param is_tracing_enabled: tracing enabled flag
157+ :type is_tracing_enabled: bool
158+ :param trace_collector_url: APM Trace Collector Endpoint URL
159+ :type trace_collector_url: str
160+ :param trace_id: Trace ID
161+ :type trace_id: str
162+ :param span_id: Span ID
163+ :type span_id: str
164+ :param parent_span_id: Parent Span ID
165+ :type parent_span_id: str
166+ :param is_sampled: Boolean for emmitting spans
167+ :type is_sampled: int (0 or 1)
168+ :param flags: Debug flags
169+ :type flags: int (0 or 1)
170+ """
171+ self .__is_tracing_enabled = is_tracing_enabled
172+ self .__trace_collector_url = trace_collector_url
173+ self .__trace_id = trace_id
174+ self .__span_id = span_id
175+ self .__parent_span_id = parent_span_id
176+ self .__is_sampled = is_sampled
177+ self .__flags = flags
178+ self .__app_name = os .environ .get (constants .FN_APP_NAME )
179+ self .__app_id = os .environ .get (constants .FN_APP_ID )
180+ self .__fn_name = os .environ .get (constants .FN_NAME )
181+ self .__fn_id = os .environ .get (constants .FN_ID )
182+
183+ def is_tracing_enabled (self ):
184+ return self .__is_tracing_enabled
185+
186+ def trace_collector_url (self ):
187+ return self .__trace_collector_url
188+
189+ def trace_id (self ):
190+ return self .__trace_id
191+
192+ def span_id (self ):
193+ return self .__span_id
194+
195+ def parent_span_id (self ):
196+ return self .__parent_span_id
197+
198+ def is_sampled (self ):
199+ return bool (self .__is_sampled )
200+
201+ def flags (self ):
202+ return self .__flags
203+
204+ # this is a helper method specific for py_zipkin
205+ def zipkin_attrs (self ):
206+ ZipkinAttrs = namedtuple (
207+ "ZipkinAttrs" ,
208+ "trace_id, span_id, parent_span_id, is_sampled, flags"
209+ )
210+
211+ trace_id = self .__trace_id
212+ span_id = self .__span_id
213+ parent_span_id = self .__parent_span_id
214+ is_sampled = bool (self .__is_sampled )
215+ trace_flags = self .__flags
216+
217+ # As the fnLb sends the parent_span_id as the span_id
218+ # assign the parent span id as the span id.
219+ if parent_span_id is None and span_id is not None :
220+ parent_span_id = span_id
221+ span_id = generate_id ()
222+
223+ zipkin_attrs = ZipkinAttrs (
224+ trace_id ,
225+ span_id ,
226+ parent_span_id ,
227+ is_sampled ,
228+ trace_flags
229+ )
230+ return zipkin_attrs
231+
232+ def service_name (self , override = None ):
233+ # in case of missing app and function name env variables
234+ service_name = (
235+ override
236+ if override is not None
237+ else str (self .__app_name ) + "::" + str (self .__fn_name )
238+ )
239+ return service_name .lower ()
240+
241+ def annotations (self ):
242+ annotations = {
243+ "generatedBy" : "faas" ,
244+ "appName" : self .__app_name ,
245+ "appID" : self .__app_id ,
246+ "fnName" : self .__fn_name ,
247+ "fnID" : self .__fn_id ,
248+ }
249+ return annotations
250+
251+
252+ def generate_id ():
253+ return "{:016x}" .format (random .getrandbits (64 ))
254+
255+
128256def context_from_format (format_def : str , ** kwargs ) -> (
129257 InvokeContext , io .BytesIO ):
130258 """
@@ -138,27 +266,76 @@ def context_from_format(format_def: str, **kwargs) -> (
138266
139267 app_id = os .environ .get (constants .FN_APP_ID )
140268 fn_id = os .environ .get (constants .FN_ID )
269+ app_name = os .environ .get (constants .FN_APP_NAME )
270+ fn_name = os .environ .get (constants .FN_NAME )
271+ # the tracing enabled env variable is passed as a "0" or "1" string
272+ # and therefore needs to be converted appropriately.
273+ is_tracing_enabled = os .environ .get (constants .OCI_TRACING_ENABLED )
274+ is_tracing_enabled = (
275+ bool (int (is_tracing_enabled ))
276+ if is_tracing_enabled is not None
277+ else False
278+ )
279+ trace_collector_url = os .environ .get (constants .OCI_TRACE_COLLECTOR_URL )
141280
142281 if format_def == constants .HTTPSTREAM :
143282 data = kwargs .get ("data" )
144283 headers = kwargs .get ("headers" )
145284
285+ # zipkin tracing http headers
286+ trace_id = span_id = parent_span_id = is_sampled = trace_flags = None
287+ tracing_context = None
288+ if is_tracing_enabled :
289+ # we generate the trace_id if tracing is enabled
290+ # but the traceId zipkin header is missing.
291+ trace_id = headers .get (constants .X_B3_TRACEID )
292+ trace_id = generate_id () if trace_id is None else trace_id
293+
294+ span_id = headers .get (constants .X_B3_SPANID )
295+ parent_span_id = headers .get (constants .X_B3_PARENTSPANID )
296+
297+ # span_id is also generated if the zipkin header is missing.
298+ span_id = generate_id () if span_id is None else span_id
299+
300+ # is_sampled should be a boolean in the form of a "0/1" but
301+ # legacy samples have them as "False/True"
302+ is_sampled = headers .get (constants .X_B3_SAMPLED )
303+ is_sampled = int (is_sampled ) if is_sampled is not None else 1
304+
305+ # not currently used but is defined by the zipkin headers standard
306+ trace_flags = headers .get (constants .X_B3_FLAGS )
307+
308+ # tracing context will be an empty object
309+ # if tracing is not enabled or the flag is missing.
310+ # this prevents the customer code from failing if they decide to
311+ # disable tracing. An empty tracing context will not
312+ # emit spans due to is_sampled being None.
313+ tracing_context = TracingContext (
314+ is_tracing_enabled ,
315+ trace_collector_url ,
316+ trace_id ,
317+ span_id ,
318+ parent_span_id ,
319+ is_sampled ,
320+ trace_flags
321+ )
322+
146323 method = headers .get (constants .FN_HTTP_METHOD )
147- request_url = headers .get (
148- constants .FN_HTTP_REQUEST_URL )
324+ request_url = headers .get (constants .FN_HTTP_REQUEST_URL )
149325 deadline = headers .get (constants .FN_DEADLINE )
150326 call_id = headers .get (constants .FN_CALL_ID )
151327 content_type = headers .get (constants .CONTENT_TYPE )
152328
153329 ctx = InvokeContext (
154- app_id , fn_id , call_id ,
330+ app_id , app_name , fn_id , fn_name , call_id ,
155331 content_type = content_type ,
156332 deadline = deadline ,
157333 config = os .environ ,
158334 headers = headers ,
159335 method = method ,
160336 request_url = request_url ,
161337 fn_format = constants .HTTPSTREAM ,
338+ tracing_context = tracing_context ,
162339 )
163340
164341 return ctx , data
0 commit comments