From 19a2cac355d51ca144c9e03ac148709f9efb129a Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 22 Nov 2025 07:43:48 +0000 Subject: [PATCH] Add log level control for docker agent Related to #742 --- cmd/pulse-docker-agent/main.go | 32 ++++++++++++++++++++++++++++++- docs/DOCKER_MONITORING.md | 4 ++++ internal/dockeragent/agent.go | 35 +++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/cmd/pulse-docker-agent/main.go b/cmd/pulse-docker-agent/main.go index 4af995c..6fc7142 100644 --- a/cmd/pulse-docker-agent/main.go +++ b/cmd/pulse-docker-agent/main.go @@ -50,7 +50,9 @@ func main() { cfg := loadConfig() - logger := zerolog.New(os.Stdout).With().Timestamp().Logger() + zerolog.SetGlobalLevel(cfg.LogLevel) + + logger := zerolog.New(os.Stdout).Level(cfg.LogLevel).With().Timestamp().Logger() cfg.Logger = &logger agent, err := dockeragent.New(cfg) @@ -87,6 +89,7 @@ func loadConfig() dockeragent.Config { envSwarmTasks := utils.GetenvTrim("PULSE_SWARM_TASKS") envIncludeContainers := utils.GetenvTrim("PULSE_INCLUDE_CONTAINERS") envCollectDisk := utils.GetenvTrim("PULSE_COLLECT_DISK") + envLogLevel := utils.GetenvTrim("LOG_LEVEL") defaultInterval := 30 * time.Second if envInterval != "" { @@ -120,6 +123,11 @@ func loadConfig() dockeragent.Config { collectDiskDefault = utils.ParseBool(envCollectDisk) } + logLevelDefault := "info" + if envLogLevel != "" { + logLevelDefault = envLogLevel + } + urlFlag := flag.String("url", envURL, "Pulse server URL (e.g. http://pulse:7655)") tokenFlag := flag.String("token", envToken, "Pulse API token (required)") intervalFlag := flag.Duration("interval", defaultInterval, "Reporting interval (e.g. 30s)") @@ -128,6 +136,7 @@ func loadConfig() dockeragent.Config { insecureFlag := flag.Bool("insecure", utils.ParseBool(envInsecure), "Skip TLS certificate verification") noAutoUpdateFlag := flag.Bool("no-auto-update", utils.ParseBool(envNoAutoUpdate), "Disable automatic agent updates") runtimeFlag := flag.String("runtime", envRuntime, "Container runtime to expect (auto, docker, podman)") + logLevelFlag := flag.String("log-level", logLevelDefault, "Log level: debug, info, warn, error") var targetFlags stringFlagList flag.Var(&targetFlags, "target", "Pulse target in url|token[|insecure] format. Repeat to send to multiple Pulse instances") var containerStateFlags stringFlagList @@ -211,6 +220,12 @@ func loadConfig() dockeragent.Config { interval = 30 * time.Second } + logLevel, err := parseLogLevel(*logLevelFlag) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + containerStates := make([]string, 0) if len(containerStateFlags) > 0 { containerStates = append(containerStates, containerStateFlags.Values()...) @@ -235,9 +250,24 @@ func loadConfig() dockeragent.Config { IncludeTasks: *includeTasksFlag, IncludeContainers: *includeContainersFlag, CollectDiskMetrics: *collectDiskFlag, + LogLevel: logLevel, } } +func parseLogLevel(value string) (zerolog.Level, error) { + normalized := strings.ToLower(strings.TrimSpace(value)) + if normalized == "" { + return zerolog.InfoLevel, nil + } + + level, err := zerolog.ParseLevel(normalized) + if err != nil { + return zerolog.InfoLevel, fmt.Errorf("invalid log level %q: must be debug, info, warn, or error", value) + } + + return level, nil +} + func parseTargetSpecs(specs []string) ([]dockeragent.TargetConfig, error) { targets := make([]dockeragent.TargetConfig, 0, len(specs)) for _, spec := range specs { diff --git a/docs/DOCKER_MONITORING.md b/docs/DOCKER_MONITORING.md index b71b4e8..f8e2656 100644 --- a/docs/DOCKER_MONITORING.md +++ b/docs/DOCKER_MONITORING.md @@ -220,6 +220,8 @@ docker run -d \ ghcr.io/rcourtman/pulse-docker-agent:latest ``` +Add `-e LOG_LEVEL=warn` when your container logs feed into journald or another centralized collector and you want quieter steady-state logging. + > **Note**: Official images for `linux/amd64` and `linux/arm64` are published to `ghcr.io/rcourtman/pulse-docker-agent`. To test local changes, run `docker build --target agent_runtime -t pulse-docker-agent:test .` from the repository root. `--pid=host`, `--uts=host`, and the `/etc/machine-id` bind keep host metadata stable so Pulse doesn’t think the container itself is the Docker host. Auto-update is disabled in the image by default; rebuild or override `PULSE_NO_AUTO_UPDATE=false` only if you manage upgrades outside of your orchestrator. Expect to grant the container the same level of Docker socket access as the systemd service—running inside Docker doesn’t sandbox the agent from the host. @@ -232,6 +234,7 @@ docker run -d \ | `--token`, `PULSE_TOKEN`| Pulse API token with `docker:report` scope (required). | — | | `--target`, `PULSE_TARGETS` | One or more `url|token[|insecure]` entries to fan-out reports to multiple Pulse servers. Separate entries with `;` or repeat the flag. | — | | `--interval`, `PULSE_INTERVAL` | Reporting cadence (supports `30s`, `1m`, etc.). | `30s` | +| `--log-level`, `LOG_LEVEL` | Log verbosity (`debug`, `info`, `warn`, `error`). Use `debug` only while actively troubleshooting to avoid noisy journals. | `info` | | `--runtime`, `PULSE_RUNTIME` | Container runtime to target (`docker`, `podman`, `auto`). | `docker` | | `--container-socket`, `PULSE_CONTAINER_SOCKET` / `CONTAINER_HOST` | Explicit runtime socket path or `unix://` URI. | Runtime default | | `--rootless`, `PULSE_RUNTIME_ROOTLESS` | Install/manage the agent as a user service (Podman). | Auto (rootful) | @@ -260,6 +263,7 @@ Need the alerts but at a different tone? The same Containers tab exposes global ## Testing and troubleshooting - Run with `--interval 15s --insecure` in a terminal to see log output while testing. +- Adjust verbosity with `--log-level` or `LOG_LEVEL` (`info` by default). Drop to `warn` for quiet steady-state logs; use `debug` only when actively debugging. - Ensure the Pulse API token has not expired or been regenerated. - If `pulse-docker-agent` reports `Cannot connect to the Docker daemon`, verify the socket path and permissions. - Check Pulse (`/containers` tab) for the latest heartbeat time. Hosts are marked offline if they stop reporting for >4× the configured interval. diff --git a/internal/dockeragent/agent.go b/internal/dockeragent/agent.go index 1048619..c7b5f97 100644 --- a/internal/dockeragent/agent.go +++ b/internal/dockeragent/agent.go @@ -57,6 +57,7 @@ type Config struct { IncludeTasks bool IncludeContainers bool CollectDiskMetrics bool + LogLevel zerolog.Level Logger *zerolog.Logger } @@ -81,20 +82,20 @@ const ( // Agent collects Docker metrics and posts them to Pulse. type Agent struct { - cfg Config - docker *client.Client - daemonHost string - runtime RuntimeKind - runtimeVer string - supportsSwarm bool - httpClients map[bool]*http.Client - logger zerolog.Logger - machineID string - hostName string - cpuCount int - targets []TargetConfig - allowedStates map[string]struct{} - stateFilters []string + cfg Config + docker *client.Client + daemonHost string + runtime RuntimeKind + runtimeVer string + supportsSwarm bool + httpClients map[bool]*http.Client + logger zerolog.Logger + machineID string + hostName string + cpuCount int + targets []TargetConfig + allowedStates map[string]struct{} + stateFilters []string hostID string prevContainerCPU map[string]cpuSample preCPUStatsFailures int @@ -158,8 +159,12 @@ func New(cfg Config) (*Agent, error) { } logger := cfg.Logger + if zerolog.GlobalLevel() == zerolog.DebugLevel && cfg.LogLevel != zerolog.DebugLevel { + zerolog.SetGlobalLevel(cfg.LogLevel) + } + if logger == nil { - defaultLogger := zerolog.New(os.Stdout).With().Timestamp().Str("component", "pulse-docker-agent").Logger() + defaultLogger := zerolog.New(os.Stdout).Level(cfg.LogLevel).With().Timestamp().Str("component", "pulse-docker-agent").Logger() logger = &defaultLogger } else { scoped := logger.With().Str("component", "pulse-docker-agent").Logger()