# Logs and outputs

## Status and spend

```python
job = client.get("job_387ee42f5e")
print(job.status.value, f"{job.progress:.0%}", job.spend, job.attempts)
```

| Field | Meaning |
| --- | --- |
| `status` | `queued`, `running`, `recovering`, `succeeded`, `stopped`, `failed`, `cancelled` |
| `progress` | Share of `work_units` completed, 0 to 1 |
| `spend` | Dollars spent so far, across all attempts |
| `attempts` | Machines the job has been placed on |
| `offer` | The current or last machine: GPU, provider, region, price |

## Logs

```python
print(job.logs(tail=50))
```

```bash
runcompute logs job_387ee42f5e --tail 50
```

Logs contain RunCompute's own lines (placement, preemption, resume) and your
container's output, each prefixed with a time and a source such as
`[runcompute]`, `[container]` or `[train]`.

## Events

Events are the structured version of the lifecycle lines in the log.

```python
for e in job.events():
    print(e["seq"], e["kind"], e["msg"])
```

| Kind | When |
| --- | --- |
| `submit` | The job was accepted |
| `match` | The job was placed on a machine |
| `checkpoint` | A checkpoint was saved |
| `preempt` | The provider took the machine back |
| `resume` | The job restarted from a checkpoint |
| `budget` | Spend reached the budget and the job stopped |
| `done` | The job finished |
| `cancel` | You cancelled it |
| `error` | No machine fits the remaining budget or constraints |

Pass `after=` with the last `seq` you saw to get only new events.

## Outputs

Files your job writes under `/outputs` are kept after it ends.

```python
for out in job.outputs():
    print(out.name, out.size_bytes)
```

```bash
runcompute outputs job_387ee42f5e
```
