11import opentracing
2- import time
32import logging
4- from opentracing .ext import tags
3+ import time
4+ from opentracing import tags
55from haystack import HaystackTracer
6- from haystack import AsyncHttpRecorder
76from haystack import LoggerRecorder
8- from haystack .text_propagator import TextPropagator
9- from haystack .propagator import PropagatorOpts
107
11- recorder = LoggerRecorder ()
12- logging .basicConfig (level = logging .DEBUG )
138
9+ def setup_tracer ():
10+ global recorder
11+ recorder = LoggerRecorder ()
1412
15- def act_as_remote_service ( headers ):
16- # remote service would have it"s own tracer
17- with HaystackTracer ( "Service-B" , recorder ,) as tracer :
18- opentracing . tracer = tracer
13+ # instantiate a haystack tracer for this service and set a common tag which applies to all traces
14+ tracer = HaystackTracer ( "Service-A" ,
15+ recorder ,
16+ common_tags = { "app.version" : "1234" })
1917
20- # ---ability to use custom propagation headers if needed---
21- # prop_opts = PropagatorOpts("X-Trace-ID", "X-Span-ID", "X-Parent-Span")
22- # opentracing.tracer.register_propagator(opentracing.Format.HTTP_HEADERS, TextPropagator(prop_opts))
18+ # now set the global tracer, so we can reference it with opentracing.tracer anywhere in our app
19+ opentracing .set_global_tracer (tracer )
2320
21+
22+ def handle_request (request_body ):
23+ logging .info (f"handling new request - { request_body } " )
24+
25+ # this next line does a few things.. namely, it starts a new scope (which contains the span) to represent
26+ # the scope of this "work". In this case, it should represent the work involved in processing the entire request
27+ with opentracing .tracer .start_active_span ("a_controller_method" ) as parent_scope :
28+ # once within the context of an active span, there are three different ways to assign additional info or
29+ # or attributes to the span
30+ """
31+ First we'll add some tags to the span
32+ Tags are key:value pairs that enable user-defined annotation of spans in order to query, filter, and
33+ comprehend trace data
34+ Tags have semantic conventions, see https://opentracing.io/specification/conventions/
35+ *tags do NOT propagate to child spans
36+ """
37+ parent_scope .span .set_tag (tags .HTTP_URL , "http://localhost/mocksvc" )
38+ parent_scope .span .set_tag (tags .HTTP_METHOD , "GET" )
39+ parent_scope .span .set_tag (tags .SPAN_KIND , "server" )
40+
41+ """
42+ Next we'll add some baggage to the span.
43+ Baggage carries data across process boundaries.. aka it DOES propagate to child spans
44+ """
45+ parent_scope .span .set_baggage_item ("business_id" , "1234" )
46+
47+ """
48+ Next lets assume you need to authenticate the client making the request
49+ """
50+ with opentracing .tracer .start_active_span ("authenticate" ):
51+ time .sleep (.25 ) # fake doing some authentication work..
52+
53+ """
54+ Finally, we'll add a log event to the request level span.
55+ Logs are key:value pairs that are useful for capturing timed log messages and other
56+ debugging or informational output from the application itself. Logs may be useful for
57+ documenting a specific moment or event within the span (in contrast to tags which
58+ should apply to the span regardless of time).
59+ """
60+ parent_scope .span .log_kv (
61+ {
62+ "some_string_value" : "foobar" ,
63+ "an_int_value" : 42 ,
64+ "a_float_value" : 4.2 ,
65+ "a_bool_value" : True ,
66+ "an_obj_as_value" : {
67+ "ok" : "hmm" ,
68+ "blah" : 4324
69+ }
70+ })
71+
72+ try :
73+ """
74+ Now lets say that as part of processing this request, we need to invoke some downstream service
75+ """
76+ make_a_downstream_request ()
77+ except Exception :
78+ # if that fails, we'll tag the request-scoped span with an error so we have success/fail metrics in haystack
79+ parent_scope .span .set_tag (tags .ERROR , True )
80+
81+
82+ def act_as_remote_service (headers ):
83+ # remote service would have it"s own tracer
84+ with HaystackTracer ("Service-B" , recorder ) as tracer :
2485 # simulate network transfer delay
2586 time .sleep (.25 )
2687
2788 # now as-if this was executing on the remote service, extract the parent span ctx from headers
28- upstream_span_ctx = opentracing . tracer .extract (opentracing .Format .HTTP_HEADERS , headers )
29- with opentracing . tracer .start_active_span ("controller_method" , child_of = upstream_span_ctx ) as parent_scope :
89+ upstream_span_ctx = tracer .extract (opentracing .Format .HTTP_HEADERS , headers )
90+ with tracer .start_active_span ("controller_method" , child_of = upstream_span_ctx ) as parent_scope :
3091 parent_scope .span .set_tag (tags .SPAN_KIND , "server" )
3192 # simulate downstream service doing some work before replying
3293 time .sleep (1 )
@@ -35,65 +96,38 @@ def act_as_remote_service(headers):
3596def make_a_downstream_request ():
3697 # create a child span representing the downstream request from current span.
3798 # Behind the scenes this uses the scope_manger to access the current active
38- # span and create a child of it.
99+ # span (which would be our request-scoped span called "a_controller_method" and create a child of it.
39100 with opentracing .tracer .start_active_span ("downstream_req" ) as child_scope :
40-
41101 child_scope .span .set_tag (tags .SPAN_KIND , "client" )
42102
43- # add some baggage (i.e. something that should propagate across
44- # process boundaries)
45- child_scope .span .set_baggage_item ("baggage-item" , "baggage-item-value" )
46-
47- # carrier here represents http headers
48- carrier = {}
49- opentracing .tracer .inject (child_scope .span .context , opentracing .Format .HTTP_HEADERS , carrier )
50- act_as_remote_service (carrier )
103+ # In order for the downstream client to use this trace as a parent, we must propagate the current span context.
104+ # This is done by calling .inject() on the tracer
105+ headers = {}
106+ opentracing .tracer .inject (child_scope .span .context , opentracing .Format .HTTP_HEADERS , headers )
107+ act_as_remote_service (headers )
51108
52109 # process the response from downstream
53110 time .sleep (.15 )
54111
55112
56- def use_http_recorder ():
57- endpoint = "http://<replace_me>"
58- global recorder
59- recorder = AsyncHttpRecorder (collector_url = endpoint )
60-
61-
62113def main ():
63114 """
64- Represents an application/service
65- """
66- # instantiate a tracer with app version common tag and set it
67- # to opentracing.tracer property
68- opentracing .tracer = HaystackTracer ("Service-A" ,
69- recorder ,
70- common_tags = {"app.version" : "1234" })
71-
72- logging .info ("mock request received" )
73- with opentracing .tracer .start_active_span ("a_controller_method" ) as parent_scope :
115+ This function represents a "parent" application/service.. i.e. the originating
116+ service of our traces in this example.
74117
75- # add a tag, tags are part of a span and do not propagate
76- # (tags have semantic conventions, see https://opentracing.io/specification/conventions/)
77- parent_scope .span .set_tag (tags .HTTP_URL , "http://localhost/mocksvc" )
78- parent_scope .span .set_tag (tags .HTTP_METHOD , "GET" )
79- parent_scope .span .set_tag (tags .SPAN_KIND , "server" )
80-
81- # doing some work.. validation, processing, etc
82- time .sleep (.25 )
83-
84- # tag the span with some information about the processing
85- parent_scope .span .log_kv (
86- {"string" : "foobar" , "int" : 42 , "float" : 4.2 , "bool" : True , "obj" : {"ok" : "hmm" , "blah" : 4324 }})
118+ In this scenario, we're pretending to be a web server.
119+ """
87120
88- make_a_downstream_request ()
121+ # at some point during application init, you'll want to instantiate the global tracer
122+ setup_tracer ()
89123
90- # uncomment this line to tag the span with an error
91- # parent_scope.span.set_tag(tags.ERROR, True )
124+ # here we assume the web framework invokes this method to handle the given request
125+ handle_request ( "hello world" )
92126
93- logging .info ("done in main" )
127+ # app shutdown
128+ logging .info ("done" )
94129
95130
96131if __name__ == "__main__" :
97- # uncomment line below to send traces to haystack collector using http recorder
98- # use_http_recorder()
132+ logging .basicConfig (level = logging .DEBUG )
99133 main ()
0 commit comments