# Instrumentation

There are two main ways to instrument your application with the Langfuse SDKs:

- Using our **[native integrations](/content/integrations/index.html)** for popular LLM and agent libraries such as OpenAI, LangChain, or the Vercel AI SDK. They automatically create observations and traces and capture prompts, responses, usage, and errors.
- Manually instrumenting your application with the Langfuse SDK. The SDKs provide 3 ways to create observations:
  - **[Context manager](/content/docs/observability/sdk/instrumentation#context-manager/index.html)**
  - **[Observe wrapper](/content/docs/observability/sdk/instrumentation#observe-wrapper/index.html)**
  - **[Manual observations](/content/docs/observability/sdk/instrumentation#manual-observations/index.html)**

All approaches are interoperable. You can nest a decorator-created observation inside a context manager or mix manual spans with our [native integrations](/content/integrations/index.html).

## [Custom instrumentation](/content/docs/observability/sdk/instrumentation#custom/index.html)

Instrument your application with the Langfuse SDK using the following methods:

### [Context manager](/content/docs/observability/sdk/instrumentation#context-manager/index.html)

The context manager allows you to create a new span and set it as the currently active observation in the OTel context for its duration. All new observations created within this block will automatically be its children.

Python SDKJS/TS SDK

```python
from langfuse import get_client, propagate_attributes

langfuse = get_client()

with langfuse.start_as_current_observation(
    as_type="span",
    name="user-request-pipeline",
    input={"user_query": "Tell me a joke"},
) as root_span:
    with propagate_attributes(user_id="user_123", session_id="session_abc"):
        with langfuse.start_as_current_observation(
            as_type="generation",
            name="joke-generation",
            model="gpt-4o",
        ) as generation:
            generation.update(output="Why did the span cross the road?")

root_span.update(output={"final_joke": "..."})
```

### [Observe wrapper](/content/docs/observability/sdk/instrumentation#observe-wrapper/index.html)

The observe decorator is an easy way to automatically capture inputs, outputs, timings, and errors of a wrapped function without modifying the function's internal logic.

Python SDKJS/TS SDK

Use [`observe()`](https://python.reference.langfuse.com/langfuse#observe) to decorate a function and automatically capture inputs, outputs, timings, and errors.

```python
from langfuse import observe

@observe()
def my_data_processing_function(data, parameter):
    return {"processed_data": data, "status": "ok"}

@observe(name="llm-call", as_type="generation")
async def my_async_llm_call(prompt_text):
    return "LLM response"
```

### [Manual observations](/content/docs/observability/sdk/instrumentation#manual-observations/index.html)

You can also manually create observations. This is useful when you need to:

- Record work that is self-contained or happens in parallel to the main execution flow but should still be part of the same overall trace (e.g., a background task initiated by a request).
- Manage the observation's lifecycle explicitly, perhaps because its start and end are determined by non-contiguous events.
- Obtain an observation object reference before it's tied to a specific context block.

Python SDKJS/TS SDK

```python
from langfuse import get_client

langfuse = get_client()

span = langfuse.start_observation(name="manual-span")
span.update(input="Data for side task")
child = span.start_observation(name="child-span", as_type="generation")
child.end()
span.end()
```

### [Nesting observations](/content/docs/observability/sdk/instrumentation#nesting-observations/index.html)

The Langfuse SDKs methods automatically handle the nesting of observations.

Python SDKJS/TS SDK

**Observe Decorator**
If you use the [observe wrapper](/content/docs/observability/sdk/instrumentation#observe-wrapper/index.html), the function call hierarchy is automatically captured and reflected in the trace.

```python
from langfuse import observe

@observe
def my_data_processing_function(data, parameter):
    # ... processing logic ...
    return {"processed_data": data, "status": "ok"}

@observe
def main_function(data, parameter):
    return my_data_processing_function(data, parameter)
```

Python SDKJS/TS SDK

Use [`propagate_attributes()`](https://python.reference.langfuse.com/langfuse#propagate_attributes) to add attributes to observations. 
In the Python SDK, `environment` is a first-class Langfuse environment and maps to `langfuse.environment`, not to trace metadata. Use it when the environment is request-scoped, for example when one shared proxy handles requests from multiple deployment environments.

```python
from langfuse import get_client, propagate_attributes

langfuse = get_client()

with langfuse.start_as_current_observation(as_type="span", name="user-workflow"):
    with propagate_attributes(
        user_id="user_123",
        session_id="session_abc",
        metadata={"experiment": "variant_a"},
        version="1.0",
        environment="staging",
        trace_name="user-workflow",
    ):
        with langfuse.start_as_current_observation(as_type="generation", name="llm-call"):
            pass
```
### [Flush observations](/content/docs/observability/sdk/instrumentation#client-lifecycle--flushing/index.html)

Always [`flush()`](https://python.reference.langfuse.com/langfuse#Langfuse.flush) or [`shutdown()`](https://python.reference.langfuse.com/langfuse#Langfuse.shutdown) the client in short-lived processes (scripts, serverless functions, workers) to avoid losing data.
