Logging
Proxima Console uses Go's slog package with JSON output for all structured logging across the agent and backend services.
Basic Usage
slog.Info("host registered",
"component", "worker",
"host_id", hostID,
"client", clientSlug,
)
This produces a JSON log line:
{
"time": "2025-01-15T10:30:00Z",
"level": "INFO",
"msg": "host registered",
"component": "worker",
"host_id": "abc-123",
"client": "acme-corp"
}
Log Levels
| Level | Purpose | Example |
|---|---|---|
DEBUG | Verbose output for development and troubleshooting | Raw NATS message payload, SQL query details |
INFO | Normal operational events | Host registered, server started, migration applied |
WARN | Recoverable issues that may need attention | Retry attempt, deprecated config used |
ERROR | Failures that need investigation | Database connection lost, message processing failed |
Set the log level via the PROXIMA_LOG_LEVEL environment variable. Defaults to info.
Required Fields
Always Include
component: Identifies the source subsystem. One of:api,worker,agent,store,nats,server
On Errors
error: The error value from the failed operation
slog.Error("failed to process message",
"component", "worker",
"error", err,
)
Contextual Fields
Include these fields when available:
| Field | When to Use |
|---|---|
client | Any operation scoped to a client |
environment | Any operation scoped to an environment |
host_id | Any operation involving a specific host |
request_id | HTTP request handling |
trace_id | Distributed tracing context |
span_id | Distributed tracing context |
duration_ms | Timed operations (queries, HTTP calls) |
Rules
1. JSON Only
All log output must be JSON. No plain text log lines. Configure slog with slog.NewJSONHandler at startup.
2. slog Everywhere
Use slog for all logging. Never use fmt.Println, log.Printf, or any other logging mechanism.
3. Structured Fields
Pass data as key-value pairs, not interpolated into the message string:
// Correct
slog.Info("host updated", "host_id", id, "fields_changed", 3)
// Wrong
slog.Info(fmt.Sprintf("host %s updated with %d fields", id, 3))
4. snake_case Keys
All log field keys use snake_case:
// Correct
"host_id", hostID
"client_slug", slug
"duration_ms", elapsed
// Wrong
"hostId", hostID
"ClientSlug", slug
5. Log at Boundaries
Log at system boundaries (HTTP handlers, NATS message handlers, worker entry points). Avoid logging inside loops or deep in utility functions.
// Correct -- log once at the handler level
func (h *Handler) ListHosts(w http.ResponseWriter, r *http.Request) {
hosts, err := h.store.ListHosts(r.Context(), params)
if err != nil {
slog.Error("failed to list hosts", "component", "api", "error", err)
// ...
}
slog.Debug("hosts listed", "component", "api", "count", len(hosts))
}
// Wrong -- logging inside the store method
func (s *Store) ListHosts(ctx context.Context, params ListParams) ([]Host, error) {
slog.Debug("querying hosts...") // Don't do this
// ...
}
6. Never Log Secrets
Never log API keys, tokens, passwords, connection strings, or any sensitive data. If you need to log an identifier for debugging, log only a prefix or hash.
7. One Line per Event
Each log call produces exactly one JSON line. Do not use multi-line log messages or log the same event across multiple calls.