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

# Query telemetry

> Query workflow logs and spans for a job from Python or Go.

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 Python theme={"system"}
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 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 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 Python theme={"system"}
logs_df = client.jobs().query_logs(job).to_pandas()
logs_df[["time", "severity_text", "body"]]
```

![Job Logs as Pandas DataFrame](https://mintcdn.com/tilebox/f-tLpk_IhM06GmYl/assets/workflows/observability/job-logs-pandas-light.png?fit=max\&auto=format\&n=f-tLpk_IhM06GmYl\&q=85\&s=ff5fdea94d3c291cacf4d58f00de5de6)

## Query job spans

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

```python Python theme={"system"}
spans = client.jobs().query_spans(job.id)

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

```go Go theme={"system"}
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 Python theme={"system"}
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"]]
```

![Job Traces as Pandas DataFrame](https://mintcdn.com/tilebox/f-tLpk_IhM06GmYl/assets/workflows/observability/job-traces-pandas-light.png?fit=max\&auto=format\&n=f-tLpk_IhM06GmYl\&q=85\&s=901ed72bce41abe53b5af9f3c302ec9a)

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.