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. The link is sealed at commit time under a per-organization advisory lock, so concurrent writes from agents and users cannot fork the 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, and it operates by prefix-cut: the contiguous oldest segment that has aged past the retention window is deleted, and the first surviving row becomes the new chain head. There is no re-anchoring — the verifier accepts a null prevChecksum on the earliest retained row as evidence of a pruned prefix rather than a break, so the integrity check continues to pass after old data ages out.

Pruning is protected by connection-level privilege separation. The API’s normal database role (breeze_app) holds only SELECT/INSERT on the audit tables and cannot delete rows at all. Retention runs on a separate, dedicated connection authenticated as breeze_audit_admin (configured via AUDIT_ADMIN_DATABASE_URL) and still gated behind the breeze.allow_audit_retention='1' session GUC. Because deletion lives on a connection the request path never touches, a compromise of the API cannot destroy audit history. The platform’s audit retention worker is the only caller — 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.