1
1
import opentracing
2
- import time
3
2
import logging
4
- from opentracing .ext import tags
3
+ import time
4
+ from opentracing import tags
5
5
from haystack import HaystackTracer
6
- from haystack import AsyncHttpRecorder
7
6
from haystack import LoggerRecorder
8
- from haystack .text_propagator import TextPropagator
9
- from haystack .propagator import PropagatorOpts
10
7
11
- recorder = LoggerRecorder ()
12
- logging .basicConfig (level = logging .DEBUG )
13
8
9
+ def setup_tracer ():
10
+ global recorder
11
+ recorder = LoggerRecorder ()
14
12
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" })
19
17
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 )
23
20
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 :
24
85
# simulate network transfer delay
25
86
time .sleep (.25 )
26
87
27
88
# 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 :
30
91
parent_scope .span .set_tag (tags .SPAN_KIND , "server" )
31
92
# simulate downstream service doing some work before replying
32
93
time .sleep (1 )
@@ -35,65 +96,38 @@ def act_as_remote_service(headers):
35
96
def make_a_downstream_request ():
36
97
# create a child span representing the downstream request from current span.
37
98
# 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.
39
100
with opentracing .tracer .start_active_span ("downstream_req" ) as child_scope :
40
-
41
101
child_scope .span .set_tag (tags .SPAN_KIND , "client" )
42
102
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 )
51
108
52
109
# process the response from downstream
53
110
time .sleep (.15 )
54
111
55
112
56
- def use_http_recorder ():
57
- endpoint = "http://<replace_me>"
58
- global recorder
59
- recorder = AsyncHttpRecorder (collector_url = endpoint )
60
-
61
-
62
113
def main ():
63
114
"""
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.
74
117
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
+ """
87
120
88
- make_a_downstream_request ()
121
+ # at some point during application init, you'll want to instantiate the global tracer
122
+ setup_tracer ()
89
123
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" )
92
126
93
- logging .info ("done in main" )
127
+ # app shutdown
128
+ logging .info ("done" )
94
129
95
130
96
131
if __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 )
99
133
main ()
0 commit comments