Skip to content

Commit 254e16b

Browse files
author
Yuri Shkuro
committed
Upgrade Python tutorial to OT v2
1 parent 60e7540 commit 254e16b

File tree

12 files changed

+90
-133
lines changed

12 files changed

+90
-133
lines changed

python/lesson02/README.md

+14-24
Original file line numberDiff line numberDiff line change
@@ -121,52 +121,42 @@ If we find this trace in the UI, it will show a proper parent-child relationship
121121

122122
You may have noticed one unpleasant side effect of our recent changes - we had to pass the Span object
123123
as the first argument to each function. Unlike Go, languages like Java and Python support thread-local
124-
storage, which is convenient for storing such request-scoped data like the current span. Unfortunately,
125-
unlike in Java, the OpenTracing API for Python currently does not expose a standard mechanism of accessing
126-
such thread-local storage (or its equivalents in async frameworks like `tornado` or `gevent`). The new
127-
API is curently a work in progress and should be available soon.
124+
storage, which is convenient for storing such request-scoped data like the current span. As of v2 the OpenTracing API for Python supports a standard mechanism for passing and accessing the current span using the concepts of Scope Manager and Scope, which are using either plain thread-local storage or more specific mechanisms provided by various async frameworks like `tornado` or `gevent`.
128125

129-
For the purpose of this tutorial, we will use an alternative API available in module `opentracing_instrumentation`,
130-
which is already included in the `requirements.txt`. This modue provides an in-memory context propagation
131-
facility suitable for multi-threaded apps and Tornado apps, via its `request_context` submodule.
132-
133-
When we create the top level `root_span`, we can store it in the context like this:
126+
Instead of calling `start_span` on the tracer, we can call `start_active_span` that invokes that mechanism and makes the span "active" and accessible via `tracer.active_span`. The return value of the `start_active_span` is a `Scope` object that needs to be closed in order to restore the previously active span on the stack.
134127

135128
```python
136-
from opentracing_instrumentation.request_context import get_current_span, span_in_context
137-
138129
def say_hello(hello_to):
139-
with tracer.start_span('say-hello') as span:
140-
span.set_tag('hello-to', hello_to)
141-
with span_in_context(span):
142-
hello_str = format_string(hello_to)
143-
print_hello(hello_str)
130+
with tracer.start_active_span('say-hello') as scope:
131+
scope.span.set_tag('hello-to', hello_to)
132+
hello_str = format_string(hello_to)
133+
print_hello(hello_str)
144134
```
145135

146136
Notice that we're no longer passing the span as argument to the functions, because they can now
147-
retrieve the span with `get_current_span` function:
137+
retrieve the span with `tracer.active_span` function. However, because creating a child span of a currently active span is such a common pattern, this now happens automatically, so that we do not need to pass the `child_of` reference to the parent span anymore:
148138

149139
```python
150140
def format_string(hello_to):
151-
root_span = get_current_span()
152-
with tracer.start_span('format', child_of=root_span) as span:
141+
with tracer.start_active_span('format') as scope:
153142
hello_str = 'Hello, %s!' % hello_to
154-
span.log_kv({'event': 'string-format', 'value': hello_str})
143+
scope.span.log_kv({'event': 'string-format', 'value': hello_str})
155144
return hello_str
156145

157146
def print_hello(hello_str):
158-
root_span = get_current_span()
159-
with tracer.start_span('println', child_of=root_span) as span:
147+
with tracer.start_active_span('println') as scope:
160148
print(hello_str)
161-
span.log_kv({'event': 'println'})
149+
scope.span.log_kv({'event': 'println'})
162150
```
163151

152+
Note that because `start_active_span` function returns a `Scope` instead of a `Span`, we use `scope.span` property to access the span when we want to annotate it with tags or logs. We could also use `tracer.active_span` property with the same effect.
153+
164154
If we run this modified program, we will see that all three reported spans still have the same trace ID.
165155

166156
### What's the Big Deal?
167157

168158
The last change we made may not seem particularly useful. But imagine that your program is
169-
much larger with many functions calling each other. By using the `request_context` mechanism we can access
159+
much larger with many functions calling each other. By using the Scope Manager mechanism we can access
170160
the current span from any place in the program without having to pass the span object as the argument to
171161
all the function calls. This is especially useful if we are using instrumented RPC frameworks that perform
172162
tracing functions automatically - they have a stable way of finding the current span.

python/lesson02/solution/hello.py

+8-13
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
11
import sys
22
import time
33
from lib.tracing import init_tracer
4-
from opentracing_instrumentation.request_context import get_current_span, span_in_context
5-
64

75
def say_hello(hello_to):
8-
with tracer.start_span('say-hello') as span:
9-
span.set_tag('hello-to', hello_to)
10-
with span_in_context(span):
11-
hello_str = format_string(hello_to)
12-
print_hello(hello_str)
6+
with tracer.start_active_span('say-hello') as scope:
7+
scope.span.set_tag('hello-to', hello_to)
8+
hello_str = format_string(hello_to)
9+
print_hello(hello_str)
1310

1411
def format_string(hello_to):
15-
root_span = get_current_span()
16-
with tracer.start_span('format', child_of=root_span) as span:
12+
with tracer.start_active_span('format') as scope:
1713
hello_str = 'Hello, %s!' % hello_to
18-
span.log_kv({'event': 'string-format', 'value': hello_str})
14+
scope.span.log_kv({'event': 'string-format', 'value': hello_str})
1915
return hello_str
2016

2117
def print_hello(hello_str):
22-
root_span = get_current_span()
23-
with tracer.start_span('println', child_of=root_span) as span:
18+
with tracer.start_active_span('println') as scope:
2419
print(hello_str)
25-
span.log_kv({'event': 'println'})
20+
scope.span.log_kv({'event': 'println'})
2621

2722
# main
2823
assert len(sys.argv) == 2

python/lesson03/README.md

+9-13
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,14 @@ The tracing instrumentation uses `inject` and `extract` to pass the span context
9191

9292
### Instrumenting the Client
9393

94-
In the `format_string` function we already create a child span. In order to pass its context over the HTTP
95-
request we need to call `tracer.inject` before building the HTTP request. But since we're building the HTTP
96-
requerst inside a helper function `http_get`, we need to make sure that function has access to the new
97-
child span we started. We can use `span_in_context()` again:
94+
In the `format_string` function we already create a child span. Since we call `tracer.start_active_span()`, this child span will be available inside the helper function `http_get`.
9895

9996
```python
10097
def format_string(hello_to):
101-
with tracer.start_span('format', child_of=get_current_span()) as span:
102-
with span_in_context(span):
103-
hello_str = http_get(8081, 'format', 'helloTo', hello_to)
104-
span.log_kv({'event': 'string-format', 'value': hello_str})
105-
return hello_str
98+
with tracer.start_active_span('format') as scope:
99+
hello_str = http_get(8081, 'format', 'helloTo', hello_to)
100+
scope.span.log_kv({'event': 'string-format', 'value': hello_str})
101+
return hello_str
106102
```
107103

108104
Now let's change `http_get` function to actually inject the span into HTTP headers using `headers` dictionary:
@@ -111,7 +107,7 @@ Now let's change `http_get` function to actually inject the span into HTTP heade
111107
def http_get(port, path, param, value):
112108
url = 'http://localhost:%s/%s' % (port, path)
113109

114-
span = get_current_span()
110+
span = tracer.active_span
115111
span.set_tag(tags.HTTP_METHOD, 'GET')
116112
span.set_tag(tags.HTTP_URL, url)
117113
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
@@ -167,12 +163,12 @@ and tagging the span as `span.kind=server`.
167163
def format():
168164
span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers)
169165
span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
170-
with tracer.start_span('format', child_of=span_ctx, tags=span_tags):
166+
with tracer.start_active_span('format', child_of=span_ctx, tags=span_tags):
171167
hello_to = request.args.get('helloTo')
172168
return 'Hello, %s!' % hello_to
173169
```
174170

175-
Make a similar change in `publisher.py`.
171+
Make a similar change in `publisher.py`. Note that we are still using `start_active_span` for consistency. In this example it does not make much of a difference, but if the logic in the formatter and publisher was more involved, we could benefit from propagating the span through thread locals.
176172

177173
### Take It For a Spin
178174

@@ -215,7 +211,7 @@ Reporting span f9c08d60e0dacb08:a9df5f5c83588207:f9c08d60e0dacb08:1 publisher.pu
215211

216212
Note how all recorded spans show the same trace ID `f9c08d60e0dacb08`. This is a sign
217213
of correct instrumentation. It is also a very useful debugging approach when something
218-
is wrong with tracing. A typical error is to miss the context propagation somwehere,
214+
is wrong with tracing. A typical error is to miss the context propagation somewhere,
219215
either in-process or inter-process, which results in different trace IDs and broken
220216
traces.
221217

python/lesson03/exercise/hello.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,24 @@
22
import sys
33
import time
44
from lib.tracing import init_tracer
5-
from opentracing_instrumentation.request_context import get_current_span, span_in_context
65

76

87
def say_hello(hello_to):
9-
with tracer.start_span('say-hello') as span:
10-
span.set_tag('hello-to', hello_to)
11-
with span_in_context(span):
12-
hello_str = format_string(hello_to)
13-
print_hello(hello_str)
8+
with tracer.start_active_span('say-hello') as scope:
9+
scope.span.set_tag('hello-to', hello_to)
10+
hello_str = format_string(hello_to)
11+
print_hello(hello_str)
1412

1513
def format_string(hello_to):
16-
with tracer.start_span('format', child_of=get_current_span()) as span:
14+
with tracer.start_active_span('format') as scope:
1715
hello_str = http_get(8081, 'format', 'helloTo', hello_to)
18-
span.log_kv({'event': 'string-format', 'value': hello_str})
16+
scope.span.log_kv({'event': 'string-format', 'value': hello_str})
1917
return hello_str
2018

2119
def print_hello(hello_str):
22-
with tracer.start_span('println', child_of=get_current_span()) as span:
20+
with tracer.start_active_span('println') as scope:
2321
http_get(8082, 'publish', 'helloStr', hello_str)
24-
span.log_kv({'event': 'println'})
22+
scope.span.log_kv({'event': 'println'})
2523

2624
def http_get(port, path, param, value):
2725
url = 'http://localhost:%s/%s' % (port, path)

python/lesson03/solution/formatter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
def format():
1212
span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers)
1313
span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
14-
with tracer.start_span('format', child_of=span_ctx, tags=span_tags):
14+
with tracer.start_active_span('format', child_of=span_ctx, tags=span_tags):
1515
hello_to = request.args.get('helloTo')
1616
return 'Hello, %s!' % hello_to
1717

python/lesson03/solution/hello.py

+12-16
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,31 @@
22
import sys
33
import time
44
from lib.tracing import init_tracer
5-
from opentracing_instrumentation.request_context import get_current_span, span_in_context
65
from opentracing.ext import tags
76
from opentracing.propagation import Format
87

98

109
def say_hello(hello_to):
11-
with tracer.start_span('say-hello') as span:
12-
span.set_tag('hello-to', hello_to)
13-
with span_in_context(span):
14-
hello_str = format_string(hello_to)
15-
print_hello(hello_str)
10+
with tracer.start_active_span('say-hello') as scope:
11+
scope.span.set_tag('hello-to', hello_to)
12+
hello_str = format_string(hello_to)
13+
print_hello(hello_str)
1614

1715
def format_string(hello_to):
18-
with tracer.start_span('formatString', child_of=get_current_span()) as span:
19-
with span_in_context(span):
20-
hello_str = http_get(8081, 'format', 'helloTo', hello_to)
21-
span.log_kv({'event': 'string-format', 'value': hello_str})
22-
return hello_str
16+
with tracer.start_active_span('format') as scope:
17+
hello_str = http_get(8081, 'format', 'helloTo', hello_to)
18+
scope.span.log_kv({'event': 'string-format', 'value': hello_str})
19+
return hello_str
2320

2421
def print_hello(hello_str):
25-
with tracer.start_span('printHello', child_of=get_current_span()) as span:
26-
with span_in_context(span):
27-
http_get(8082, 'publish', 'helloStr', hello_str)
28-
span.log_kv({'event': 'println'})
22+
with tracer.start_active_span('println') as scope:
23+
http_get(8082, 'publish', 'helloStr', hello_str)
24+
scope.span.log_kv({'event': 'println'})
2925

3026
def http_get(port, path, param, value):
3127
url = 'http://localhost:%s/%s' % (port, path)
3228

33-
span = get_current_span()
29+
span = tracer.active_span
3430
span.set_tag(tags.HTTP_METHOD, 'GET')
3531
span.set_tag(tags.HTTP_URL, url)
3632
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)

python/lesson03/solution/publisher.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
def publish():
1212
span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers)
1313
span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
14-
with tracer.start_span('publish', child_of=span_ctx, tags=span_tags):
14+
with tracer.start_active_span('publish', child_of=span_ctx, tags=span_tags):
1515
hello_str = request.args.get('helloStr')
1616
print(hello_str)
1717
return 'published'

python/lesson04/README.md

+14-32
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,16 @@
77

88
### Walkthrough
99

10-
In Lesson 3 we have seen how span context is propagated over the wire between different applications.
11-
It is not hard to see that this process can be generalized to passing more than just the tracing context.
12-
With OpenTracing instrumentation in place, we can support general purpose _distributed context propagation_
13-
where we associate some metadata with the transaction and make that metadata available anywhere in the
14-
distributed call graph. In OpenTracing this metadata is called _baggage_, to highlight the fact that
15-
it is carried over in-band with all RPC requests, just like baggage.
10+
In Lesson 3 we have seen how span context is propagated over the wire between different applications. It is not hard to see that this process can be generalized to passing more than just the tracing context. With OpenTracing instrumentation in place, we can support general purpose _distributed context propagation_ where we associate some metadata with the transaction and make that metadata available anywhere in the distributed call graph. In OpenTracing this metadata is called _baggage_, to highlight the fact that it is carried over in-band with all RPC requests, just like baggage.
1611

17-
To see how it works in OpenTracing, let's take the application we built in Lesson 3. You can copy the source
18-
code from [../lesson03/solution](../lesson03/solution) package:
12+
To see how it works in OpenTracing, let's take the application we built in Lesson 3. You can copy the source code from [../lesson03/solution](../lesson03/solution) package:
1913

2014
```
2115
mkdir lesson04/exercise
2216
cp -r lesson03/solution/*py lesson04/exercise/
2317
```
2418

25-
The `formatter` service takes the `helloTo` parameter and returns a string `Hello, {helloTo}!`. Let's modify
26-
it so that we can customize the greeting too, but without modifying the public API of that service.
19+
The `formatter` service takes the `helloTo` parameter and returns a string `Hello, {helloTo}!`. Let's modify it so that we can customize the greeting too, but without modifying the public API of that service.
2720

2821
### Set Baggage in the Client
2922

@@ -43,23 +36,22 @@ And update `sayHello`:
4336

4437
```python
4538
def say_hello(hello_to, greeting):
46-
with tracer.start_span('say-hello') as span:
47-
span.set_tag('hello-to', hello_to)
48-
span.set_baggage_item('greeting', greeting)
49-
with span_in_context(span):
50-
hello_str = format_string(hello_to)
51-
print_hello(hello_str)
39+
with tracer.start_active_span('say-hello') as scope:
40+
scope.span.set_tag('hello-to', hello_to)
41+
scope.span.set_baggage_item('greeting', greeting)
42+
hello_str = format_string(hello_to)
43+
print_hello(hello_str)
5244
```
5345

54-
By doing this we read a second command line argument as a "greeting" and store it in the baggage under `'greeting'` key.
46+
By doing this we read a second command line argument as a "greeting" and store it in the baggage under the `'greeting'` key.
5547

5648
### Read Baggage in Formatter
5749

5850
Change the following code in the `formatter`'s HTTP handler:
5951

6052
```python
61-
with tracer.start_span('format', child_of=span_ctx, tags=span_tags) as span:
62-
greeting = span.get_baggage_item('greeting')
53+
with tracer.start_active_span('format', child_of=span_ctx, tags=span_tags) as scope:
54+
greeting = scope.span.get_baggage_item('greeting')
6355
if not greeting:
6456
greeting = 'Hello'
6557
hello_to = request.args.get('helloTo')
@@ -68,8 +60,7 @@ with tracer.start_span('format', child_of=span_ctx, tags=span_tags) as span:
6860

6961
### Run it
7062

71-
As in Lesson 3, first start the `formatter` and `publisher` in separate terminals, then run the client
72-
with two arguments, e.g. `Bryan Bonjour`. The `publisher` should print `Bonjour, Bryan!`.
63+
As in Lesson 3, first start the `formatter` and `publisher` in separate terminals, then run the client with two arguments, e.g. `Bryan Bonjour`. The `publisher` should print `Bonjour, Bryan!`.
7364

7465
```
7566
# client
@@ -107,13 +98,7 @@ Reporting span 1f5b4b5b21ea181d:214e6b2fb3400125:1f5b4b5b21ea181d:1 publisher.pu
10798

10899
### What's the Big Deal?
109100

110-
We may ask - so what, we could've done the same thing by passing the `greeting` as an HTTP request parameter.
111-
However, that is exactly the point of this exercise - we did not have to change any APIs on the path from
112-
the root span in `hello.py` all the way to the server-side span in `formatter`, three levels down.
113-
If we had a much larger application with much deeper call tree, say the `formatter` was 10 levels down,
114-
the exact code changes we made here would have worked, despite 8 more services being in the path.
115-
If changing the API was the only way to pass the data, we would have needed to modify 8 more services
116-
to get the same effect.
101+
We may ask - so what, we could've done the same thing by passing the `greeting` as an HTTP request parameter. However, that is exactly the point of this exercise - we did not have to change any APIs on the path from the root span in `hello.py` all the way to the server-side span in `formatter`, three levels down. If we had a much larger application with much deeper call tree, say the `formatter` was 10 levels down, the exact code changes we made here would have worked, despite 8 more services being in the path. If changing the API was the only way to pass the data, we would have needed to modify 8 more services to get the same effect.
117102

118103
Some of the possible applications of baggage include:
119104

@@ -125,10 +110,7 @@ Some of the possible applications of baggage include:
125110

126111
### Now, a Warning... NOW a Warning?
127112

128-
Of course, while baggage is an extermely powerful mechanism, it is also dangerous. If we store a 1Mb value/string
129-
in baggage, every request in the call graph below that point will have to carry that 1Mb of data. So baggage
130-
must be used with caution. In fact, Jaeger client libraries implement centrally controlled baggage restrictions,
131-
so that only blessed services can put blessed keys in the baggage, with possible restrictions on the value length.
113+
Of course, while baggage is an extremely powerful mechanism, it is also dangerous. If we store a 1Mb value/string in baggage, every request in the call graph below that point will have to carry that 1Mb of data. So baggage must be used with caution. In fact, Jaeger client libraries implement centrally controlled baggage restrictions, so that only blessed services can put blessed keys in the baggage, with possible restrictions on the value length.
132114

133115
## Conclusion
134116

python/lesson04/solution/formatter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
def format():
1212
span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers)
1313
span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
14-
with tracer.start_span('format', child_of=span_ctx, tags=span_tags) as span:
15-
greeting = span.get_baggage_item('greeting')
14+
with tracer.start_active_span('format', child_of=span_ctx, tags=span_tags) as scope:
15+
greeting = scope.span.get_baggage_item('greeting')
1616
if not greeting:
1717
greeting = 'Hello'
1818
hello_to = request.args.get('helloTo')

0 commit comments

Comments
 (0)