lab
This procedure is part of a lab that teaches you how to instrument your application with OpenTelemetry.
Each procedure in the lab builds upon the last, so make sure you've completed the last procedure, Set up your lab environment, before starting this one.
Your Python application is running and getting a lot of traffic, so it's time to instrument it. Using OpenTelemetry provides freedom for you to choose your preferred observability platform—New Relic, of course. Also, because it's open source and supported by many actors in the observability space, you can trust its development and authority as a standard, knowing that it will be broadly supported into the future.
Here, you manually instrument your application to generate traces with the OpenTelemetry API. Then, you configure the SDK to send them to New Relic so you can analyze the results later.
Instrument your application
In the terminal window that's running your simulator, press <CTRL-C>
.
You should see your simulator shut down. Now you can add some dependencies and update your app logic.
Install the OpenTelemetry SDK and supporting packages into your virtual environment:
$pip install opentelemetry-api$pip install opentelemetry-sdk$pip install opentelemetry-exporter-otlp-proto-grpc
Now that you've installed your dependencies, you need to use those dependencies to instrument your application.
Instrumenting your application begins with a tracer provider. A tracer provider is used for holding configurations and for building tracers. Tracers are then used for creating spans. Spans collect information about an operation or process.
In db.py, create a tracer provider:
1import logging2from opentelemetry import trace3from opentelemetry.sdk.resources import Resource4from opentelemetry.sdk.trace import TracerProvider5
6provider = TracerProvider(7 resource=Resource.create({"service.name": "speedeedeebee"})8)9trace.set_tracer_provider(provider)10
11class DuplicateKeyError(Exception):12 pass13
14class KeyDoesNotExistError(Exception):15 pass16
17db = {}18
19def read(key):20 """Read key from the database."""21 global db22
23 try:24 value = db[key]25 logging.debug("Successful read")26 return value27 except KeyError as ke:28 msg = f"Key `{key}` doesn't exist"29 logging.debug(msg)30 raise KeyDoesNotExistError(msg)31
32def create(key, value):33 """Write key:value to the database."""34 global db35
36 if key in db:37 msg = f"Key `{key}` already exists"38 logging.debug(msg)39 raise DuplicateKeyError(msg)40
41 db[key] = value42 logging.debug("Successful create")43 return value44
45def update(key, value):46 """Update key in the database."""47 global db48
49 if key in db:50 db[key] = value51 logging.debug("Successful update")52 return value53
54 msg = f"Key `{key}` doesn't exist"55 logging.debug(msg)56 raise KeyDoesNotExistError(msg)57
58def delete(key):59 """Delete key from the database."""60 global db61
62 if key in db:63 del db[key]64 logging.debug("Successful delete")65 return True66
67 return False
Here, you created a tracer provider with a resource. A resource describes a service as a collection of attributes. In this resource, you specified name of your service as "speedeedeebee". You also configured your API to use your new tracer provider.
Add a span processor, which processes span data before exporting it to a telemetry consumer:
1import logging2from grpc import Compression3from opentelemetry import trace4from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter5from opentelemetry.sdk.resources import Resource6from opentelemetry.sdk.trace import TracerProvider7from opentelemetry.sdk.trace.export import BatchSpanProcessor8
9provider = TracerProvider(10 resource=Resource.create({"service.name": "speedeedeebee"})11)12provider.add_span_processor(13 BatchSpanProcessor(14 OTLPSpanExporter(compression=Compression.Gzip)15 )16)17trace.set_tracer_provider(provider)18
19class DuplicateKeyError(Exception):20 pass21
22class KeyDoesNotExistError(Exception):23 pass24
25db = {}26
27def read(key):28 """Read key from the database."""29 global db30
31 try:32 value = db[key]33 logging.debug("Successful read")34 return value35 except KeyError as ke:36 msg = f"Key `{key}` doesn't exist"37 logging.debug(msg)38 raise KeyDoesNotExistError(msg)39
40def create(key, value):41 """Write key:value to the database."""42 global db43
44 if key in db:45 msg = f"Key `{key}` already exists"46 logging.debug(msg)47 raise DuplicateKeyError(msg)48
49 db[key] = value50 logging.debug("Successful create")51 return value52
53def update(key, value):54 """Update key in the database."""55 global db56
57 if key in db:58 db[key] = value59 logging.debug("Successful update")60 return value61
62 msg = f"Key `{key}` doesn't exist"63 logging.debug(msg)64 raise KeyDoesNotExistError(msg)65
66def delete(key):67 """Delete key from the database."""68 global db69
70 if key in db:71 del db[key]72 logging.debug("Successful delete")73 return True74
75 return False
The BatchSpanProcessor
you used here batches spans before exporting them. This reduces the number of requests you send to New Relic.
Within this span processor, you also configured a span exporter. The exporter is in charge of serializing and sending spans to the consumer. Here, you used OTLP, OpenTelemetry's exchange protocol, and Gzip compression to efficiently transport your telemetry data to New Relic.
You configure your tracer provider with this processor logically prior to setting your tracer provider in the API.
Create two environment variables that you use to configure your OpenTelemetry pipelines. Don't forget to replace the license key placeholder with your real one:
$export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317$export OTEL_EXPORTER_OTLP_HEADERS="api-key=<YOUR-LICENSE-KEY>"
The OpenTelemetry Protocol (OTLP) endpoint is the url of our OpenTelemetry receiver. Your service sends data directly to New Relic through this endpoint.
Important
https://otlp.nr-data.net:4317 is our US endpoint. If you're in the EU, use https://otlp.eu01.nr-data.net/ instead.
There are several different types of API keys to choose from in New Relic that each serve a different purpose. To instrument your application with OpenTelemetry, you need a license key.
The span exporter you configured in the last step automatically uses these standard environment variables.
Create a tracer:
1import logging2from grpc import Compression3from opentelemetry import trace4from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter5from opentelemetry.sdk.resources import Resource6from opentelemetry.sdk.trace import TracerProvider7from opentelemetry.sdk.trace.export import BatchSpanProcessor8
9provider = TracerProvider(10 resource=Resource.create({"service.name": "speedeedeebee"})11)12provider.add_span_processor(13 BatchSpanProcessor(14 OTLPSpanExporter(compression=Compression.Gzip)15 )16)17trace.set_tracer_provider(provider)18
19tracer = trace.get_tracer(__name__)20
21class DuplicateKeyError(Exception):22 pass23
24class KeyDoesNotExistError(Exception):25 pass26
27db = {}28
29def read(key):30 """Read key from the database."""31 global db32
33 try:34 value = db[key]35 logging.debug("Successful read")36 return value37 except KeyError as ke:38 msg = f"Key `{key}` doesn't exist"39 logging.debug(msg)40 raise KeyDoesNotExistError(msg)41
42def create(key, value):43 """Write key:value to the database."""44 global db45
46 if key in db:47 msg = f"Key `{key}` already exists"48 logging.debug(msg)49 raise DuplicateKeyError(msg)50
51 db[key] = value52 logging.debug("Successful create")53 return value54
55def update(key, value):56 """Update key in the database."""57 global db58
59 if key in db:60 db[key] = value61 logging.debug("Successful update")62 return value63
64 msg = f"Key `{key}` doesn't exist"65 logging.debug(msg)66 raise KeyDoesNotExistError(msg)67
68def delete(key):69 """Delete key from the database."""70 global db71
72 if key in db:73 del db[key]74 logging.debug("Successful delete")75 return True76
77 return False
You use this to create spans.
Wrap the logic in each of your database functions with a span:
1import logging2from grpc import Compression3from opentelemetry import trace4from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter5from opentelemetry.sdk.resources import Resource6from opentelemetry.sdk.trace import TracerProvider7from opentelemetry.sdk.trace.export import BatchSpanProcessor8
9provider = TracerProvider(10 resource=Resource.create({"service.name": "speedeedeebee"})11)12provider.add_span_processor(13 BatchSpanProcessor(14 OTLPSpanExporter(compression=Compression.Gzip)15 )16)17trace.set_tracer_provider(provider)18
19tracer = trace.get_tracer(__name__)20
21class DuplicateKeyError(Exception):22 pass23
24class KeyDoesNotExistError(Exception):25 pass26
27db = {}28
29def read(key):30 """Read key from the database."""31 global db32
33 with tracer.start_as_current_span("read", kind=trace.SpanKind.SERVER) as span:34 try:35 value = db[key]36 logging.debug("Successful read")37 return value38 except KeyError as ke:39 msg = f"Key `{key}` doesn't exist"40 logging.debug(msg)41 raise KeyDoesNotExistError(msg)42
43def create(key, value):44 """Write key:value to the database."""45 global db46
47 with tracer.start_as_current_span("create", kind=trace.SpanKind.SERVER) as span:48 if key in db:49 msg = f"Key `{key}` already exists"50 logging.debug(msg)51 raise DuplicateKeyError(msg)52
53 db[key] = value54 logging.debug("Successful create")55 return value56
57def update(key, value):58 """Update key in the database."""59 global db60
61 with tracer.start_as_current_span("update", kind=trace.SpanKind.SERVER) as span:62 if key in db:63 db[key] = value64 logging.debug("Successful update")65 return value66
67 msg = f"Key `{key}` doesn't exist"68 logging.debug(msg)69 raise KeyDoesNotExistError(msg)70
71def delete(key):72 """Delete key from the database."""73 global db74
75 with tracer.start_as_current_span("delete", kind=trace.SpanKind.SERVER) as span:76 if key in db:77 del db[key]78 logging.debug("Successful delete")79 return True80
81 return False
To capture data about the operations in your database functions, you used the tracer.start_as_current_span()
context manager. In it, you specified the name of the span and the kind of span it is. Because it's a database server, you specify trace.SpanKind.SERVER
.
The API populates some data about the span for you. You'll see the data it captures when you look at your spans in New Relic.
In the success cases, capture the key that was used for each operation:
1import logging2from grpc import Compression3from opentelemetry import trace4from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter5from opentelemetry.sdk.resources import Resource6from opentelemetry.sdk.trace import TracerProvider7from opentelemetry.sdk.trace.export import BatchSpanProcessor8
9provider = TracerProvider(10 resource=Resource.create({"service.name": "speedeedeebee"})11)12provider.add_span_processor(13 BatchSpanProcessor(14 OTLPSpanExporter(compression=Compression.Gzip)15 )16)17trace.set_tracer_provider(provider)18
19tracer = trace.get_tracer(__name__)20
21class DuplicateKeyError(Exception):22 pass23
24class KeyDoesNotExistError(Exception):25 pass26
27db = {}28
29def read(key):30 """Read key from the database."""31 global db32
33 with tracer.start_as_current_span("read", kind=trace.SpanKind.SERVER) as span:34 try:35 value = db[key]36 logging.debug("Successful read")37 span.set_attribute("key", key)38 return value39 except KeyError as ke:40 msg = f"Key `{key}` doesn't exist"41 logging.debug(msg)42 raise KeyDoesNotExistError(msg)43
44def create(key, value):45 """Write key:value to the database."""46 global db47
48 with tracer.start_as_current_span("create", kind=trace.SpanKind.SERVER) as span:49 if key in db:50 msg = f"Key `{key}` already exists"51 logging.debug(msg)52 raise DuplicateKeyError(msg)53
54 db[key] = value55 logging.debug("Successful create")56 span.set_attribute("key", key)57 return value58
59def update(key, value):60 """Update key in the database."""61 global db62
63 with tracer.start_as_current_span("update", kind=trace.SpanKind.SERVER) as span:64 if key in db:65 db[key] = value66 logging.debug("Successful update")67 span.set_attribute("key", key)68 return value69
70 msg = f"Key `{key}` doesn't exist"71 logging.debug(msg)72 raise KeyDoesNotExistError(msg)73
74def delete(key):75 """Delete key from the database."""76 global db77
78 with tracer.start_as_current_span("delete", kind=trace.SpanKind.SERVER) as span:79 if key in db:80 del db[key]81 logging.debug("Successful delete")82 span.set_attribute("key", key)83 return True84
85 return False
Here, you use the span you created with the context manager to capture the key used in the operation as an attribute.
Restart your simulator
Now that you've changed the application logic, you need to restart your simulator. Make sure you do this in the same terminal window where you set your environment variables:
$python simulator.py
You've instrumented your application to send traces to New Relic using OTLP. You've also restarted your simulator. Now, it's time to view your data.
lab
This procedure is part of a lab that teaches you how to instrument your application with OpenTelemetry. Now that you've instrumented your app, view your telemetry data in New Relic.