Python Tracing SDK

Tracing with Instana is automatic but if you want even more visibility into custom code, a specific application area, or some in-house component, you can use the Python Tracing SDK of Instana.

Instana Python Tracing SDK

The Instana Python module provides an API to trace any arbitrary part of your application.

You can instrument a section of code for tracing with an API as follows:

from instana.singletons import tracer

try:
  span = tracer.start_span("mywork")
  # The code to be instrumented
  id = user.find_by_name('john.smith')
  span.set_tag('user_id', id)
except Exception as e:
  span.log_exception(e)
finally:
  span.finish()

Alternatively, you can use the context manager with the with-as statement, which automatically captures and logs any exceptions that are raised, as follows:

from instana.singletons import tracer

with tracer.start_active_span("mywork") as scope:
  # The code to be instrumented
  id = user.find_by_name('john.smith')
  scope.span.set_tag('user_id', id)

Asynchronous tracing

Some operations that you want to trace might be asynchronous, which means that they return immediately but still continue to work separately from the main sequence of instructions. To trace these operations, for example, with asyncio, you can use async_tracer-related tracing methods as follows:

import asyncio
from instana.singletons import tracer, async_tracer


async def do_work(parent_span):
    with async_tracer.start_active_span('launch_async_work', child_of=parent_span):
        print('Work stared')
        await asyncio.sleep(1)
        print('Work finished!')

with tracer.start_active_span('launch_uvloop') as sync_scope:
    asyncio.run(do_work(sync_scope.span))

Tracing spans in forked processes

If you want to trace spans in a forked process then add a time period after span.finish(); for example, as follows:

 def forked_process(num):
    print(f'in forked process {num}')
    try:
        span = tracer.start_span(f"forked process {num}")
        print(f'sleep 10 for forked process {num}')
        time.sleep(10)
    except Exception as e:
        span.log_exception(e)
    finally:
        span.finish()

    logger.warning(f'sleep 2 for forked process {num}')
    time.sleep(2)   ##  < -- with a sleep after span.finish(), the span can be collected
    

Carrying context into new threads

Tracing is local to a thread. If you create a new thread, the context must be carried to that new thread and then picked up. You can instrument the code to carry context in to new threads as follows:

from threading import Thread

def child_thread_function(parent_span):
    with tracer.start_active_span('child_thread_span', child_of=parent_span) as child_scope:
        print('Thread offloaded work goes here')

with tracer.start_active_span('parent_thread_span') as parent_scope:
    thread = Thread(target = child_thread_function, args = (parent_scope.span, ))
    thread.start()
    thread.join()

Tracing jobs scheduled to run later

You can instrument jobs that are queued to run later as follows:

# Python 3.8
import asyncio
import datetime
import uvloop
import aiohttp

from instana.singletons import tracer, async_tracer

uvloop.install()

async def launch_async_calls(parent_span):
    with async_tracer.start_active_span('launch_async_calls', child_of=parent_span):
        async with aiohttp.ClientSession() as session:
            async with session.get('https://wikipedia.org') as resp:
               print(resp.status)
               print(await resp.text())

async def run_at(dt, coro):
    await asyncio.sleep((dt - datetime.datetime.now()).total_seconds())
    return await coro

with tracer.start_active_span('launch_uvloop') as sync_scope:
    sync_scope.span.set_tag('span.kind', 'entry')
    asyncio.run(
      run_at(datetime.datetime.now() + datetime.timedelta(seconds=5),
             launch_async_calls(sync_scope.span)))

Adding custom tags to Instana spans

To add custom tags to an Instana span, instrument the following code by using the set_tag method:

with tracer.start_active_span('well_chosen_span_name_here') as scope:
    scope.span.set_tag('custom_tag', 'custom_value')