---
module: agent.log
title: Agent Log — self-log upload & error-to-incident monitoring
tab: Internal
order: 20
audience: dev
---

> **Internal · ops/support view.** Confirmed against `hiveops-module-log` source (`com.hiveops.logs`) 2026-07-01.

## What it does (and doesn't)

`hiveops-module-log` is the agent's **self-observability** module. It watches and ships the agent's **own** operational log file (`logs/hiveops-agent.log`) — it does **not** parse ATM journals, count cassettes, or emit hardware/`IAT_*` events. It exists so HiveIQ can see when the agent itself is unhealthy without SSHing to the ATM.

It packs **two independent `AgentModule` implementations** into one drop-in JAR:

| Module name | Class | Job |
|---|---|---|
| `log-upload` | `LogUploadModule` | Periodically POSTs the whole `hiveops-agent.log` to the backend so support can read an ATM's agent log from the platform. |
| `log-monitor` | `LogMonitorModule` | Tails the same log for `ERROR`/`FATAL` lines and, when enough pile up, reports a summary that the backend turns into a **`SOFTWARE_ERROR` incident**. |

`log-monitor` declares a dependency on `log-upload` (so the shared log-path resolution runs first), but the two run on separate schedulers and can be enabled/disabled independently.

Flow at a glance:
- **log-upload:** on start, and then every `log.upload.interval.minutes` (default 15), multipart-POST `logs/hiveops-agent.log` → `{endpoint}/api/agent/{country}/{atm}/log/upload`.
- **log-monitor:** every `log.monitor.interval.minutes` (default 5), read only the bytes appended since last check, count `ERROR`+`FATAL` (and `WARN` separately). If errors ≥ `log.monitor.error.threshold` **and** the cooldown has elapsed, POST a JSON summary `{errorCount, warnCount, sampleErrors[]}` → the backend creates/dedups a `SOFTWARE_ERROR` incident for that ATM.

## Config properties it registers

All read from `hiveops.properties` (main properties). There is no `getDefaultConfiguration()` override — defaults below are the inline `getProperty(key, default)` fallbacks in code.

**log-upload:**

| Key | Default | Meaning |
|---|---|---|
| `incident.log.upload.enabled` | `true` | Master switch for the upload module. |
| `log.upload.interval.minutes` | `15` | Upload cadence (also fires once immediately on start). |

**log-monitor:**

| Key | Default | Meaning |
|---|---|---|
| `incident.log.monitor.enabled` | `true` | Master switch for the monitor module. |
| `log.monitor.interval.minutes` | `5` | How often the log is scanned for new error lines. |
| `log.monitor.cooldown.minutes` | `120` | Minimum gap between two error reports (anti-spam). |
| `log.monitor.error.threshold` | `1` | Minimum error count in a scan before a report is sent. |
| `log.monitor.max.samples` | `5` | Max error lines included as `sampleErrors` (each truncated to 200 chars). |

**Shared (not owned by this module):** both modules pick the HTTP base URL from `agent.endpoint` (preferred) or `incident.endpoint` (fallback), and build their HTTP client via the matching `agent.*` / `incident.*` settings (auth token, host header, etc.). If neither endpoint is set, both modules self-disable.

The monitored/uploaded file path is `logs/hiveops-agent.log` under the base given by the JVM system property `hiveops-agent.log` (set by `AgentApplication`), falling back to `{startedFromDir}/logs/hiveops-agent.log`.

## Enabled / deployed to ATMs

- **Enable condition:** each sub-module self-enables when `agent.endpoint` **or** `incident.endpoint` is set **and** its own `incident.log.upload.enabled` / `incident.log.monitor.enabled` flag is `true` (both default true). So on any normally-configured device both are on by default.
- **Ships everywhere:** `hiveops-module-log` is in **both** the `atm` and `srv` module sets of `deployment/build-dist.sh`, so it's in every standard install/patch ZIP as the drop-in `modules/hiveops-module-log.jar` (not baked into the fat JAR).
- **Disable per ATM/institution:** push `modules.disabled=log-upload,log-monitor` (comma list; include either or both names) via a `CONFIG_UPDATE` fleet task. Turning off just the noise: set `incident.log.monitor.enabled=false` but leave upload on, or vice-versa.
- **Retrofit an ATM without it:** deliver `modules/hiveops-module-log.jar` via a fleet `INSTALL_MODULE` task (drops the JAR and restarts the agent), or via a full agent update.

## What the backend does with it

- **Log upload** → agent-proxy `AgentLogController` `POST /api/agent/{country}/{atm}/log/upload` → forwarded to incident's internal API. Agent-proxy rejects uploads over **50 MB** with `413 Payload Too Large`.
- **Error report** → the backend maps the report to a **`SOFTWARE_ERROR`** incident via `IncidentAutoCreationService`. That path **suppresses** creation if the ATM is parked or `OUT_OF_SERVICE`, and **dedups** against an already-active `SOFTWARE_ERROR` incident for the same ATM (so a flapping agent gives one incident, not hundreds).

## Common failure modes / what to check

| Symptom | Likely cause | What to check |
|---|---|---|
| No agent log ever appears in the platform for an ATM | Module disabled, or no `agent.endpoint`/`incident.endpoint` on that ATM | Check `modules.disabled`; confirm an endpoint is set; look for "Log upload module initialized" in the ATM's `hiveops-agent.log`. |
| Log upload rejected | File exceeds 50 MB | Agent-proxy returns 413; check log-rotation on the ATM — the module uploads the single current `hiveops-agent.log`, not rotated `.gz` files. |
| Expected a `SOFTWARE_ERROR` incident but none created | ATM parked/`OUT_OF_SERVICE`, or an active `SOFTWARE_ERROR` incident already open (dedup), or still inside the 120-min cooldown | Check ATM status and existing open incidents; check `log.monitor.cooldown.minutes`. |
| Error incidents never fire even though the agent logs errors | Errors don't match the expected Log4j2 line shape (`yyyy-MM-dd HH:mm:ss.SSS LEVEL ...`), so the scanner doesn't count them | Confirm the ATM's `log4j2.xml` pattern starts with timestamp + level; non-standard patterns are silently skipped. |
| Flood of errors right after startup produced nothing | First monitor scan is delayed one full interval (default 5 min) after start | Expected; give it an interval. |
| Report seems to go nowhere / backend 404 | **Path mismatch (see gotcha):** the monitor POSTs to `{endpoint}/atm/log-error?country=&name=`, but agent-proxy exposes the error endpoint at `/api/agent/{country}/{atm}/log/errors` | If error-to-incident stops working after an endpoint/proxy change, verify how `/atm/log-error` is routed for that fleet — see the Claude tab "Do NOT / gotchas". *(routing of the legacy path is unverified.)* |
