logger

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: MIT Imports: 3 Imported by: 0

README

kite-mcp-logger

Go Reference

Context-aware structured logger port for the algo2go ecosystem. Defines the Logger interface (over log/slog) plus three implementations: SlogAdapter (production wrapper), Noop (silent for tests/init), and Capture (in-memory accumulator for assertions).

Used by Sundeepg98/kite-mcp-server across 100+ files for ctx-threaded structured logging — the foundational logging primitive for every MCP tool handler, broker adapter, scheduler tick, audit-log writer, and OAuth callback.

Why a separate module?

A Logger interface is the cleanest cross-cutting primitive — every algo2go consumer (broker dashboards, monitoring, future broker adapters, trading bots) needs structured logging with a standard port. Hosting the interface + adapters as a module:

  • Standardizes the ctx-threaded logging contract across consumers
  • Lets unrelated projects adopt Capture for table-driven tests without pulling in kite-mcp-server
  • Reduces the dep-graph weight for users who only need logging

Stability promise

v0.x — unstable. The Logger interface signatures may evolve. Pin v0.1.0 deliberately. v1.0 ships only after the public API (interface methods + adapter constructors) is reviewed for stability and at least one external consumer ships against it.

Install

go get github.com/algo2go/kite-mcp-logger@v0.1.0

Public API

Interface (port.go)
type Logger interface {
    Info(ctx context.Context, msg string, args ...any)
    Warn(ctx context.Context, msg string, args ...any)
    Error(ctx context.Context, msg string, args ...any)
    Debug(ctx context.Context, msg string, args ...any)
    With(args ...any) Logger
}
Adapters
  • NewSlogAdapter(*slog.Logger) Logger — production wrapper
  • NewNoop() Logger — silent (tests, init paths)
  • NewCapture() *CaptureLogger — in-memory accumulator with assertion helpers (AssertContains, AssertCount, Records())

Reference consumer

Sundeepg98/kite-mcp-server — used in 100+ files including:

  • app/lifecycle.go — startup/shutdown logging with structured fields
  • kc/usecases/*.go — every use case threads ctx + Logger
  • kc/audit/store.go — audit-log persistence with structured records
  • kc/papertrading/engine.go — paper-trade lifecycle logging
  • kc/alerts/evaluator.go — alert evaluation tracing

License

MIT — see LICENSE.

Authors

Original design: Sundeepg98 (Zerodha Tech). Multi-module promotion (2026-05-10): algo2go contributors.

Documentation

Overview

Package logger defines a minimal Logger port (hexagonal-style) and a few thin adapters around it. The goal is to:

  1. Decouple call sites from the concrete *slog.Logger so tests can swap in a no-op (or a capturing) implementation per test without touching package-level globals — directly enabling agent-side test isolation (one test run, one logger instance, no lingering handler state) and unblocking parallel test execution for any suite that previously serialized on slog.SetDefault.
  2. Leave the door open to future structured-logging back-ends (zap, zerolog, OpenTelemetry log SDK) without rewriting every call site — only the adapter changes.

Method signatures intentionally mirror slog.Logger's variadic `(msg string, args ...any)` shape so the migration is a search-and- replace at consumer sites: drop the `*slog.Logger` field type for `logger.Logger`, leave the call sites alone. Error is the one exception — it lifts `err error` into the signature because the existing call-site convention is `logger.Error(msg, "error", err)`, which is repetitive and easy to forget; the explicit error parameter makes the contract obvious and lets adapters attach the error with a canonical key.

This package has zero non-stdlib dependencies, so importing it from any kc/* sub-package is acyclic by construction.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsSlog

func AsSlog(l Logger) *slog.Logger

AsSlog returns the *slog.Logger from a Logger when the underlying implementation is a slogAdapter, otherwise nil. Used by the few remaining call sites that have to bridge to a slog-typed parameter during incremental migration. Returning nil rather than panicking keeps mock-Logger tests from blowing up if they hit a not-yet- migrated branch.

Types

type CaptureLogger

type CaptureLogger struct {
	// contains filtered or unexported fields
}

CaptureLogger is a Logger that records every call into an in-memory slice. Useful in tests that want to assert "we logged X" without piping through slog handlers / io.Discard / regex matches.

Records is protected by a sync.Mutex so the same logger can be used from a goroutine spawned inside the system-under-test. With returns a child that shares the SAME mutex + records slice — you can attach the parent CaptureLogger and read from it after the system writes through any number of child loggers. Each child carries its own "with" prefix that gets prepended to every record's Args.

func NewCapture

func NewCapture() *CaptureLogger

NewCapture returns an empty CaptureLogger.

func (*CaptureLogger) Debug

func (c *CaptureLogger) Debug(_ context.Context, msg string, args ...any)

func (*CaptureLogger) Error

func (c *CaptureLogger) Error(_ context.Context, msg string, err error, args ...any)

func (*CaptureLogger) Info

func (c *CaptureLogger) Info(_ context.Context, msg string, args ...any)

func (*CaptureLogger) Records

func (c *CaptureLogger) Records() []CaptureRecord

Records returns a snapshot copy of all observed records. Safe to call concurrently with logger writes; the returned slice is independent of the live buffer.

func (*CaptureLogger) Warn

func (c *CaptureLogger) Warn(_ context.Context, msg string, args ...any)

func (*CaptureLogger) With

func (c *CaptureLogger) With(args ...any) Logger

With produces a child CaptureLogger that prefixes every record's Args with the supplied pairs. Records are recorded into the same underlying slice — there's only one buffer per Capture root.

type CaptureRecord

type CaptureRecord struct {
	Level string
	Msg   string
	Args  []any
}

CaptureRecord is a single observed log call. CaptureLogger appends one of these per Debug/Info/Warn/Error invocation. Args is the raw variadic slice the caller passed (for Error, the err is implicitly appended after "error" — exactly mirroring slogAdapter.Error so a CaptureLogger test exercises the same code path).

type Logger

type Logger interface {
	// Debug logs at debug level. Typically gated by LOG_LEVEL=debug.
	Debug(ctx context.Context, msg string, args ...any)

	// Info logs at info level. The default operational level.
	Info(ctx context.Context, msg string, args ...any)

	// Warn logs at warn level. Used for recoverable anomalies.
	Warn(ctx context.Context, msg string, args ...any)

	// Error logs at error level. err is conventionally attached under
	// the "error" key; pass nil if no error object exists (rare —
	// prefer Warn in that case).
	Error(ctx context.Context, msg string, err error, args ...any)

	// With returns a new Logger that prepends args to every record.
	// Mirrors slog.Logger.With — useful for request-scoped or
	// component-scoped enrichment ("request_id", id; "component",
	// "billing"; etc.).
	With(args ...any) Logger
}

Logger is the minimal structured-logging contract used across the codebase. Implementations are required to be safe for concurrent use; the canonical adapter (slogAdapter) is, since *slog.Logger is.

`args ...any` follows the slog convention: alternating (key string, value any) pairs, or a slog.Attr value, or a slog.Group. Adapters MAY normalise these but MUST NOT silently drop them — a dropped key is a dropped audit signal.

func NewNoop

func NewNoop() Logger

NewNoop returns a Logger that discards every record.

Cheaper than NewSlog(slog.New(slog.NewTextHandler(io.Discard, nil))) because it short-circuits before any allocation — the slog path still allocates the slog.Record, formats the time, walks the args slice, etc., even when the handler is io.Discard. For a hot test loop the difference is real.

func NewSlog

func NewSlog(l *slog.Logger) Logger

NewSlog wraps an *slog.Logger as a Logger port. A nil input is replaced by slog.Default() so callers don't have to nil-check before constructing — the canonical "no logger configured" path still works (output goes to slog's default handler).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL