Unified Logger: Kill the Observability Blind Spot

Context

Plugin loading is the most fragile part of QNTX startup. Loom has intermittently failed to start across many restarts. When it fails, there is zero trace in the structured log file (tmp/qntx-*.log). The only evidence exists in terminal output — which is gone the moment the terminal scrolls or the session ends.

This session proved:

The root cause is not a timeout value or missing retry logic. The root cause is split observability: two loggers, one blind.

The Problem

There are two loggers:

  1. logger.Logger (global) — writes to terminal only (os.Stdout). Created in init().
  2. serverLogger (server) — writes to terminal + WebSocket + file. Created in server/init.go:createServerLogger().

The plugin loader uses logger.Logger.Named("plugin-loader") — terminal only. Everything it logs is invisible in the structured log file.

The server logger adds three things on top of the base logger:

The file core has no dependency on the server. It only needs a log path, which comes from config. There is no reason it can't be part of the global logger from the start.

The WebSocket core does depend on the server — but that's the browser UI log panel, not the structured log file.

What Needs to Change

1. Add file output to logger.Logger early

In main.go, after loading config but before plugin loading starts:

Then pluginLogger = logger.Logger.Named("plugin-loader") automatically inherits file output.

2. Server logger becomes an extension, not a replacement

createServerLogger should add the WebSocket core on top of the already-file-enabled global logger. It should not recreate the file core — that's already there.

3. Remove the bandaid

The code in cmd/qntx/main.go that logs plugin results through defaultServer.GetLogger() after async loading completes — this becomes unnecessary because m.logger (the plugin loader) now writes to the file directly.

Similarly, server/server.go:GetLogger() was added solely for this workaround.

Files to Modify

Verification

  1. make test
  2. make dev, then check tmp/qntx-*.log for:
  3. Kill loom's binary mid-startup and verify the failure is visible in the structured log