Skip to content

Log Shipping

Log Shipping collects diagnostic logs from the Breeze agent and stores them centrally for querying, correlation, and troubleshooting. Every agent component — heartbeat, commands, patching, enrollment, discovery, terminal, and more — writes structured log entries through Go’s slog package. The logging subsystem intercepts these entries, buffers them in memory, compresses them with gzip, and ships them to the API in batches every 60 seconds. On the server side, logs are stored in PostgreSQL with indexes on device, timestamp, level, and component, making it fast to search across thousands of devices.

This is distinct from the fleet event log system (which collects Windows Event Log, syslog, and application logs from managed devices). Log Shipping specifically handles the Breeze agent’s own diagnostic output — the internal telemetry that helps you understand what the agent is doing, debug enrollment failures, diagnose command execution issues, and monitor agent health across your fleet.


LevelDescriptionNumeric Priority
debugVerbose diagnostic output for development and deep troubleshootingLowest
infoNormal operational messages (startup, enrollment, heartbeat success)Default
warnNon-fatal issues that may indicate a problem (retry, degraded state)Medium
errorFailures that require attention (command execution failed, connection lost)Highest
FieldTypeMax LengthDescription
timestampISO 8601 datetimeWhen the log entry was created on the agent
levelenumOne of: debug, info, warn, error
componentstring100 charsWhich agent subsystem produced the entry (e.g., heartbeat, commands, patching)
messagestring10,000 charsHuman-readable log message
fieldsJSON32 KBStructured key-value data (command IDs, durations, error details, etc.)
agentVersionstring50 charsVersion of the agent that produced the entry
ComponentWhat It Logs
heartbeatHeartbeat send/receive, metric collection, cert renewal
commandsCommand dispatch, execution, result reporting
patchingPatch scan, download, install, reboot handling
enrollmentEnrollment attempts, token exchange, config persistence
discoveryNetwork scan execution, host detection, result shipping
terminalPTY allocation, resize, data relay
desktopRemote desktop session lifecycle, WebRTC signaling
updaterSelf-update checks, downloads, binary replacement
mtlsCertificate loading, renewal, TLS configuration
loggingLog shipper lifecycle, buffer management

The agent’s logging subsystem has three layers:

The agent writes logs to stdout (or a file via RotatingWriter) using Go’s slog package. The format is configurable as text (default) or json.

// Create a component logger
logger := logging.L("heartbeat")
logger.Info("heartbeat sent", "statusCode", 200, "durationMs", 45)

When file logging is enabled, the RotatingWriter handles size-based rotation:

SettingDefaultDescription
Max file size50 MBRotates when the log file exceeds this size
Max backups3Keeps up to 3 rotated files (.1, .2, .3)
File permissions0600Owner read/write only
Directory permissions0700Owner read/write/execute only

Rotation works by shifting existing backups (.3 is deleted, .2 becomes .3, etc.) and renaming the current file to .1.

The Shipper intercepts all log entries via a custom slog.Handler wrapper (shippingHandler) and forwards them to the API:

  1. Every slog call passes through the shippingHandler, which writes to the local handler (stdout/file) and also enqueues the entry for remote shipping if it meets the minimum level threshold.

  2. Entries are buffered in a channel with capacity for 500 entries. If the buffer is full, entries are dropped and a counter is incremented. The dropped count is reported in the next heartbeat.

  3. Every 60 seconds (or when 500 entries accumulate), the shipper flushes the buffer. Entries are serialized as JSON and gzip-compressed.

  4. The compressed batch is POSTed to POST /api/v1/agents/:agentId/logs with Content-Encoding: gzip and Authorization: Bearer headers.

  5. On failure, the shipper retries up to 2 additional times with 1-second backoff plus random jitter. Server errors (5xx) and network errors trigger retries. Client errors (4xx) do not.

  6. On graceful shutdown, the shipper drains remaining buffered entries and ships them before exiting.


Agents ship logs to:

POST /api/v1/agents/:agentId/logs
Content-Type: application/json
Content-Encoding: gzip
Authorization: Bearer AGENT_TOKEN
{
"logs": [
{
"timestamp": "2026-02-15T14:30:00.000Z",
"level": "info",
"component": "heartbeat",
"message": "heartbeat sent successfully",
"fields": {
"statusCode": 200,
"durationMs": 45
},
"agentVersion": "1.2.0"
}
]
}
LimitValue
Max entries per request500
Max decompressed payload10 MB
Max message length10,000 characters
Max component length100 characters
Max fields JSON size32 KB
Batch insert size100 rows per database insert
StatusMeaning
201All logs accepted and stored
200Empty batch (0 logs) — no-op
207Partial success — some logs stored, some failed
400Invalid request body, decompression failure, or validation error
404Agent/device not found
500All logs failed to insert

Diagnostic logs are queried per-device through the devices API:

Terminal window
curl "/api/v1/devices/:deviceId/diagnostic-logs?level=warn,error&component=patching&since=2026-02-01T00:00:00Z&search=failed" \
-H "Authorization: Bearer $TOKEN"
ParameterTypeDescription
levelstringComma-separated levels to include (e.g., warn,error)
componentstringExact component name to filter by
sinceISO 8601 datetimeOnly logs at or after this time
untilISO 8601 datetimeOnly logs at or before this time
searchstringCase-insensitive text search across message and fields
pageintegerPage number (default 1)
limitintegerResults per page (default 1000)
{
"logs": [
{
"id": "uuid",
"deviceId": "uuid",
"orgId": "uuid",
"timestamp": "2026-02-15T14:30:00.000Z",
"level": "error",
"component": "patching",
"message": "patch installation failed",
"fields": {
"patchId": "KB5034441",
"exitCode": 1603,
"error": "HRESULT 0x80070005: Access denied"
},
"agentVersion": "1.2.0",
"createdAt": "2026-02-15T14:30:05.000Z"
}
],
"total": 47,
"limit": 1000,
"offset": 0
}

For searching logs across multiple devices and organizations, use the fleet log search endpoint:

Terminal window
curl -X POST /api/v1/logs/search \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "patch installation failed",
"timeRange": {
"start": "2026-02-01T00:00:00Z",
"end": "2026-02-28T23:59:59Z"
},
"level": ["error", "critical"],
"category": ["system"],
"deviceIds": ["UUID1", "UUID2"],
"limit": 100,
"sortBy": "timestamp",
"sortOrder": "desc"
}'
FeatureEndpointDescription
AggregationGET /api/v1/logs/aggregationTime-bucketed counts grouped by level, category, source, or device
TrendsGET /api/v1/logs/trendsTop error patterns and trending log sources
Correlation detectionPOST /api/v1/logs/correlation/detectFind patterns appearing across multiple devices
Correlation listGET /api/v1/logs/correlationList detected correlations with status
Saved queriesGET/POST/DELETE /api/v1/logs/queriesSave and reuse common search filters

When the agent’s buffer is full (500 entries), new log entries are dropped. The agent tracks dropped entries and reports the count in each heartbeat. After a successful heartbeat, the counter resets to zero.

You can monitor this by checking the heartbeat data for a device. A non-zero dropped log count indicates the agent is producing logs faster than it can ship them. Common causes:

  • Network latency to the API server is high
  • Debug-level logging is enabled, producing high volume
  • Agent is under heavy load (e.g., running many concurrent commands)

To mitigate, increase the shipping level to warn or error, or investigate the root cause of the high log volume.


MethodPathDescription
POST/api/v1/agents/:id/logsShip a batch of diagnostic log entries
MethodPathDescription
GET/api/v1/devices/:id/diagnostic-logsQuery diagnostic logs for a specific device
MethodPathDescription
POST/api/v1/logs/searchSearch logs across the fleet with filters
GET/api/v1/logs/aggregationTime-bucketed log aggregation
GET/api/v1/logs/trendsTrending log patterns
POST/api/v1/logs/correlation/detectDetect cross-device log correlations
GET/api/v1/logs/correlation/detect/:jobIdCheck status of an async correlation detection job
GET/api/v1/logs/correlationList detected correlations
GET/api/v1/logs/queriesList saved log search queries
POST/api/v1/logs/queriesCreate a saved log search query
GET/api/v1/logs/queries/:idGet a saved query by ID
DELETE/api/v1/logs/queries/:idDelete a saved query

Logs not appearing for a device Verify the agent is enrolled and the log shipper is initialized. The shipper starts after enrollment when the agent has a valid server URL, agent ID, and auth token. Check the agent’s local stdout for [log-shipper] messages indicating shipping errors.

Only seeing info-level and above The default minimum shipping level is info. To capture debug entries, dynamically adjust the shipping level via the agent’s configuration or use SetShipperLevel("debug"). Note that debug-level logging significantly increases volume and may cause buffer drops.

Logs delayed by up to 60 seconds This is expected. The shipper flushes every 60 seconds or when the buffer reaches 500 entries, whichever comes first. For time-critical debugging, lower the minimum level to produce more entries and trigger faster flushes.

“Device not found” when agent ships logs The ingest endpoint looks up the device by agentId (the :id path parameter). If the agent was recently re-enrolled with a new agent ID, the old ID will return 404. Verify the agent’s configuration file has the correct agent ID.

Search returns no results despite logs existing The search parameter on the diagnostic logs endpoint uses PostgreSQL ILIKE against the message column and the fields JSON cast to text. Ensure your search term does not contain special SQL characters. Also verify the since/until time range includes the logs you are looking for.

High dropped log count in heartbeat The agent’s buffer holds 500 entries. If the shipper cannot deliver batches fast enough (network issues, API downtime), entries are dropped. The dropped count resets after each successful heartbeat. Investigate network connectivity between the agent and API, or raise the minimum shipping level to reduce volume.