﻿---
title: Instrumenting custom code
description: Elastic APM instruments a variety of libraries out of the box, but sometimes you need to know how long a specific function took or how often it gets called...
url: https://www.elastic.co/elastic/docs-builder/docs/3028/reference/apm/agents/python/instrumenting-custom-code
products:
  - APM Agent
  - APM Python Agent
applies_to:
  - Serverless Observability projects: Generally available
  - Elastic Stack: Generally available
  - Application Performance Monitoring Agent for Python: Generally available
---

# Instrumenting custom code
## Creating Additional Spans in a Transaction

Elastic APM instruments a variety of libraries out of the box, but sometimes you need to know how long a specific function took or how often it gets called.
Assuming you’re using one of our [supported frameworks](https://www.elastic.co/elastic/docs-builder/docs/3028/reference/apm/agents/python/set-up-apm-python-agent), you can apply the `@elasticapm.capture_span()` decorator to achieve exactly that. If you’re not using a supported framework, see [Creating New Transactions](#instrumenting-custom-code-transactions).
`elasticapm.capture_span` can be used either as a decorator or as a context manager. The following example uses it both ways:
```python
import elasticapm

@elasticapm.capture_span()
def coffee_maker(strength):
    fetch_water()

    with elasticapm.capture_span('near-to-machine'):
        insert_filter()
        for i in range(strength):
            pour_coffee()

        start_drip()

    fresh_pots()
```

Similarly, you can use `elasticapm.async_capture_span` for instrumenting `async` workloads:
```python
import elasticapm

@elasticapm.async_capture_span()
async def coffee_maker(strength):
    await fetch_water()

    async with elasticapm.async_capture_span('near-to-machine'):
        await insert_filter()
        async for i in range(strength):
            await pour_coffee()

        start_drip()

    fresh_pots()
```

<note>
  `asyncio` support is only available in Python 3.7+.
</note>

See [the API docs](/elastic/docs-builder/docs/3028/reference/apm/agents/python/api-reference#api-capture-span) for more information on `capture_span`.

## Creating New Transactions

It’s important to note that `elasticapm.capture_span` only works if there is an existing transaction. If you’re not using one of our [supported frameworks](https://www.elastic.co/elastic/docs-builder/docs/3028/reference/apm/agents/python/set-up-apm-python-agent), you need to create a `Client` object and begin and end the transactions yourself. You can even utilize the agent’s [automatic instrumentation](/elastic/docs-builder/docs/3028/reference/apm/agents/python/supported-technologies#automatic-instrumentation)!
To collect the spans generated by the supported libraries, you need to invoke `elasticapm.instrument()` (just once, at the initialization stage of your application) and create at least one transaction. It is up to you to determine what you consider a transaction within your application — it can be the whole execution of the script or a part of it.
The example below will consider the whole execution as a single transaction with two HTTP request spans in it. The config for `elasticapm.Client` can be passed in programmatically, and it will also utilize any config environment variables available to it automatically.
```python
import requests
import time
import elasticapm

def main():
    sess = requests.Session()
    for url in [ 'https://www.elastic.co', 'https://benchmarks.elastic.co' ]:
        resp = sess.get(url)
        time.sleep(1)

if __name__ == '__main__':
    client = elasticapm.Client(service_name="foo", server_url="https://example.com:8200")
    elasticapm.instrument() 
    client.begin_transaction(transaction_type="script")
    main()
    client.end_transaction(name=__name__, result="success")
```

Note that you don’t need to do anything to send the data — the `Client` object will handle that before the script exits. Additionally, the `Client` object should be treated as a singleton — you should only create one instance and store/pass around that instance for all transaction handling.

## Distributed Tracing

When instrumenting custom code across multiple services, you should propagate the TraceParent where possible. This allows Elastic APM to bundle the various transactions into a single distributed trace. The Python Agent will automatically add TraceParent information to the headers of outgoing HTTP requests, which can then be used on the receiving end to add that TraceParent information to new manually-created transactions.
Additionally, the Python Agent provides utilities for propagating the TraceParent in string format.
```python
import elasticapm

client = elasticapm.Client(service_name="foo", server_url="https://example.com:8200")

# Retrieve the current TraceParent as a string, requires active transaction
traceparent_string = elasticapm.get_trace_parent_header()

# Create a TraceParent object from a string and use it for a new transaction
parent = elasticapm.trace_parent_from_string(traceparent_string)
client.begin_transaction(transaction_type="script", trace_parent=parent)
# Do some work
client.end_transaction(name=__name__, result="success")

# Create a TraceParent object from a dictionary of headers, provided
# automatically by the sending service if it is using an Elastic APM Agent.
parent = elasticapm.trace_parent_from_headers(headers_dict)
client.begin_transaction(transaction_type="script", trace_parent=parent)
# Do some work
client.end_transaction(name=__name__, result="success")
```