> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tilebox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracing

> Use built-in workflow traces and custom spans to inspect job execution, task duration, and bottlenecks.

Tilebox traces workflow jobs automatically. Job submission creates a root trace, task runners continue that trace across machines, and every task execution creates a span.

<Frame>
  <img src="https://mintcdn.com/tilebox/TYquvc9froFIydg1/assets/console/job-execution-light.png?fit=max&auto=format&n=TYquvc9froFIydg1&q=85&s=05eaf26b162762e4e73ac042d27bf9c2" alt="Job Execution Trace View" className="dark:hidden" width="1536" height="970" data-path="assets/console/job-execution-light.png" />

  <img src="https://mintcdn.com/tilebox/TYquvc9froFIydg1/assets/console/job-execution-dark.png?fit=max&auto=format&n=TYquvc9froFIydg1&q=85&s=be03415e61e5eee3cee78325c2d12339" alt="Job Execution Trace View" className="hidden dark:block" width="1536" height="970" data-path="assets/console/job-execution-dark.png" />
</Frame>

Built-in traces connect task order, dependencies, parallel execution, task duration, task status, runner identity, service identity, and logs emitted while a span was active.

## Add custom spans

Use `context.tracer` inside a task to add spans around meaningful parts of your own code.

<CodeGroup>
  ```python Python theme={"system"}
  from tilebox.workflows import ExecutionContext, Task

  class ProcessScene(Task):
      scene_id: str

      def execute(self, context: ExecutionContext) -> None:
          with context.tracer.span("download-scene") as span:
              span.set_attribute("scene_id", self.scene_id)
              # download input data

          with context.tracer.span("compute-index"):
              # perform expensive computation
              pass
  ```

  ```go Go theme={"system"}
  package tasks

  import (
  	"context"

  	"github.com/tilebox/tilebox-go/workflows/v1"
  )

  type ProcessScene struct{}

  func (t *ProcessScene) Execute(ctx context.Context) error {
  	return workflows.WithSpan(ctx, "compute-index", func(ctx context.Context) error {
  		// perform expensive computation
  		return nil
  	})
  }
  ```
</CodeGroup>

Custom spans are nested under the current task span. Logs emitted inside the span are correlated with its `trace_id` and `span_id`.

## Span status and exceptions

If a task raises an exception, Tilebox records the exception on the task span and marks the span as failed before the task is retried or marked failed.

For finer-grained error reporting, record errors on your custom spans before re-raising them.

<CodeGroup>
  ```python Python theme={"system"}
  class ProcessScene(Task):
      scene_id: str

      def execute(self, context: ExecutionContext) -> None:
          with context.tracer.span("publish-output") as span:
              try:
                  # publish output
                  pass
              except Exception as error:
                  span.record_exception(error)
                  raise
  ```

  ```go Go theme={"system"}
  package tasks

  import (
  	"context"
  	"fmt"

  	"github.com/tilebox/tilebox-go/workflows/v1"
  )

  type ProcessScene struct{}

  func (t *ProcessScene) Execute(ctx context.Context) error {
  	return workflows.WithSpan(ctx, "publish-output", func(ctx context.Context) error {
  		if err := publishOutput(); err != nil {
  			return fmt.Errorf("failed to publish output: %w", err)
  		}
  		return nil
  	})
  }

  func publishOutput() error {
  	return nil
  }
  ```
</CodeGroup>

## Query spans

You can retrieve spans for a job through the jobs client. Python results can also be converted to a pandas DataFrame.

<CodeGroup>
  ```python Python theme={"system"}
  from tilebox.workflows import Client

  client = Client()
  job = client.jobs().submit("process-scene", ProcessScene(scene_id="S2A_001"))

  spans = client.jobs().query_spans(job)
  for span in spans:
      print(span.name, span.status_code, span.duration)

  df = spans.to_pandas()
  ```

  ```go Go theme={"system"}
  package main

  import (
  	"context"
  	"fmt"
  	"log/slog"
  	"time"

  	"github.com/google/uuid"
  	"github.com/tilebox/tilebox-go/workflows/v1"
  )

  func main() {
  	ctx := context.Background()
  	client := workflows.NewClient()
  	jobID := uuid.MustParse("019e07b1-916b-0630-f3ba-f1c33235d174")

  	for span, err := range client.Jobs.QuerySpans(ctx, jobID) {
  		if err != nil {
  			slog.ErrorContext(ctx, "failed to query job spans", slog.Any("error", err))
  			return
  		}

  		fmt.Printf("%s %-40s %s\n",
  			span.StartTime.Format(time.RFC3339Nano),
  			span.Name,
  			span.Duration(),
  		)
  	}
  }
  ```
</CodeGroup>

See [Query telemetry](/workflows/observability/query) for the log and span query APIs.

## Export to another backend

Tilebox stores traces by default. To export spans to your own observability backend as well, configure an [OpenTelemetry](/workflows/observability/integrations/open-telemetry) or [Axiom](/workflows/observability/integrations/axiom) integration when the runner process starts.
