fix: reduce syslog flooding by downgrading routine logs to debug level

Addresses issue #861 - syslog flooded on docker host

Many routine operational messages were being logged at INFO level,
causing excessive log volume when monitoring multiple VMs/containers.
These messages are now logged at DEBUG level:

- Guest threshold checking (every guest, every poll cycle)
- Storage threshold checking (every storage, every poll cycle)
- Host agent linking messages
- Filesystem inclusion in disk calculation
- Guest agent disk usage replacement
- Polling start/completion messages
- Alert cleanup and save messages

Users can set LOG_LEVEL=debug to see these messages if needed for
troubleshooting. The default INFO level now produces significantly
less log output.

Also updated documentation in CONFIGURATION.md and DOCKER.md to:
- Clarify what each log level includes
- Add tip about using LOG_LEVEL=warn for minimal logging
This commit is contained in:
rcourtman 2025-12-18 23:24:17 +00:00
parent c97dffb81a
commit f29d8b1b43
5 changed files with 36 additions and 23 deletions

View file

@ -87,8 +87,19 @@ Environment variables take precedence over `system.json`.
| Variable | Description | Default |
|----------|-------------|---------|
| `FRONTEND_PORT` | Public listening port | `7655` |
| `LOG_LEVEL` | Log verbosity (`debug`, `info`, `warn`, `error`) | `info` |
| `LOG_LEVEL` | Log verbosity (see below) | `info` |
| `LOG_FORMAT` | Log output format (`auto`, `json`, `console`) | `auto` |
#### Log Levels
| Level | Description |
|-------|-------------|
| `error` | Only errors and critical issues |
| `warn` | Errors + warnings (recommended for minimal logging) |
| `info` | Standard operational messages (startup, connections, alerts) |
| `debug` | Verbose output including per-guest/storage polling details |
> **Tip**: If your syslog is being flooded with Pulse messages, set `LOG_LEVEL=warn` to significantly reduce log volume while still capturing important events.
| `PULSE_PUBLIC_URL` | Public URL for notifications/OIDC | `""` |
| `ALLOWED_ORIGINS` | CORS allowed domains | `""` (Same origin) |
| `DISCOVERY_ENABLED` | Auto-discover nodes | `false` |

View file

@ -57,7 +57,9 @@ Pulse is configured via environment variables.
| `API_TOKENS` | Comma-separated API tokens | *(unset)* |
| `DISCOVERY_SUBNET` | Custom CIDR to scan | *(auto)* |
| `ALLOWED_ORIGINS` | CORS allowed domains | *(none)* |
| `LOG_LEVEL` | Log verbosity | `info` |
| `LOG_LEVEL` | Log verbosity (`debug`, `info`, `warn`, `error`) | `info` |
> **Tip**: Set `LOG_LEVEL=warn` to reduce log volume while still capturing important events.
<details>
<summary><strong>Advanced: Resource Limits & Healthcheck</strong></summary>

View file

@ -2057,7 +2057,7 @@ func (m *Manager) CheckGuest(guest interface{}, instanceName string) {
// Debug logging for high memory VMs
if memUsage > 85 {
log.Info().
log.Debug().
Str("vm", name).
Float64("memUsage", memUsage).
Str("status", status).
@ -2100,7 +2100,7 @@ func (m *Manager) CheckGuest(guest interface{}, instanceName string) {
monitorOnly := settings.MonitorOnly
if monitorOnly || m.guestHasMonitorOnlyAlerts(guestID) {
log.Info().
log.Debug().
Str("guest", name).
Bool("monitorOnly", monitorOnly).
Msg("Pulse monitor-only status applied")
@ -2185,7 +2185,7 @@ func (m *Manager) CheckGuest(guest interface{}, instanceName string) {
}
// Check each metric
log.Info().
log.Debug().
Str("guest", name).
Float64("cpu", cpu).
Float64("memory", memUsage).
@ -4341,7 +4341,7 @@ func (m *Manager) CheckStorage(storage models.Storage) {
// Check usage if storage has valid data (even if not currently active on this node)
// In clusters, storage may show as inactive on nodes where it's not currently mounted
// but we still want to alert on high usage
log.Info().
log.Debug().
Str("storage", storage.Name).
Str("id", storage.ID).
Float64("usage", storage.Usage).
@ -8140,7 +8140,7 @@ func (m *Manager) SaveActiveAlerts() error {
return fmt.Errorf("failed to rename active alerts file: %w", err)
}
log.Info().Int("count", len(alerts)).Msg("Saved active alerts to disk")
log.Debug().Int("count", len(alerts)).Msg("Saved active alerts to disk")
return nil
}
@ -8316,7 +8316,7 @@ func (m *Manager) CleanupAlertsForNodes(existingNodes map[string]bool) {
m.mu.Lock()
defer m.mu.Unlock()
log.Info().
log.Debug().
Int("totalAlerts", len(m.activeAlerts)).
Int("existingNodes", len(existingNodes)).
Interface("nodes", existingNodes).
@ -8349,7 +8349,7 @@ func (m *Manager) CleanupAlertsForNodes(existingNodes map[string]bool) {
}
if removedCount > 0 {
log.Info().Int("removed", removedCount).Int("remaining", len(m.activeAlerts)).Msg("Cleaned up alerts for non-existent nodes")
log.Debug().Int("removed", removedCount).Int("remaining", len(m.activeAlerts)).Msg("Cleaned up alerts for non-existent nodes")
// Save the cleaned up state
go func() {
defer func() {

View file

@ -2219,7 +2219,7 @@ func (m *Monitor) ApplyHostReport(report agentshost.Report, tokenRecord *config.
linkedNodeID, linkedVMID, linkedContainerID := m.findLinkedProxmoxEntity(hostname)
if linkedNodeID != "" {
host.LinkedNodeID = linkedNodeID
log.Info().
log.Debug().
Str("hostId", identifier).
Str("hostname", hostname).
Str("linkedNodeId", linkedNodeID).
@ -2227,7 +2227,7 @@ func (m *Monitor) ApplyHostReport(report agentshost.Report, tokenRecord *config.
}
if linkedVMID != "" {
host.LinkedVMID = linkedVMID
log.Info().
log.Debug().
Str("hostId", identifier).
Str("hostname", hostname).
Str("linkedVmId", linkedVMID).
@ -2235,7 +2235,7 @@ func (m *Monitor) ApplyHostReport(report agentshost.Report, tokenRecord *config.
}
if linkedContainerID != "" {
host.LinkedContainerID = linkedContainerID
log.Info().
log.Debug().
Str("hostId", identifier).
Str("hostname", hostname).
Str("linkedContainerId", linkedContainerID).
@ -5837,7 +5837,7 @@ func (m *Monitor) pollPVEInstance(ctx context.Context, instanceName string, clie
// When the instance is part of a cluster, the cluster name is used for guest IDs to prevent duplicates
// when multiple cluster nodes are configured as separate PVE instances.
func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceName string, clusterName string, isCluster bool, client PVEClientInterface, nodeEffectiveStatus map[string]string) bool {
log.Info().
log.Debug().
Str("instance", instanceName).
Str("clusterName", clusterName).
Bool("isCluster", isCluster).
@ -6189,7 +6189,7 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam
}
if len(includedFS) > 0 {
log.Info().
log.Debug().
Str("instance", instanceName).
Str("vm", res.Name).
Int("vmid", res.VMID).
@ -6223,7 +6223,7 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam
diskFree = totalBytes - usedBytes
diskUsage = safePercentage(float64(usedBytes), float64(totalBytes))
log.Info().
log.Debug().
Str("instance", instanceName).
Str("vm", res.Name).
Int("vmid", res.VMID).
@ -6596,7 +6596,7 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam
nodesWithResources[res.Node] = true
}
log.Info().
log.Debug().
Str("instance", instanceName).
Int("nodesInResources", len(nodesWithResources)).
Int("totalVMsFromResources", len(allVMs)).
@ -6719,7 +6719,7 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam
m.pollReplicationStatus(ctx, instanceName, client, allVMs)
log.Info().
log.Debug().
Str("instance", instanceName).
Int("vms", len(allVMs)).
Int("containers", len(allContainers)).

View file

@ -208,7 +208,7 @@ func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, clu
}
}
log.Info().
log.Debug().
Str("instance", instanceName).
Int("totalNodes", len(nodes)).
Int("onlineNodes", onlineNodes).
@ -864,7 +864,7 @@ func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, clu
}
duration := time.Since(startTime)
log.Info().
log.Debug().
Str("instance", instanceName).
Int("totalVMs", len(allVMs)).
Int("successfulNodes", successfulNodes).
@ -913,7 +913,7 @@ func (m *Monitor) pollContainersWithNodes(ctx context.Context, instanceName stri
}
}
log.Info().
log.Debug().
Str("instance", instanceName).
Int("totalNodes", len(nodes)).
Int("onlineNodes", onlineNodes).
@ -1173,7 +1173,7 @@ func (m *Monitor) pollContainersWithNodes(ctx context.Context, instanceName stri
}
duration := time.Since(startTime)
log.Info().
log.Debug().
Str("instance", instanceName).
Int("totalContainers", len(allContainers)).
Int("successfulNodes", successfulNodes).
@ -1236,7 +1236,7 @@ func (m *Monitor) pollStorageWithNodes(ctx context.Context, instanceName string,
}
}
log.Info().
log.Debug().
Str("instance", instanceName).
Int("totalNodes", len(nodes)).
Int("onlineNodes", onlineNodes).
@ -1623,7 +1623,7 @@ func (m *Monitor) pollStorageWithNodes(ctx context.Context, instanceName string,
Int("failedNodes", failedNodes).
Msg("All nodes failed to retrieve storage - check Proxmox API permissions for Datastore.Audit on all storage")
} else {
log.Info().
log.Debug().
Str("instance", instanceName).
Int("totalStorage", len(allStorage)).
Int("successfulNodes", successfulNodes).