# Logs

> Assert the entries your services log, read from files or sent to Axx over UDP, TCP or HTTP.

Source: https://axx.nimbusxr.us/next/references/packs/logs/

Assert the entries your services log, read from files or sent to Axx over UDP, TCP or HTTP.

A **log** is where a service's log lines are: a file Axx reads, or an address Axx listens on while services send their lines to it. Register it with `the {word} log with the following properties:`, then assert the entries a scenario must produce. Only what the log received after the scenario registered it counts, and every step waits for its entries (10 seconds unless `within {duration}` says otherwise).

Use logs to prove that something did **not** happen: have the service log its decision ("line ML-KES-0413-1 rejected: duplicate reference") and assert that entry, rather than waiting and hoping nothing arrives. Match on data unique to the scenario (a reference, an id): scenarios run in parallel and share the log.

**The url** says where the lines are:

| url | Axx |
| --- | --- |
| `file:///var/log/app.log`, `file://logs/app.log` | reads what is appended to the file (relative to axx.yaml); `file://.axx/logs/services.log` is the console of the services Axx starts |
| `udp://0.0.0.0:5140` | listens; each datagram is one or more lines (e.g. Docker's syslog log driver, a service's syslog handler) |
| `tcp://0.0.0.0:5150` | listens; newline-delimited or octet-counted (RFC 6587) messages |
| `http://0.0.0.0:5160/logs`, `https://...` | listens; the body of each POST or PUT to that path (e.g. a Fluent Bit or Vector http output). https uses a self-signed certificate |

Axx opens listeners before it starts the services, for the log steps of the scenarios in the run, so services can send from the start; `axx up` keeps them open between runs. Services in containers reach them at `host.docker.internal`.

**Patterns** are regular expressions (Java syntax), searched in the log's text, not matched against whole lines: `^` and `$` match at line boundaries, every match counts, and a pattern can span lines (`\n`, or `(?s)` to let `.` match newlines), which covers multi-line entries such as stack traces.

Add it to a project with `axx pack add logs`.

## Steps

### `logs.log`

```gherkin
Given the {word} log with the following properties:
  | property | value |
```

The table may start with the row naming its columns, as above, or go straight to its rows.

Register a log under a name. Assertions only look at what the log receives from then on.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

| Property | Takes | Default |
|---|---|---|
| `url` | where the log's lines are: a file Axx reads (`file://`, relative to the directory of axx.yaml or absolute), or an address Axx listens on (`udp://`, `tcp://`, `http://`, `https://`); `${env:..}` and `${sys:..}` are expanded | _required_ |

Any other property fails the step.

**Example:**

```gherkin
Given the parcels log with the following properties:
  | url | udp://0.0.0.0:5140 |
Given the console log with the following properties:
  | url | file://.axx/logs/services.log |
```

### `logs.entry`

```gherkin
Then [[within {duration} ]]the {word} log has an entry matching {string}
```

Wait (10s, or the given time) until the log has a match for the regular expression. The pattern is searched in the log's text: `^` and `$` match at line boundaries and a pattern can span lines.

| Parameter | Takes | For example |
|---|---|---|
| `{duration}` | a duration in seconds (`s`) or minutes (`m`) | `5s`, `2m` |
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |
| `{string}` | text in double or single quotes, which the step leaves out | `"Get a quote"`, `'Express'` |

**Variants**, the parts in `[[...]]` said or left out:

- `the {word} log has an entry matching {string}`
- `within {duration} the {word} log has an entry matching {string}`

**Example:**

```gherkin
Then the parcels log has an entry matching 'registration refused reference=PX-EVT-4003'
Then within 30s the parcels log has an entry matching 'manifest line processed line=ML-MAP-0019-3 status=IMPORTED'
```

### `logs.entries`

```gherkin
Then [[within {duration} ]]the {word} log has entries matching:
  | pattern |
```

Wait until the log has a match for every regular expression in the table. Each row needs a match of its own: the same pattern in two rows needs two matches.

| Parameter | Takes | For example |
|---|---|---|
| `{duration}` | a duration in seconds (`s`) or minutes (`m`) | `5s`, `2m` |
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

A row is a regular expression the log must have a match for.

**Variants**, the parts in `[[...]]` said or left out:

- `the {word} log has entries matching:`
- `within {duration} the {word} log has entries matching:`

**Example:**

```gherkin
Then the console log has entries matching:
  | msg="manifest line processed" line=ML-FJORD-0101-1 status=REJECTED |
  | msg="manifest line processed" line=ML-FJORD-0101-2 status=REJECTED |
```

### `logs.count`

```gherkin
Then [[within {duration} ]]the {word} log has {int} entry/entries matching {string}
```

Wait until the log has the given number of matches for the regular expression, for example one per retry. More matches than that fail the step.

| Parameter | Takes | For example |
|---|---|---|
| `{duration}` | a duration in seconds (`s`) or minutes (`m`) | `5s`, `2m` |
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |
| `{int}` | a whole number | `200`, `3` |
| `{string}` | text in double or single quotes, which the step leaves out | `"Get a quote"`, `'Express'` |

**Variants**, the parts in `[[...]]` said or left out:

- `the {word} log has {int} entry/entries matching {string}`
- `within {duration} the {word} log has {int} entry/entries matching {string}`

**Example:**

```gherkin
Then the parcels log has 2 entries matching 'storing parcel PX-DBF-3002 failed, retrying'
```

### `logs.across`

```gherkin
Then [[within {duration} ]]the logs have entries matching:
  | log | pattern |
```

The table may start with the row naming its columns, as above, or go straight to its rows.

Wait until every log in the table has a match for its regular expression, for example the service's own entry and its dependency's. Each row needs a match of its own.

| Parameter | Takes | For example |
|---|---|---|
| `{duration}` | a duration in seconds (`s`) or minutes (`m`) | `5s`, `2m` |

A row names a log registered in the scenario, and a regular expression it must have a match for.

**Variants**, the parts in `[[...]]` said or left out:

- `the logs have entries matching:`
- `within {duration} the logs have entries matching:`

**Example:**

```gherkin
Then the logs have entries matching:
  | parcels | msg="registration refused; not announced" reference=PX-ADR-1104 |
  | console | GET /v1/postcodes/DE/00012                                      |
```
