Skip to main content

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

LevelPurposeExample
DEBUGVerbose output for development and troubleshootingRaw NATS message payload, SQL query details
INFONormal operational eventsHost registered, server started, migration applied
WARNRecoverable issues that may need attentionRetry attempt, deprecated config used
ERRORFailures that need investigationDatabase 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:

FieldWhen to Use
clientAny operation scoped to a client
environmentAny operation scoped to an environment
host_idAny operation involving a specific host
request_idHTTP request handling
trace_idDistributed tracing context
span_idDistributed tracing context
duration_msTimed 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.