Skip to content

Audit Logs

Breeze RMM provides three distinct logging systems that together give you full visibility into platform activity, agent health, and device-level events. Each system serves a different purpose and is stored in its own database table with dedicated API endpoints.

| Log Type | Purpose | Source | Database Table | |---|---|---|---| | Platform Audit Logs | Track user, API key, agent, and system actions across the platform | API server | audit_logs | | Agent Diagnostic Logs | Capture internal agent runtime diagnostics (debug, info, warn, error) | Breeze agent | agent_logs | | Device Event Logs | Collect OS-level event logs from managed devices (security, hardware, application, system) | Managed devices | device_event_logs |


Platform audit logs record every significant action taken within your Breeze environment. They are the primary compliance and security trail.

Each audit log entry contains the following fields:

| Field | Type | Description | |---|---|---| | id | UUID | Unique identifier for the log entry | | orgId | UUID | Organization the action belongs to (multi-tenant scoping) | | timestamp | Timestamp | When the action occurred (defaults to current time) | | actorType | Enum | Who performed the action: user, api_key, agent, or system | | actorId | UUID | Identifier of the actor (user ID, agent ID, etc.) | | actorEmail | String (255) | Email address of the actor (when available) | | action | String (100) | The action performed (e.g., user.login, device.create, script.execute) | | resourceType | String (50) | Type of resource acted upon (e.g., device, policy, organization) | | resourceId | UUID | Identifier of the affected resource | | resourceName | String (255) | Human-readable name of the affected resource | | details | JSONB | Additional context about the action (before/after values, parameters, etc.) | | ipAddress | String (45) | Client IP address (supports IPv4 and IPv6) | | userAgent | Text | Client user-agent string | | result | Enum | Outcome of the action: success, failure, or denied | | errorMessage | Text | Error details when the result is failure or denied | | checksum | String (128) | SHA-256 of this row’s canonical payload | | prevChecksum | String (128) | Checksum of the previous audit row in this organization’s hash chain |

Breeze tracks four types of actors:

| Actor Type | Description | Example Actions | |---|---|---| | user | A human user authenticated via JWT | Logging in, updating a device, exporting data | | api_key | An API key (prefix brz_) used for programmatic access | Automated script execution, integrations | | agent | A Breeze agent running on a managed device | Submitting event logs, heartbeat updates | | system | Internal platform operations with no human actor | Scheduled cleanups, automated policy evaluations |

When a non-UUID actor ID is provided (e.g., an agent string identifier), the system stores a zero UUID as the actorId and preserves the original value in the details JSONB field under the key rawActorId. The same treatment applies to non-UUID resourceId values, stored as rawResourceId in details.

Actions are classified into categories based on their prefix. This categorization drives the reporting endpoints:

| Category | Action Prefixes | Examples | |---|---|---| | authentication | user.login, user.logout, user.permission | user.login, user.login.failed, user.permission.change | | device | device. | device.create, device.update, device.delete | | automation | script. | script.execute | | policy | policy., automation.policy. | policy.create, policy.update, policy.evaluate, automation.policy.evaluate | | alert | alert. | alert.create, alert.acknowledge | | compliance | data. | data.access, data.export | | organization | organization. | organization.update | | system | Everything else | System-generated events |

The following actions are flagged as security events and appear in the security events report:

  • user.login
  • user.login.failed
  • user.permission.change
  • policy.update
  • policy.create
  • policy.evaluate
  • automation.policy.evaluate
  • agent.source.ip.changed — emitted when an agent’s source IP changes between heartbeats; can indicate NAT changes, mobility, or stolen tokens.

Audit log rows are append-only at the database layer. Postgres triggers refuse UPDATE, DELETE, and TRUNCATE against the table — even a superuser cannot rewrite history without explicit administrative privileges.

In addition to row-level immutability, each row carries prevChecksum linking to the previous audit row in the same organization. Together with checksum, this produces a per-org SHA-256 hash chain. Verifying the chain end-to-end detects insertion, deletion, or alteration of any row between two timestamps.

Retention pruning is the one supported path that removes audit rows. It requires both the breeze_audit_admin Postgres role and the breeze.allow_audit_retention='1' session GUC, and re-anchors the chain on the surviving rows so the integrity check continues to pass after old data ages out. The platform’s audit retention worker handles this — operators do not prune by hand.

The following actions are tracked for compliance reporting:

  • data.access
  • data.export
  • device.create, device.delete
  • policy.update, policy.evaluate, automation.policy.evaluate
  • script.execute
  • organization.update

Audit events are written asynchronously via createAuditLogAsync, which inserts into the database without blocking the request. If the insert fails, the error is logged to the console (suppressed in test environments).

Two helper functions are available in application code:

  • writeAuditEvent(c, event) — Low-level function that accepts a request-like context and an AuditEventInput object. Extracts the client IP via getTrustedClientIp, which prefers cf-connecting-ip, then x-forwarded-for, then x-real-ip, and is gated by the TRUST_PROXY_HEADERS env var so untrusted hops can’t spoof the source IP. Events with no orgId are silently dropped (with a console warning in non-test environments).
  • writeRouteAudit(c, event) — Convenience wrapper for route handlers that automatically extracts actorId and actorEmail from the Hono auth context.

The audit log list endpoints support the following query parameters for filtering:

| Parameter | Type | Description | |---|---|---| | page | String (number) | Page number (default: 1) | | limit | String (number) | Results per page (default: 50, max: 100) | | user | String | Filter by actor email or user name (ILIKE match) | | action | String | Filter by action name (ILIKE match) | | resource | String | Filter by resource type or resource name (ILIKE match) | | from | ISO 8601 datetime | Start of date range | | to | ISO 8601 datetime | End of date range | | skipCount | Boolean | When true, the list endpoint skips the count(*) over the filtered set and returns only the page itself. Use it on high-frequency dashboard widgets where the total isn’t displayed. |

The search endpoint (GET /search) adds a q parameter that performs a full-text ILIKE search across action, actor email, resource type, resource name, and the JSON-cast details field. Search conditions are combined with any active filters using AND logic.

The dashboard’s audit-log viewer humanizes raw action codes (e.g. device.create renders as “Created device”) and, for actorType=agent rows, qualifies the actor column with the device hostname instead of showing only the agent identifier.

Audit log retention is configured per organization in the audit_retention_policies table:

| Field | Type | Default | Description | |---|---|---|---| | retentionDays | Integer | 365 | Number of days to retain audit logs | | archiveToS3 | Boolean | false | Whether to archive logs to S3 before deletion | | lastCleanupAt | Timestamp | null | When the last cleanup ran |


Agent diagnostic logs capture internal runtime information from the Breeze agent running on managed devices. These are useful for troubleshooting agent connectivity, update failures, and component-level issues.

| Field | Type | Description | |---|---|---| | id | UUID | Unique identifier | | deviceId | UUID | The device this agent runs on | | orgId | UUID | Organization scope | | timestamp | Timestamp | When the log entry was generated on the agent | | level | Enum | Severity: debug, info, warn, or error | | component | String (100) | Agent subsystem that generated the log (e.g., updater, heartbeat, ws) | | message | Text | The log message | | fields | JSONB | Structured key-value metadata attached to the log entry | | agentVersion | String (50) | Version of the agent that generated the entry | | createdAt | Timestamp | When the record was inserted into the database |

The agent_logs table has the following indexes for efficient querying:

  • agent_logs_device_idx — By device ID
  • agent_logs_org_ts_idx — By organization ID and timestamp (composite)
  • agent_logs_level_component_idx — By level and component (composite)
  • agent_logs_timestamp_idx — By timestamp

Agents submit diagnostic logs via POST /agents/:id/logs with a JSON body containing a logs array (maximum 500 entries per request). Each entry must include:

  • timestamp (ISO 8601 datetime)
  • level (debug, info, warn, error)
  • component (string, max 100 characters)
  • message (string)
  • fields (optional object with arbitrary key-value pairs)
  • agentVersion (optional string, max 50 characters)

Logs are batch-inserted in groups of 100 rows. The endpoint returns the number of successfully inserted records.

Diagnostic logs are queried via GET /devices/:id/diagnostic-logs with the following optional parameters:

| Parameter | Type | Description | |---|---|---| | level | String | Filter by level(s), comma-separated (e.g., warn,error) | | component | String | Filter by component name (exact match) | | since | ISO 8601 datetime | Start of time range | | until | ISO 8601 datetime | End of time range | | search | String | ILIKE search on the message field | | page | String (number) | Page number | | limit | String (number) | Results per page (default max: 1000) |

Results are ordered by timestamp descending and include the total count for pagination.


Device event logs represent OS-level events collected from managed devices. There are two mechanisms for working with device event logs: stored event logs (persisted in the database) and live event log queries (real-time queries sent to the agent).

Agents periodically submit event logs that are stored in the device_event_logs table for historical querying.

| Field | Type | Description | |---|---|---| | id | UUID | Unique identifier | | deviceId | UUID | The device the event came from | | orgId | UUID | Organization scope | | timestamp | Timestamp | When the event occurred on the device | | level | Enum | Severity: info, warning, error, or critical | | category | Enum | Event category: security, hardware, application, or system | | source | String (255) | The source application or component | | eventId | String (100) | OS-specific event identifier | | message | Text | The event message | | details | JSONB | Additional structured event data | | createdAt | Timestamp | When the record was inserted |

The device_event_logs table has a unique index on (deviceId, source, eventId) to prevent duplicate events. When an agent submits events that conflict with existing records, the duplicates are silently ignored (ON CONFLICT DO NOTHING).

  • device_event_logs_device_idx — By device ID
  • device_event_logs_org_ts_idx — By organization ID and timestamp (composite)
  • device_event_logs_cat_level_idx — By category and level (composite)
  • device_event_logs_dedup_idx — Unique index on (device ID, source, event ID)

Agents submit event logs via PUT /agents/:id/eventlogs with a JSON body containing an events array. Each event must include:

  • timestamp (string, required)
  • level (info, warning, error, critical)
  • category (security, hardware, application, system)
  • source (string, required)
  • eventId (string, optional)
  • message (string, required)
  • details (optional object)

Events are batch-inserted in groups of 100. The submission is also recorded as an audit event with action agent.eventlogs.submit.

Query stored event logs for a device via GET /devices/:id/eventlogs:

| Parameter | Type | Description | |---|---|---| | category | Enum | Filter by category: security, hardware, application, system | | level | Enum | Filter by level: info, warning, error, critical | | source | String | Filter by source (exact match) | | startDate | ISO 8601 datetime | Start of time range | | endDate | ISO 8601 datetime | End of time range | | page | String (number) | Page number | | limit | String (number) | Results per page (default max: 500) |

For real-time inspection, Breeze can query the Windows Event Log (or equivalent) directly on a device by sending commands through the agent. These are available under both the /system-tools/ and /devices/ route namespaces.

GET /system-tools/devices/:deviceId/eventlogs

Returns the list of event logs available on the device (e.g., Application, Security, System). Each log includes:

| Field | Type | Description | |---|---|---| | name | String | Internal log name | | displayName | String | Human-readable display name | | recordCount | Number | Number of records in the log | | maxSize | Number | Maximum log size in bytes | | retentionDays | Number | Configured retention period | | lastWriteTime | String | Timestamp of the most recent entry |

GET /system-tools/devices/:deviceId/eventlogs/:name

Returns metadata for a specific event log by name (case-insensitive match).

GET /system-tools/devices/:deviceId/eventlogs/:name/events

| Parameter | Type | Description | |---|---|---| | page | String (number) | Page number | | limit | String (number) | Results per page | | level | Enum | Filter by level: information, warning, error, critical, verbose | | source | String | Filter by source | | startTime | ISO 8601 datetime | Start of time range | | endTime | ISO 8601 datetime | End of time range | | eventId | String (number) | Filter by OS event ID |

Each returned event entry contains:

| Field | Type | Description | |---|---|---| | recordId | Number | Record identifier within the log | | timeCreated | String | When the event was generated | | level | Enum | information, warning, error, critical, or verbose | | source | String | Source application or component | | eventId | Number | OS-specific event ID | | message | String | Event message text | | category | String | Event category | | user | String or null | User associated with the event | | computer | String | Computer name | | rawXml | String | Raw XML representation (when available) |

GET /system-tools/devices/:deviceId/eventlogs/:name/events/:recordId

Returns a single event log entry by its record ID.


All audit log endpoints are mounted at /api/v1/audit-logs and require JWT authentication.

GET /audit-logs

Returns entries in a flattened format used by the AuditLogViewer UI component. Supports page, limit, user, action, resource, from, and to query parameters.

Response shape:

{
"entries": [
{
"id": "uuid",
"timestamp": "2026-02-18T12:00:00.000Z",
"action": "device.create",
"resource": "My Workstation",
"resourceType": "device",
"details": "{}",
"ipAddress": "192.168.1.10",
"userAgent": "Mozilla/5.0...",
"sessionId": null,
"user": {
"name": "Jane Admin",
"email": "jane@example.com",
"role": "user",
"department": ""
},
"changes": {
"before": {},
"after": {}
}
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 142,
"totalPages": 3
}
}
GET /audit-logs/logs

Returns entries in a detailed format used by RecentActivity and UserActivityReport components. Same query parameters as above.

Response shape:

{
"data": [
{
"id": "uuid",
"timestamp": "2026-02-18T12:00:00.000Z",
"user": {
"id": "uuid",
"name": "Jane Admin",
"email": "jane@example.com",
"role": "user"
},
"action": "device.create",
"resource": {
"type": "device",
"id": "uuid",
"name": "My Workstation"
},
"category": "device",
"result": "success",
"ipAddress": "192.168.1.10",
"userAgent": "Mozilla/5.0...",
"details": {}
}
],
"pagination": { "page": 1, "limit": 50, "total": 142, "totalPages": 3 }
}
GET /audit-logs/logs/:id

Returns a single audit log entry in full format. Returns 404 if the entry does not exist or belongs to a different organization.

GET /audit-logs/search?q=login

Full-text search across action, actor email, resource type, resource name, and details. The q parameter is required (minimum 1 character). All standard filter parameters (user, action, resource, from, to) can be combined with the search query.

POST /agents/:id/logs

Requires agent bearer token authentication. Accepts a JSON body:

{
"logs": [
{
"timestamp": "2026-02-18T12:00:00Z",
"level": "warn",
"component": "updater",
"message": "Update check failed: connection timeout",
"fields": { "retryCount": 3, "endpoint": "https://updates.example.com" },
"agentVersion": "1.4.2"
}
]
}

Maximum 500 log entries per request. Returns { "received": <count> } with status 201.

PUT /agents/:id/eventlogs

Requires agent bearer token authentication.

{
"events": [
{
"timestamp": "2026-02-18T11:45:00Z",
"level": "error",
"category": "application",
"source": "Application Error",
"eventId": "1000",
"message": "Faulting application name: app.exe",
"details": { "faultModule": "ntdll.dll" }
}
]
}

Returns { "success": true, "count": <inserted> }.

GET /devices/:id/eventlogs?category=security&level=error&startDate=2026-02-01T00:00:00Z

Requires JWT authentication. Returns paginated results from the device_event_logs table.


All audit log queries are automatically scoped to the caller’s organization. The orgCondition from the authenticated user’s context is applied to every database query, ensuring that users can only see audit logs for their own organization. Partner-scoped users may see logs across their managed organizations.


  1. Verify that the action handler calls writeAuditEvent() or writeRouteAudit(). Events are written asynchronously, so failures do not block request handling.
  2. Check the API server console for [audit] Failed to write audit log: messages, which indicate database insert failures.
  3. Confirm the orgId is being passed. Events with a null or undefined orgId are silently dropped. Look for [audit] Dropped event (no orgId): warnings in the console.
  4. Ensure the actorType value is one of the four valid enum values: user, api_key, agent, or system.
  1. Confirm the agent is shipping logs by checking the response from POST /agents/:id/logs. A 201 with { "received": N } indicates the API accepted the batch.
  2. Verify the agent ID maps to a valid device. The endpoint looks up the device by agentId and returns 404 if no match is found.
  3. Check batch insert errors in the API console: [AgentLogs] Error batch inserting logs for device <id>.
  4. Ensure each log entry has valid level, component, and timestamp fields matching the expected schema.
  1. For stored events, confirm the agent has submitted events via PUT /agents/:id/eventlogs. Check the count in the response.
  2. For live queries, verify the device is online and connected to the WebSocket. Live queries have a 30-second timeout.
  3. Check that your query filters (category, level, source, date range) are not too restrictive. Try removing filters to get unfiltered results first.
  4. For deduplication issues, note that the device_event_logs table has a unique index on (deviceId, source, eventId). If the same event is submitted again, it is silently skipped.
  1. Verify your filters and date range are correct. The export endpoints apply the same filtering logic as the list endpoints.
  2. Check that the organization has audit log entries in the specified time range.
  3. Note that exports are capped at 10,000 rows. If you need more, narrow the date range or filter criteria and export in batches.
  1. Confirm the target device is online and the agent is connected.
  2. The command timeout is 30 seconds. Large event logs on the device may exceed this timeout.
  3. Use the level, source, and eventId filters to narrow the query and reduce response size.
  4. Check the API console for Failed to parse agent response for event messages, which may indicate the agent returned an unexpected format.