DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Exploring Intercooler.js: Simplify AJAX With HTML Attributes
  • Reconciling Privacy Preferences Across Two Datastores With Snowflake and Airflow
  • Building Event-Driven Data Pipelines in GCP
  • The Right to Be Forgotten in Event-Driven Data Products

Trending

  • Why Your DLP Policies Fall Short the Moment AI Agents Enter the Picture
  • MuleSoft IDP: Enhancing Efficiency and Accuracy in Data Extraction
  • What Is Plagiarism? How to Avoid It and Cite Sources
  • Feature Flag Debt: Performance Impact in Enterprise Applications
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Monitoring and Observability
  4. A Hands-On Guide to OpenTelemetry: Manual Instrumentation for Developers

A Hands-On Guide to OpenTelemetry: Manual Instrumentation for Developers

This article continues the series by explaining how to manually instrument your application's telemetry data on your observability journey.

By 
Eric D.  Schabell user avatar
Eric D. Schabell
DZone Core CORE ·
Paige Cruz user avatar
Paige Cruz
·
Sep. 17, 24 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
4.3K Views

Join the DZone community and get the full member experience.

Join For Free

Are you ready to start your journey on the road to collecting telemetry data from your applications? 

In this series, you'll explore how to adopt OpenTelemetry (OTel) and how to instrument an application to collect tracing telemetry. You'll learn how to leverage out-of-the-box automatic instrumentation tools and understand when it's necessary to explore more advanced manual instrumentation for your applications. By the end of this series, you'll have an understanding of how telemetry travels from your applications to the OpenTelemetry Collector, and be ready to bring OpenTelemetry to your future projects. Everything discussed here is supported by a hands-on, self-paced workshop authored by Paige Cruz. 

The previous article zoomed into the usage of Jaeger to help developers when visually exploring their telemetry data. In this article, we'll help developers with manually instrumenting to add metadata specific to their application or business allowing them to derive better insights faster.

It is assumed that you followed the previous articles in setting up both OpenTelemetry and the example Python application project, but if not, go back and see the previous articles as it's not covered here.

Up to now in this series, we've used automatic and programmatic instrumentation to collect tracing data for our system interactions. It would be much more interesting to manually instrument our application, adding metadata specific to our business that helps derive better insights faster.

Adding Span Attributes

Adding span attributes means we are tracking key, value pairs containing metadata to annotate a span with information about the operation it is tracking. For example, to set this up for our Python application, we use the syntax:

span.set_attribute("KEY","VALUE")

To apply this to our application (that you installed and used in the previous articles from this series), we open the file manual/app.py, import the get_current_span from the API, and add an attribute tracking the count of homepage loads to the index() method as follows with the new code shown in bold:

...
  from opentelemetry import trace
  from opentelemetry.trace import set_tracer_provider, get_current_span
  from opentelemetry.sdk.trace import TracerProvider
  from opentelemetry.sdk.trace.export import SimpleSpanProcessor
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
...
@app.route('/')
def index():
  span = trace.get_current_span()
  global HITS
  HITS = HITS + 1
  span.set_attribute("hits", HITS)
  msg = f'This webpage has been viewed {HITS} times'
  return msg
...


We now can build a new container image and run the pod to verify as follows:

$ podman build -t hello-otel:manual -f manual/Buildfile-manual

$ podman play kube manual/app_pod.yaml


With the pod running open a browser to http://localhost:8080 and open the Jaeger UI at http://localhost:16686, searching for traces of the operation / which should look something like this:

Searching for traces of the operation /

Click on a trace and confirm the hits attribute which is in span details:

Click on a trace and confirm the hits attribute which is in span details

This verifies that the manual instrumentation with its attributes is working for tracing our application homepage. What about tracing nested spans?

Let's try manually instrumenting for nested spans. But first, we need to stop the running pod with the following:

$ podman play kube manual/app_pod.yaml --down


Now let's enhance our tracking of nested spans.

Enhancing Programmatic Instrumentation

The instrumented requests library provides data about requests to the Dog API in our application, but not custom methods get_breed() or validate_breed(). To capture that work in /doggo traces we can manually instrument nested spans to reflect their hierarchical relationship. The following creates a new span within the current trace context:

tracer.start_as_current_span()

To add this, open manual/app.py and get access to the global tracer. This is what actually creates and manages spans, shown below in bold for the new code:

...
app = Flask("hello-otel")

FlaskInstrumentor().instrument_app(app)
Jinja2Instrumentor().instrument()
RequestsInstrumentor().instrument()
tracer = provider.get_tracer(app.name)
...


Further down the file, we find the get_breed() method and create a nested span as follows:

...
def get_breed(url):
  with tracer.start_as_current_span("get_breed"):
    path = urllib3.util.parse_url(url).path
    match = re.search(r"/breeds/([^/]+)/", path)
    if match:
      result = match.group(1)
      return result
...


We now need to rebuild the container image and run the pod to verify as follows:

$ podman build -t hello-otel:manual -f manual/Buildfile-manual

$ podman play kube manual/app_pod.yaml


With the pod running open a browser to http://localhost:8080/doggo and make a few requests by refreshing the page. Open the Jaeger UI at http://localhost:16686, searching for traces for the operation /doggo. You should see that the span count has increased but to confirm, click on a trace:

Span count has increased

Now let's verify we are receiving tracing data from the nested spans. The trace waterfall should show one span for the overall request to /doggo, the GET request to the Dog API, and the operation get_breed() to give a fuller picture of where time is spent fulfilling requests to /doggo:

Verify we are receiving tracing data from the nested spans

So far we've used the trace waterfall to visualize our traces. Let's try another option by clicking on the menu in the upper right corner and selecting Trace Graph:

Click on the menu in the upper right corner and selecting Trace Graph

By clicking on the T over on the right-hand vertical menu bar, we can color the critical path or the spans that directly contribute to the slowest path of processing this request. This is how we can view a trace graph by time:

View a trace graph by time

In this example, we're only working with small traces. You can imagine with larger trace samples, such as this example with 160 spans, a birds-eye view can be a very powerful tool when troubleshooting more complex traces:

Birds-eye view

Now let's try adding more span events to our application, but first, we need to stop the running pod so we can add more code to the application:

$ podman play kube manual/app_pod.yaml --down


Now let's add span events.

Adding Span Events

An event contains a structured log with a name, one or more attributes, and a timestamp. These are used to add structured annotations to a current span:

span.add_event(name, attributes)

To add one to our application, open the file manual/app.py and add a span event representing the result of the dice roll to the roll_dice() method as follows. Note the new code is in bold type:

...
@app.route("/rolldice")
def roll_dice():
  sp = trace.get_current_span()
  result = do_roll()
  sp.add_event("rolled dice",attributes={"result":result})
  return result
...


We now need to rebuild the container image and run the pod to verify as follows:

$ podman build -t hello-otel:manual -f manual/Buildfile-manual

$ podman play kube manual/app_pod.yaml


With the pod running, open a browser to http://localhost:8080/rolldice and make a few requests by refreshing the page. Open the Jaeger UI at http://localhost:16686, search for traces for the operation /rolldice, and click on a trace:

Search for traces for the operation /rolldice, and click on a trace

It is of note that the timestamp associated with this span event is relative to the start time of the trace itself:

The timestamp associated with this span event is relative to the start time of the trace itself

Now let's try exploring span status, but first, we need to stop the running pod so we can add more code to the application:

$ podman play kube manual/app_pod.yaml --down


Now let's see what defines a span status.

Adding Span Status

A span status is typically used to identify spans that have not been completed successfully and can be set any time before the span is finished. Spans can have a status of Unset, OK, or Error.

span.set_status(status, description)

The /doggo page allows users to search for images of a specific dog breed. If the search term is invalid, an error message is returned to the user and the request successfully returns 200 OK. If we consider invalid searches to be errors, we need to instrument validate_breed() to trace this activity. This will not make the entire trace failed, but noting the error on a span will be helpful to our application visibility.

To add a span status, open the file manual/app.py and locate validate_breed(). Create a nested span and add an attribute for the breed to help us understand what the user was searching for as follows with bold type indicating our new code:

...
def validate_breed(breed):
  with tracer.start_as_current_span("validate_breed") as span:
    span.set_attribute("breed", breed)
      if breed not in breeds:
        raise ValueError("No breed found.")
      return
...


We now need to rebuild the container image and run the pod to verify as follows:

$ podman build -t hello-otel:manual -f manual/Buildfile-manual

$ podman play kube manual/app_pod.yaml


With the pod running, open a browser to http://localhost:8080/doggo and make a few requests by refreshing the page. Search for valid dog breeds like Doberman or Akita and nonsense terms like woof:

Search for breed

Now open the Jaeger UI at http://localhost:16686 and search for traces for the operation /doggo. We are verifying that there is at least one trace with a successful request marked with an error. Click that trace to see the detailed view:

Click trace for detailed view

Verify that the error message is recorded on the search_breed span and that the breed is recorded as a span attribute:

Verify that the error message is recorded on the search_breed span and that the breed is recorded as a span attribute

As we have seen, combining programmatic and manual instrumentation to enhance our application visibility provided by tracing data is another powerful addition to our toolbox. We've explored and added span attributes, enhanced our programmatic instrumentation, added span events, and explored tracing span status.

These examples use code from a Python application that you can explore in the provided hands-on workshop. 

What's Next?

This article detailed manually instrumenting our application to add metadata specific to our business needs, allowing us to derive better insights faster.

In our next article, we will be turning to manually instrumenting metrics for our application.

Attribute (computing) Data (computing) Event Instrumentation (computer programming) Telemetry

Published at DZone with permission of Eric D. Schabell. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Exploring Intercooler.js: Simplify AJAX With HTML Attributes
  • Reconciling Privacy Preferences Across Two Datastores With Snowflake and Airflow
  • Building Event-Driven Data Pipelines in GCP
  • The Right to Be Forgotten in Event-Driven Data Products

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook