Skip to main content

pkg/log/logger.go

Source

Overview

What:

Defines the gateway's minimal logging interface and a process-wide shared logger implementation.

The package intentionally exposes a small Logger interface (Debugw/Infow/Warnw/Errorw), and provides a Zap-backed default logger plus hooks to replace it in tests or embeddings.

Why:

Most of the gateway code uses structured logging, but does not want to:

  • import Zap directly everywhere
  • carry a concrete logger through every constructor
  • leak a rich logging API surface into packages that only need "log with fields"

This package provides a narrow abstraction and a safe default so the rest of the codebase can log consistently.

How:

  • Shared() returns the global logger, initializing the default implementation once (ensureDefault).
  • Configure(...) replaces the global logger and its sync function.
  • Sync() flushes buffered logs via the configured sync function and suppresses a few known-benign errors.

The default logger is Zap production config, optionally writing to the path in APIGW_LOG_PATH.

Notes: Security: log fields can accidentally include secrets. Prefer logging identifiers and metadata, not payloads or credentials.

Contents

Imports

import block 1

pkg/log/logger.go#L3
import (
"os"
"strings"
"sync"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

Variables

var block 1

pkg/log/logger.go#L47
var (
once sync.Once
mu sync.RWMutex
global Logger
syncLogger = func() error { return nil }
)

once

What: Ensures default logger initialization happens exactly once.

Why: Avoids races and double-initialization while keeping Shared() cheap.

How: The ensureDefault function calls once.Do(...) with the initialization closure.

mu

What: Synchronizes access to global and syncLogger.

Why: Multiple goroutines may call Shared(), Configure(), and Sync() concurrently.

How: The Shared function uses RLock; Configure and ensureDefault use Lock.

global

What: Holds the current process-wide logger implementation.

Why: Enables low-friction logging for packages that do not carry a logger dependency.

How: Set during ensureDefault or Configure and accessed under mu.

syncLogger

What: Function used by Sync() to flush the active logger backend.

Why: Some loggers require explicit flush on shutdown (e.g., Zap), but not all implementations do.

How: Set by ensureDefault or Configure; called by Sync().

Notes: Defaults to a no-op until configured.

Types

type block 1

pkg/log/logger.go#L13
type Logger interface {
Debugw(msg string, keysAndValues ...any)
Infow(msg string, keysAndValues ...any)
Warnw(msg string, keysAndValues ...any)
Errorw(msg string, keysAndValues ...any)
}

Logger

What: Minimal structured logging interface used across the gateway.

Why: Keeps package dependencies small and makes it easy to swap logging backends.

How: Implemented by zapLogger (Zap-backed) and noopLogger; consumed by most gateway packages.

Notes: Methods follow the *w convention (message plus key/value pairs) for structured fields.

type block 2

pkg/log/logger.go#L20
type noopLogger struct{}

noopLogger

What: Implementation of Logger that discards all logs.

Why: Provides a safe fallback when no logger is configured.

How: Methods are defined with empty bodies.

type block 3

pkg/log/logger.go#L27
type zapLogger struct {
s *zap.SugaredLogger
}

zapLogger

What: Adapter that wraps a *zap.SugaredLogger to implement Logger.

Why: Allows most of the codebase to depend only on this package's interface while still using Zap.

How: Each method delegates to the corresponding sugared logger method.

Functions and Methods

(noopLogger).Debugw

What: Discards a debug-level log call.

Why: Implements the Logger interface for the no-op logger.

How: Empty method body.

pkg/log/logger.go#L22
func (noopLogger) Debugw(string, ...any) {}

(noopLogger).Infow

What: Discards an info-level log call.

Why: Implements the Logger interface for the no-op logger.

How: Empty method body.

pkg/log/logger.go#L23
func (noopLogger) Infow(string, ...any)  {}

(noopLogger).Warnw

What: Discards a warning-level log call.

Why: Implements the Logger interface for the no-op logger.

How: Empty method body.

pkg/log/logger.go#L24
func (noopLogger) Warnw(string, ...any)  {}

(noopLogger).Errorw

What: Discards an error-level log call.

Why: Implements the Logger interface for the no-op logger.

How: Empty method body.

pkg/log/logger.go#L25
func (noopLogger) Errorw(string, ...any) {}

(*zapLogger).Debugw

What: Emits a debug-level structured log entry.

Why: Allows call sites to use a consistent structured logging API independent of the backing implementation.

How: Delegates to the underlying zap.SugaredLogger.Debugw.

pkg/log/logger.go#L31
func (z *zapLogger) Debugw(msg string, keysAndValues ...any) {
z.s.Debugw(msg, keysAndValues...)
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L32: z.s.Debugw(msg, keysAndValues...)
    • What: Calls z.s.Debugw.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.

(*zapLogger).Infow

What: Emits an info-level structured log entry.

Why: Info logs capture normal operational events without being as noisy as debug.

How: Delegates to the underlying zap.SugaredLogger.Infow.

pkg/log/logger.go#L35
func (z *zapLogger) Infow(msg string, keysAndValues ...any) {
z.s.Infow(msg, keysAndValues...)
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L36: z.s.Infow(msg, keysAndValues...)
    • What: Calls z.s.Infow.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.

(*zapLogger).Warnw

What: Emits a warning-level structured log entry.

Why: Warnings represent unexpected or degraded situations that may not be fatal.

How: Delegates to the underlying zap.SugaredLogger.Warnw.

pkg/log/logger.go#L39
func (z *zapLogger) Warnw(msg string, keysAndValues ...any) {
z.s.Warnw(msg, keysAndValues...)
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L40: z.s.Warnw(msg, keysAndValues...)
    • What: Calls z.s.Warnw.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.

(*zapLogger).Errorw

What: Emits an error-level structured log entry.

Why: Error logs are used for failures that operators should notice and potentially alert on.

How: Delegates to the underlying zap.SugaredLogger.Errorw.

pkg/log/logger.go#L43
func (z *zapLogger) Errorw(msg string, keysAndValues ...any) {
z.s.Errorw(msg, keysAndValues...)
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L44: z.s.Errorw(msg, keysAndValues...)
    • What: Calls z.s.Errorw.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.

Shared

What: Returns the process-wide shared logger.

Why: Provides a low-friction way for packages to log without threading a logger through every function signature.

How: Ensures the default logger is initialized once (ensureDefault), then returns global under an RW lock.

Notes: For highly testable code paths, prefer dependency injection over calling Shared() directly.

pkg/log/logger.go#L55
func Shared() Logger {
ensureDefault()

mu.RLock()
defer mu.RUnlock()
return global
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L56: ensureDefault()
    • What: Calls ensureDefault.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.
  • L58: mu.RLock()
    • What: Calls mu.RLock.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.
  • L59: defer mu.RUnlock()
    • What: Defers a call for cleanup.
    • Why: Ensures the deferred action runs even on early returns.
    • How: Schedules the call to run when the surrounding function returns.
  • L60: return global
    • What: Returns from the current function.
    • Why: Ends the current execution path and hands control back to the caller.
    • How: Executes a return statement (possibly returning values).

Configure

What: Replaces the global shared logger and its sync function.

Why: Enables tests and downstream consumers to inject a logger consistent with their environment.

How: Panics if logger is nil, marks the default initializer as "used", then swaps global and syncLogger under a mutex; when syncFn is nil, Sync() becomes a no-op.

Notes: Call this early during startup to avoid mixed loggers across packages.

pkg/log/logger.go#L64
func Configure(logger Logger, syncFn func() error) {
if logger == nil {
panic("log: Configure called with nil logger")
}

once.Do(func() {})

mu.Lock()
global = logger
if syncFn == nil {
syncLogger = func() error { return nil }
} else {
syncLogger = syncFn
}
mu.Unlock()
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L65: if logger == nil { panic("log: Configure called with nil logger") }
    • What: Branches conditionally.
    • Why: Handles different execution paths based on runtime state.
    • How: Evaluates the condition and executes the matching branch.
    • Nested steps:
      • L66: panic("log: Configure called with nil logger")
        • What: Calls panic.
        • Why: Performs side effects or delegates work to a helper.
        • How: Executes the expression statement.
  • L69: once.Do(func() {})
    • What: Calls once.Do.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.
    • Nested steps:
      • L69: func() {}
        • What: Defines an inline function (closure).
        • Why: Encapsulates callback logic and may capture variables from the surrounding scope.
        • How: Declares a func literal and uses it as a value (for example, as an HTTP handler or callback).
  • L71: mu.Lock()
    • What: Calls mu.Lock.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.
  • L72: global = logger
    • What: Assigns global.
    • Why: Keeps intermediate state available for later steps in the function.
    • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
  • L73: if syncFn == nil { syncLogger = func() error { return nil } } else { syncLogger = syncFn }
    • What: Branches conditionally.
    • Why: Handles different execution paths based on runtime state.
    • How: Evaluates the condition and executes the matching branch.
    • Nested steps:
      • L74: syncLogger = func() error { return nil }
        • What: Assigns syncLogger.
        • Why: Keeps intermediate state available for later steps in the function.
        • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
        • Nested steps:
          • L74: func() error { return nil }
            • What: Defines an inline function (closure).
            • Why: Encapsulates callback logic and may capture variables from the surrounding scope.
            • How: Declares a func literal and uses it as a value (for example, as an HTTP handler or callback).
            • Nested steps:
              • L74: return nil
                • What: Returns from the current function.
                • Why: Ends the current execution path and hands control back to the caller.
                • How: Executes a return statement (possibly returning values).
      • L76: syncLogger = syncFn
        • What: Assigns syncLogger.
        • Why: Keeps intermediate state available for later steps in the function.
        • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
  • L78: mu.Unlock()
    • What: Calls mu.Unlock.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.

NewZapLogger

What: Adapts a *zap.Logger to the package's Logger interface.

Why: Keeps Zap out of most call sites while still using Zap for production-quality logging.

How: If the base logger is nil, returns NewNoop and a no-op sync function; otherwise wraps base.Sugar() in a zapLogger and returns base.Sync.

Notes: The returned sync function should typically be wired to log.Configure(..., syncFn) or called during shutdown.

pkg/log/logger.go#L82
func NewZapLogger(base *zap.Logger) (Logger, func() error) {
if base == nil {
return NewNoop(), func() error { return nil }
}
return &zapLogger{s: base.Sugar()}, base.Sync
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L83: if base == nil { return NewNoop(), func() error { return nil } }
    • What: Branches conditionally.
    • Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
    • How: Evaluates the condition and executes the matching branch.
    • Nested steps:
      • L84: return NewNoop(), func() error { return nil }
        • What: Returns from the current function.
        • Why: Ends the current execution path and hands control back to the caller.
        • How: Executes a return statement (possibly returning values).
        • Nested steps:
          • L84: func() error { return nil }
            • What: Defines an inline function (closure).
            • Why: Encapsulates callback logic and may capture variables from the surrounding scope.
            • How: Declares a func literal and uses it as a value (for example, as an HTTP handler or callback).
            • Nested steps:
              • L84: return nil
                • What: Returns from the current function.
                • Why: Ends the current execution path and hands control back to the caller.
                • How: Executes a return statement (possibly returning values).
  • L86: return &zapLogger{s: base.Sugar()}, base.Sync
    • What: Returns from the current function.
    • Why: Ends the current execution path and hands control back to the caller.
    • How: Executes a return statement (possibly returning values).

NewZapSugaredLogger

What: Adapts a *zap.SugaredLogger to the package's Logger interface.

Why: Some callers already work with sugared loggers; this avoids re-wrapping.

How: If the sugared logger is nil, returns NewNoop and a no-op sync function; otherwise wraps it in a zapLogger and returns s.Sync.

pkg/log/logger.go#L90
func NewZapSugaredLogger(s *zap.SugaredLogger) (Logger, func() error) {
if s == nil {
return NewNoop(), func() error { return nil }
}
return &zapLogger{s: s}, s.Sync
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L91: if s == nil { return NewNoop(), func() error { return nil } }
    • What: Branches conditionally.
    • Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
    • How: Evaluates the condition and executes the matching branch.
    • Nested steps:
      • L92: return NewNoop(), func() error { return nil }
        • What: Returns from the current function.
        • Why: Ends the current execution path and hands control back to the caller.
        • How: Executes a return statement (possibly returning values).
        • Nested steps:
          • L92: func() error { return nil }
            • What: Defines an inline function (closure).
            • Why: Encapsulates callback logic and may capture variables from the surrounding scope.
            • How: Declares a func literal and uses it as a value (for example, as an HTTP handler or callback).
            • Nested steps:
              • L92: return nil
                • What: Returns from the current function.
                • Why: Ends the current execution path and hands control back to the caller.
                • How: Executes a return statement (possibly returning values).
  • L94: return &zapLogger{s: s}, s.Sync
    • What: Returns from the current function.
    • Why: Ends the current execution path and hands control back to the caller.
    • How: Executes a return statement (possibly returning values).

NewNoop

What: Constructs a logger that discards all output.

Why: Useful for tests and for environments that explicitly want silence.

How: Returns a noopLogger value that implements Logger with empty method bodies.

pkg/log/logger.go#L98
func NewNoop() Logger {
return noopLogger{}
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L99: return noopLogger{}
    • What: Returns from the current function.
    • Why: Ends the current execution path and hands control back to the caller.
    • How: Executes a return statement (possibly returning values).

Sync

What: Flushes any buffered log entries for the current global logger.

Why: Ensures logs are not lost on process exit, especially when writing to files.

How: Calls the configured syncLogger function; suppresses a couple of known benign OS errors; otherwise returns the sync error.

Notes: Zap can return platform-specific errors when syncing stdout/stderr; this helper makes shutdown logic more robust.

pkg/log/logger.go#L103
func Sync() error {
if err := syncLogger(); err != nil {
msg := err.Error()
if strings.Contains(msg, "bad file descriptor") || strings.Contains(msg, "invalid argument") {
return nil
}
return err
}
return nil
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L104: if err := syncLogger(); err != nil { msg := err.Error() if strings.Contains(msg, "bad file descriptor") || strings.Contains(msg, "invalid a…
    • What: Branches conditionally.
    • Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
    • How: Evaluates the condition and executes the matching branch.
    • Nested steps:
      • L104: err := syncLogger()
        • What: Defines err.
        • Why: Keeps intermediate state available for later steps in the function.
        • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
      • L105: msg := err.Error()
        • What: Defines msg.
        • Why: Keeps intermediate state available for later steps in the function.
        • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
      • L106: if strings.Contains(msg, "bad file descriptor") || strings.Contains(msg, "invalid argument") { return nil }
        • What: Branches conditionally.
        • Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
        • How: Evaluates the condition and executes the matching branch.
        • Nested steps:
          • L107: return nil
            • What: Returns from the current function.
            • Why: Ends the current execution path and hands control back to the caller.
            • How: Executes a return statement (possibly returning values).
      • L109: return err
        • What: Returns from the current function.
        • Why: Ends the current execution path and hands control back to the caller.
        • How: Executes a return statement (possibly returning values).
  • L111: return nil
    • What: Returns from the current function.
    • Why: Ends the current execution path and hands control back to the caller.
    • How: Executes a return statement (possibly returning values).

ensureDefault

What: Initializes the default Zap-backed global logger exactly once.

Why: Avoids requiring explicit setup for common paths while keeping initialization thread-safe.

How: Uses sync.Once to build a Zap production config, optionally updates output paths from APIGW_LOG_PATH, sets encoder keys and ISO8601 timestamps, builds the logger, then stores the adapter and sync function under a mutex.

Notes: Panics if the Zap logger cannot be built; treat this as a startup-time configuration error.

pkg/log/logger.go#L114
func ensureDefault() {
once.Do(func() {
cfg := zap.NewProductionConfig()
if path := strings.TrimSpace(os.Getenv("APIGW_LOG_PATH")); path != "" {
cfg.OutputPaths = []string{path}
cfg.ErrorOutputPaths = []string{path}
}
cfg.EncoderConfig.TimeKey = "time"
cfg.EncoderConfig.MessageKey = "msg"
cfg.EncoderConfig.LevelKey = "level"
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder

base, err := cfg.Build()
if err != nil {
panic(err)
}

logger, syncFn := NewZapLogger(base)

mu.Lock()
global = logger
syncLogger = syncFn
mu.Unlock()
})
}

Walkthrough

The list below documents the statements inside the function body, including nested blocks and inline closures.

  • L115: once.Do(func() { cfg := zap.NewProductionConfig() if path := strings.TrimSpace(os.Getenv("APIGW_LOG_PATH")); path != "" { cfg.OutputPaths =…
    • What: Calls once.Do.
    • Why: Performs side effects or delegates work to a helper.
    • How: Executes the expression statement.
    • Nested steps:
      • L115: func() { cfg := zap.NewProductionConfig() if path := strings.TrimSpace(os.Getenv("APIGW_LOG_PATH")); path != "" { cfg.OutputPaths = []strin…
        • What: Defines an inline function (closure).
        • Why: Encapsulates callback logic and may capture variables from the surrounding scope.
        • How: Declares a func literal and uses it as a value (for example, as an HTTP handler or callback).
        • Nested steps:
          • L116: cfg := zap.NewProductionConfig()
            • What: Defines cfg.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L117: if path := strings.TrimSpace(os.Getenv("APIGW_LOG_PATH")); path != "" { cfg.OutputPaths = []string{path} cfg.ErrorOutputPaths = []string{pa…
            • What: Branches conditionally.
            • Why: Handles different execution paths based on runtime state.
            • How: Evaluates the condition and executes the matching branch.
            • Nested steps:
              • L117: path := strings.TrimSpace(os.Getenv("APIGW_LOG_PATH"))
                • What: Defines path.
                • Why: Keeps intermediate state available for later steps in the function.
                • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
              • L118: cfg.OutputPaths = []string{path}
                • What: Assigns cfg.OutputPaths.
                • Why: Keeps intermediate state available for later steps in the function.
                • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
              • L119: cfg.ErrorOutputPaths = []string{path}
                • What: Assigns cfg.ErrorOutputPaths.
                • Why: Keeps intermediate state available for later steps in the function.
                • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L121: cfg.EncoderConfig.TimeKey = "time"
            • What: Assigns cfg.EncoderConfig.TimeKey.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L122: cfg.EncoderConfig.MessageKey = "msg"
            • What: Assigns cfg.EncoderConfig.MessageKey.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L123: cfg.EncoderConfig.LevelKey = "level"
            • What: Assigns cfg.EncoderConfig.LevelKey.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L124: cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
            • What: Assigns cfg.EncoderConfig.EncodeTime.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L126: base, err := cfg.Build()
            • What: Defines base, err.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L127: if err != nil { panic(err) }
            • What: Branches conditionally.
            • Why: Handles different execution paths based on runtime state.
            • How: Evaluates the condition and executes the matching branch.
            • Nested steps:
              • L128: panic(err)
                • What: Calls panic.
                • Why: Performs side effects or delegates work to a helper.
                • How: Executes the expression statement.
          • L131: logger, syncFn := NewZapLogger(base)
            • What: Defines logger, syncFn.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L133: mu.Lock()
            • What: Calls mu.Lock.
            • Why: Performs side effects or delegates work to a helper.
            • How: Executes the expression statement.
          • L134: global = logger
            • What: Assigns global.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L135: syncLogger = syncFn
            • What: Assigns syncLogger.
            • Why: Keeps intermediate state available for later steps in the function.
            • How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
          • L136: mu.Unlock()
            • What: Calls mu.Unlock.
            • Why: Performs side effects or delegates work to a helper.
            • How: Executes the expression statement.

Architecture

Reference

Neighboring source