# How to Configure Zipkin or Langfuse Observability with the @Tracked Annotation in Embabel

> Learn to configure Zipkin or Langfuse observability in Embabel using the @Tracked annotation. Automatically generate OpenTelemetry spans and export data with simple property settings.

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: how-to-guide
- Published: 2026-08-08

---

**Embabel-Agent's built-in observability module automatically creates OpenTelemetry spans for any method annotated with `@Tracked`, enabling seamless export to Zipkin or Langfuse through classpath detection and simple property configuration.**

The embabel-agent repository provides a comprehensive observability solution that integrates OpenTelemetry tracing directly into your Spring Boot application through aspect-oriented programming. By using the `@Tracked` annotation, you can generate detailed spans for custom business operations without writing boilerplate instrumentation code, then route those spans to Zipkin or Langfuse for visualization and analysis.

## Architecture of the @Tracked Observability Pipeline

Understanding how the pipeline works helps troubleshoot configuration issues and optimize span generation.

### Aspect-Oriented Span Creation

When a method annotated with `@Tracked` is invoked, the **`TrackedAspect`** (a Spring AOP aspect defined in [`embabel-agent-observability/src/main/java/com/embabel/agent/observability/tracing/TrackedAspect.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/main/java/com/embabel/agent/observability/tracing/TrackedAspect.java)) intercepts the call. It starts an **OpenTelemetry span** whose name, type, and description are extracted directly from the annotation attributes. The aspect wraps the method execution, ensuring the span ends when the method returns or throws an exception.

### Configuration Property Resolution

The **`ObservabilityProperties`** class ([`embabel-agent-observability/src/main/java/com/embabel/agent/observability/ObservabilityProperties.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/main/java/com/embabel/agent/observability/ObservabilityProperties.java)) supplies the global flags that control the pipeline. Specifically, `observability.tracing.enabled` activates the OpenTelemetry SDK, while `observability.tracking.enabled` enables the `TrackedAspect` processing. Both must be `true` for `@Tracked` annotations to generate spans.

### SDK Auto-Configuration

The **`OpenTelemetrySdkAutoConfiguration`** class ([`embabel-agent-autoconfigure/embabel-agent-observability-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/observability/OpenTelemetrySdkAutoConfiguration.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-autoconfigure/embabel-agent-observability-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/observability/OpenTelemetrySdkAutoConfiguration.java)) builds the `SdkTracerProvider`. It performs classpath detection: if the Zipkin or Langfuse exporter dependency is present, it auto-configures the appropriate span exporter. If no exporter is found, it falls back to a no-op provider to prevent runtime errors.

### Span Export to Collectors

Once the SDK creates a span, the configured exporter sends it to the remote collector. The **`TrackedAspectAutoConfiguration`** ([`embabel-agent-autoconfigure/embabel-agent-observability-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/observability/TrackedAspectAutoConfiguration.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-autoconfigure/embabel-agent-observability-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/observability/TrackedAspectAutoConfiguration.java)) ensures the aspect is only active when tracking is explicitly enabled via properties.

## Step-by-Step Configuration for Zipkin or Langfuse

Configuring an exporter requires adding the correct dependency and setting the endpoint properties.

### Add the Exporter Dependency

Add the OpenTelemetry exporter dependency matching your target backend to your Maven or Gradle build. Only the exporter block corresponding to the present dependency will be active at runtime.

```xml
<!-- Zipkin Exporter -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>

<!-- OR Langfuse Exporter -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-langfuse</artifactId>
</dependency>

```

### Enable Tracing in application.yml

Configure the global flags and exporter-specific settings in [`application.yml`](https://github.com/embabel/embabel-agent/blob/main/application.yml) or `application.properties`. The `embabel.observability` namespace controls the Embabel-specific features, while the `otel.exporter` namespace configures the OpenTelemetry SDK.

```yaml
embabel:
  observability:
    tracing:
      enabled: true          # Activates OpenTelemetry SDK

      tracked:
        enabled: true        # Enables @Tracked aspect processing

# Zipkin Configuration

otel:
  exporter:
    zipkin:
      endpoint: http://localhost:9411/api/v2/spans

# OR Langfuse Configuration

otel:
  exporter:
    langfuse:
      endpoint: https://api.langfuse.com
      apikey: ${LANGFUSE_API_KEY}

```

## Annotating Methods with @Tracked

The **`@Tracked`** annotation ([`embabel-agent-observability/src/main/java/com/embabel/agent/observability/annotation/Tracked.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/main/java/com/embabel/agent/observability/annotation/Tracked.java)) supports several attributes to enrich your telemetry data.

### Annotation Attributes Reference

| Attribute | Type | Description |
|-----------|------|-------------|
| `value` (alias `name`) | String | Human-readable operation name; defaults to the method name if omitted. |
| `type` | `TrackType` | Enum classification (e.g., `PROCESSING`, `IO`) stored as the `embabel.tracked.type` attribute. |
| `description` | String | Free-form text stored as the `embabel.tracked.description` attribute. |
| `event` | String | Optional event name emitted when the method returns successfully. |

The **`TrackType`** enum ([`embabel-agent-observability/src/main/java/com/embabel/agent/observability/annotation/TrackType.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/main/java/com/embabel/agent/observability/annotation/TrackType.java)) provides standardized categorization for your operations.

### Practical Examples

Apply the annotation to service methods to capture business logic execution. The generated spans automatically include the annotation attributes as OpenTelemetry span attributes.

```kotlin
import com.embabel.agent.observability.annotation.Tracked
import com.embabel.agent.observability.annotation.TrackType

class OrderService {

    @Tracked("createOrder")
    fun create(order: Order) {
        // Business logic
    }

    @Tracked(
        value = "cancelOrder",
        type = TrackType.PROCESSING,
        description = "Cancels an existing order and triggers refund"
    )
    fun cancel(orderId: String) {
        // Cancellation logic
    }

    @Tracked(
        value = "fetchInventory",
        type = TrackType.IO,
        event = "inventory.loaded"
    )
    fun loadInventory(): Inventory {
        // External API call
        return inventoryClient.fetch()
    }
}

```

When these methods execute, the spans appear in your Zipkin or Langfuse dashboard with attributes like `embabel.tracked.name`, `embabel.tracked.type`, and `embabel.tracked.description`.

## Summary

- The **`@Tracked`** annotation triggers automatic span creation via **`TrackedAspect`** for any annotated method.
- Enable observability by setting `embabel.observability.tracing.enabled` and `observability.tracking.enabled` to `true`.
- Add the **`opentelemetry-exporter-zipkin`** or **`opentelemetry-exporter-langfuse`** dependency to route spans to your chosen backend.
- Configure exporter endpoints under the **`otel.exporter`** namespace in your [`application.yml`](https://github.com/embabel/embabel-agent/blob/main/application.yml).
- Use annotation attributes (`type`, `description`, `event`) to add semantic context to your traces.

## Frequently Asked Questions

### What is the difference between `observability.tracing.enabled` and `observability.tracking.enabled`?

`observability.tracing.enabled` activates the OpenTelemetry SDK and global span recording, while `observability.tracking.enabled` specifically enables the Spring AOP aspect that processes `@Tracked` annotations. You must enable both properties for the annotation to generate spans.

### Can I export spans to both Zipkin and Langfuse simultaneously?

Yes. The `OpenTelemetrySdkAutoConfiguration` supports multiple exporters on the classpath. If you include both the Zipkin and Langfuse exporter dependencies and configure both endpoints in [`application.yml`](https://github.com/embabel/embabel-agent/blob/main/application.yml), the SDK will export spans to both collectors concurrently.

### How do I access the custom attributes added by @Tracked in the observability UI?

The attributes appear as standard OpenTelemetry span attributes. In Zipkin, look for tags prefixed with `embabel.tracked.` (e.g., `embabel.tracked.name`, `embabel.tracked.type`). In Langfuse, these attributes map to the trace or span metadata fields depending on the exporter mapping configuration.

### Does @Tracked work with private methods or only public methods?

The **`TrackedAspect`** uses Spring AOP, which typically proxies public methods of Spring beans. Private methods, static methods, or internal calls within the same class bypass the proxy and will not trigger span creation. Annotate public service methods to ensure reliable instrumentation.