Manual Instrumentation
trace_utils.py
Defines two functions: one for creating a data processing pipeline, and one for creating a tracer. The tracer will be used to produce spans. Here, the SDK and API are used. The SDK is the implementation for telemetry data processing, while the API is called by developers to generate spans.
# OTel API
from opentelemetry import trace as trace_api
# OTel SDK
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
from resource_utils import create_resource
def create_tracing_pipeline()-> BatchSpanProcessor:
# write spans straight to console (stdout)
console_exporter = ConsoleSpanExporter()
# push spans to one or more SpanExporters
span_processor = BatchSpanProcessor(console_exporter)
return span_processor
# function to create a tracer with the given name and version
def create_tracer(name:str, version:str)->trace_api.Tracer:
provider = TracerProvider(
resource=create_resource(name, version)
)
provider.add_span_processor(create_tracing_pipeline())
trace_api.set_tracer_provider(provider)
tracer = trace_api.get_tracer(name, version)
return tracerresource_utils.py
Defines resource attributes to make it easier to identify the source of telemetry signals. Semantic Convention is used here to follow the standard and avoid spelling errors.
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.resource import ResourceAttributes
def create_resource(name: str, version: str) -> Resource:
svc_resource = Resource.create(
{
ResourceAttributes.SERVICE_NAME: name,
ResourceAttributes.SERVICE_VERSION: version
}
)
return svc_resourceapp.py
Instrument the function index corresponding to the Flask endpoint "/".
First, use the tracer decorator to automatically handle span creation, start, and end. Inside the index function, use the get_current_span method to obtain the current span in the environment, and set attributes on this span using the attribute names provided by Semantic Convention.
@app.route("/")
@tracer.start_as_current_span("index")
def index():
span = trace_api.get_current_span()
span.set_attributes(
{
SpanAttributes.HTTP_METHOD: request.method,
SpanAttributes.URL_PATH: request.path,
SpanAttributes.HTTP_RESPONSE_STATUS_CODE: 200,
}
)
do_stuff()
current_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime())
return f"Hello, World! It's currently {current_time}"How to pass OTel Context between different applications? When a request passes through different services, use the traceid to identify it. Put the traceid in the request header. When the next service receives the request, extract the traceid from the header, use the attach method provided by OTel to set the context. After the request ends in the current service, use the detach method to cancel/destroy the context?
from flask import Flask, make_response, request
from trace_utils import create_tracer
from opentelemetry import trace as trace_api
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry import context
from opentelemetry.propagate import inject,extract
@app.teardown_request
def teardown_request_func(err):
previous_context = request.environ.get("previous_ctx_token",None)
if previous_context:
context.detach(previous_context)
@app.before_request
def before_request_func():
ctx = extract(request.headers)
previous_ctx = context.attach(ctx)
request.environ["previous_ctx_token"] = previous_ctx
@app.route("/users", methods=["GET"])
@tracer.start_as_current_span("users")
def get_user():
user, status = db.get_user(123)
data = {}
if user is not None:
data = {"id": user.id, "name": user.name, "address": user.address}
response = make_response(data, status)
return response