---
title: "Query telemetry"
description: "Query workflow logs and spans for a job from Python or Go."
image: "https://tilebox.com/images/tilebox-docs-social-preview.png"
---

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

# Query telemetry

import { CodeGroup } from "@docs/components/ui/code-group";

import { Frame } from "@docs/components/ui/frame";

Tilebox stores logs and spans for each workflow job. Use the jobs client to query that telemetry from notebooks, scripts, or automated diagnostics.

## Query job logs

`query_logs()` returns a `LogRecords` list. Pagination is handled automatically.

```python title="Python"
from tilebox.workflows import Client

client = Client()
job = client.jobs().find("019e07b1-916b-0630-f3ba-f1c33235d174")

logs = client.jobs().query_logs(job)

for record in logs:
    print(record.time, record.severity_text, record.body)
    print(record.attributes)
```

```go title="Go"
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 record, err := range client.Jobs.QueryLogs(
		ctx,
		jobID,
		workflows.WithSortDirection(workflows.Ascending),
	) {
		if err != nil {
			slog.ErrorContext(ctx, "failed to query job logs", slog.Any("error", err))
			return
		}

		fmt.Println(record.Time.Format(time.RFC3339Nano), record.Level, record.Body)
		fmt.Println(record.Attributes)
	}
}
```

Each log record includes:

- `time`
- `severity_number` and `severity_text`
- `body`
- `trace_id` and `span_id`
- `attributes`
- `runner_attributes`

### As pandas DataFrame

Use `to_pandas()` to convert log records to a pandas DataFrame.

```python title="Python"
logs_df = client.jobs().query_logs(job).to_pandas()
logs_df[["time", "severity_text", "body"]]
```

  <img src="/docs/assets/workflows/observability/job-logs-pandas-light.png" alt="Job Logs as Pandas DataFrame" className="dark:hidden" />
  <img src="/docs/assets/workflows/observability/job-logs-pandas-dark.png" alt="Job Logs as Pandas DataFrame" className="hidden dark:block" />

## Query job spans

`query_spans()` returns a `Spans` list. Pagination is handled automatically.

```python title="Python"
spans = client.jobs().query_spans(job.id)

for span in spans:
    print(span.name, span.status_code, span.duration)
    print(span.attributes)
```

```go title="Go"
package main

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

	"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,
		workflows.WithSortDirection(workflows.Ascending),
	) {
		if err != nil {
			slog.ErrorContext(ctx, "failed to query job spans", slog.Any("error", err))
			return
		}

		fmt.Println(span.Name, span.StatusCode, span.Duration())
		fmt.Println(span.Attributes)
	}
}
```

Each span includes:

- `start_time` and `end_time`
- `duration`
- `trace_id`, `span_id`, and `parent_span_id`
- `name`
- `status_code` and `status_message`
- `attributes`
- `runner_attributes`
- `events`

### As pandas DataFrame

Use `to_pandas()` to convert spans to a pandas DataFrame.

```python title="Python"
spans_df = client.jobs().query_spans(job).to_pandas()

slow_spans = spans_df.sort_values("duration", ascending=False).head(10)
slow_spans[["name", "duration", "start_time"]]
```

  <img src="/docs/assets/workflows/observability/job-traces-pandas-light.png" alt="Job Traces as Pandas DataFrame" className="dark:hidden" />
  <img src="/docs/assets/workflows/observability/job-traces-pandas-dark.png" alt="Job Traces as Pandas DataFrame" className="hidden dark:block" />

Nested `attributes`, `runner_attributes`, and `events` stay as Python objects in DataFrame columns. Span DataFrames include a computed `duration` column.

Source: https://tilebox.com/docs/workflows/run-and-inspect/query-telemetry/index.mdx
