# Log Annotations

> Source: https://parallelworks.com/docs/run/workflows/building-workflows/log-annotations

# Log Annotations

A step can talk to ACTIVATE through its own log. Any line a step prints in the form `::command::message` is read as a **workflow command** instead of ordinary output: it can raise an error, warning, or notice on the run page, fold a noisy section of the log into a collapsible group, or mark a line as debug-only.

Workflow commands are just text on stdout or stderr, so they work from any language and from any shell, and they work the same whether the step runs in your user workspace or over SSH on a cluster.

```yaml
jobs:
  main:
    steps:
      - run: |
          echo "::notice::Starting the solver"
          echo "::warning file=inputs.dat,line=12::Using the default timestep"
          echo "::error title=Solver::Residual did not converge"
```

## Commands

| Command | What it does |
| --- | --- |
| `::error::` | Raises an error annotation on the run and highlights the line in red. |
| `::warning::` | Raises a warning annotation and highlights the line in yellow. |
| `::notice::` | Raises an informational annotation and highlights the line in blue. |
| `::debug::` | Marks the line as debug output. Hidden in the log viewer until **Show Debug** is clicked. |
| `::group::` | Starts a collapsible section in the log viewer, titled with the message. |
| `::endgroup::` | Ends the most recently opened group. |

Command names are lowercase, and the command must be the whole line, starting at the first column. A line that merely contains `::error::` somewhere in the middle is left alone.

## Try it

Edit the step output below to see what ACTIVATE would make of it. It runs the same parser the log viewer runs, and applies the same rules the workflow executor uses to decide which annotations a step keeps.

:::info Annotations do not change a step's status
`::error::` reports a problem; it does not fail the step. Step and job status still come from the exit code of the commands you run. To fail a step, exit non-zero (for example `exit 1`).
:::

## Annotations

`::error::`, `::warning::`, and `::notice::` produce **annotations**, which are collected as the step runs and surfaced in three places:

- **On the run page**, in an **Annotations** banner above the job view that counts every annotation in the run by severity, including annotations raised inside subworkflows. Expand it to read each one with the job and step that raised it.
- **In the log itself**, where the line is highlighted and labeled by severity.
- **In the CLI**, where [`pw workflows runs view`](/docs/cli/pw/workflows/runs/view) prints a summary and [`pw workflows runs errors`](/docs/cli/pw/workflows/runs/errors) reports the error annotations and exits non-zero when there are any, which makes it usable as a CI gate.

Annotations stream to the run page while the step is still running, so a long step can report progress as it goes.

### Parameters

An annotation can carry parameters between the command name and the closing `::`, written as `key=value` and separated by commas. A single space separates the command name from the parameters.

```
::error file=solver.py,line=42,title=Convergence::Residual did not converge
```

| Parameter | Description |
| --- | --- |
| `title` | Short label shown in place of the default severity label. |
| `file` | Path of the file the annotation refers to. |
| `line` | Line number in `file`. |
| `endLine` | Last line of the range, when the annotation covers more than one line. |
| `col` | Column number in `line`. |
| `endColumn` | Last column of the range. |
| `id` | Identifier used to update an annotation in place. See [Updating an annotation](#updating-an-annotation). |

All parameters are optional, and unknown keys are ignored. Values cannot contain a colon, since the first `::` after the parameters ends them; the message after the parameters may contain colons freely.

### Updating an annotation

Give an annotation an `id` and every later annotation with that same `id` replaces it in place, keeping its original position in the list. This is how a step reports progress without filling the run page with one annotation per update:

```yaml
jobs:
  upload:
    steps:
      - run: |
          for pct in 0 25 50 75 100; do
            echo "::notice id=upload::Uploading results: ${pct}%"
            sleep 5
          done
```

The run page shows a single "Uploading results" notice that counts up to 100%.

### Limits

A step keeps at most 50 annotations; anything beyond that is dropped from the annotation list, though the line still appears in the log. Updates by `id` keep working past the cap, so progress annotations are never lost to it.

Any [secret](/docs/account-settings/workflow-variables) used by the workflow is redacted from annotations before they are stored, exactly as it is redacted from the log.

## Grouping log lines

`::group::` and `::endgroup::` fold a section of the log into a collapsible block titled with the group's message. Groups can be nested, and each level is indented in the log viewer.

```yaml
jobs:
  build:
    steps:
      - run: |
          echo "::group::Installing dependencies"
          pip install -r requirements.txt
          echo "::endgroup::"
          echo "::group::Compiling"
          make -j8
          echo "::endgroup::"
```

Click a group header in the log viewer to expand or collapse it.

## Debug lines

`::debug::` marks output that is useful when something goes wrong but noise the rest of the time. Debug lines are hidden in the log viewer until you click **Show Debug**.

```yaml
jobs:
  main:
    steps:
      - run: |
          echo "::debug::PATH is $PATH"
          echo "::debug::Resolved host is $(hostname)"
          ./run-solver
```

Grouping and debug are presentation only. The downloaded log and the raw log fetched with [`pw workflows runs logs`](/docs/cli/pw/workflows/runs/logs) contain the literal `::group::` and `::debug::` lines.

## Reacting to failures

Annotations report what happened; `if:` conditions decide what runs next. To run a cleanup or alert step after something fails, see [`jobs.<job>.if`](/docs/run/workflows/building-workflows/yaml-fields#jobsjobif).

```yaml
jobs:
  main:
    steps:
      - run: ./run-solver
      - name: Report the failure
        if: ${{ error }}
        run: echo "::error title=Solver::The solver step failed, see the log above"
```
