Files
vaultik/internal/log/log.go
sneak 7ae470e530 Remediate all lint findings under the canonical golangci-lint config
Fix every finding surfaced by the canonical .golangci.yml with
golangci-lint v2.12.2 (refs #61), behavior-preserving throughout:

- err113: dynamic errors replaced with package-level sentinels and %w
  wrapping; direct comparisons converted to errors.Is
- goprintffuncname: printf-style helpers renamed with an f suffix
  (ui.Writer message methods, cli.ReportErrorf, database.Fatalf,
  vaultik stdoutf) and all call sites updated
- revive: stuttering type names renamed (blob.Handler, blob.WithReader,
  blob.ChunkPosition, storage.URL, storage.Info), doc comments added,
  unused parameters blanked, package comments added
- contextcheck/noctx: ctx threaded through blob.Packer
  (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites;
  context-aware exec and sql variants used
- funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated
  functions split into focused helpers across production and test code
- paralleltest/tparallel/thelper/usetesting/testpackage: tests
  parallelized where safe (global log.Initialize kept in the serial
  phase), helpers marked, t.TempDir adopted, external test packages
  where only exported API is used
- gosec: integer conversions clamped or justified, header timeouts
  added, remaining findings suppressed with per-site justifications
- mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other
  mechanical findings fixed directly

Remove the deprecated log.LogOptions alias (callers migrated to
log.Options). make check is green.
2026-08-07 18:51:21 +00:00

198 lines
4.7 KiB
Go

// Package log provides the application-wide structured logger: slog
// with a colorized TTY handler on terminals and JSON output otherwise.
package log
import (
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
"runtime"
"strings"
"golang.org/x/term"
)
// Level represents the logging level.
type Level int
const (
// LevelFatal represents a fatal error level that will exit the program.
LevelFatal Level = iota
// LevelError represents an error level.
LevelError
// LevelWarn represents a warning level.
LevelWarn
// LevelNotice represents a notice level (mapped to Info in slog).
LevelNotice
// LevelInfo represents an informational level.
LevelInfo
// LevelDebug represents a debug level.
LevelDebug
)
// Config holds logger configuration.
type Config struct {
Verbose bool
Debug bool
Cron bool
Quiet bool
}
//nolint:gochecknoglobals // package-level logger is the package's purpose
var logger *slog.Logger
// Initialize sets up the global logger based on the provided configuration.
func Initialize(cfg Config) {
// Determine log level based on configuration
var level slog.Level
switch {
case cfg.Cron || cfg.Quiet:
// In cron/quiet mode keep warnings and errors visible — the
// whole point of --cron is to stay silent only on total
// success, so that anything cron emails to root is genuinely
// "something went wrong, look at it." A backup with stuck
// permission errors or skipped files should NOT be silent.
level = slog.LevelWarn
case cfg.Debug || strings.Contains(os.Getenv("GODEBUG"), "vaultik"):
level = slog.LevelDebug
case cfg.Verbose:
level = slog.LevelInfo
default:
level = slog.LevelWarn
}
// Create handler with appropriate level
opts := &slog.HandlerOptions{
Level: level,
}
// Check if stdout is a TTY
if term.IsTerminal(int(os.Stdout.Fd())) {
// Use colorized TTY handler
logger = slog.New(NewTTYHandler(os.Stdout, opts))
} else {
// Use JSON format for non-TTY output
logger = slog.New(slog.NewJSONHandler(os.Stdout, opts))
}
// Set as default logger
slog.SetDefault(logger)
}
// callerSkipFrames is the number of stack frames between runtime.Caller
// and the code that invoked the package-level logging function.
const callerSkipFrames = 2
// getCaller returns the caller information as a string
func getCaller() string {
_, file, line, ok := runtime.Caller(callerSkipFrames)
if !ok {
return "unknown"
}
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
}
// Fatal logs a fatal error message and exits the program with code 1.
func Fatal(msg string, args ...any) {
if logger != nil {
// Add caller info to args
args = append(args, "caller", getCaller())
logger.Error(msg, args...)
}
os.Exit(1)
}
// Fatalf logs a formatted fatal error message and exits the program with code 1.
func Fatalf(format string, args ...any) {
Fatal(fmt.Sprintf(format, args...))
}
// Error logs an error message.
func Error(msg string, args ...any) {
if logger != nil {
args = append(args, "caller", getCaller())
logger.Error(msg, args...)
}
}
// Errorf logs a formatted error message.
func Errorf(format string, args ...any) {
Error(fmt.Sprintf(format, args...))
}
// Warn logs a warning message.
func Warn(msg string, args ...any) {
if logger != nil {
args = append(args, "caller", getCaller())
logger.Warn(msg, args...)
}
}
// Warnf logs a formatted warning message.
func Warnf(format string, args ...any) {
Warn(fmt.Sprintf(format, args...))
}
// Notice logs a notice message (mapped to Info level).
func Notice(msg string, args ...any) {
if logger != nil {
args = append(args, "caller", getCaller())
logger.Info(msg, args...)
}
}
// Noticef logs a formatted notice message.
func Noticef(format string, args ...any) {
Notice(fmt.Sprintf(format, args...))
}
// Info logs an informational message.
func Info(msg string, args ...any) {
if logger != nil {
args = append(args, "caller", getCaller())
logger.Info(msg, args...)
}
}
// Infof logs a formatted informational message.
func Infof(format string, args ...any) {
Info(fmt.Sprintf(format, args...))
}
// Debug logs a debug message.
func Debug(msg string, args ...any) {
if logger != nil {
args = append(args, "caller", getCaller())
logger.Debug(msg, args...)
}
}
// Debugf logs a formatted debug message.
func Debugf(format string, args ...any) {
Debug(fmt.Sprintf(format, args...))
}
// With returns a logger with additional context attributes.
func With(args ...any) *slog.Logger {
if logger != nil {
return logger.With(args...)
}
return slog.Default()
}
// WithContext returns a logger with the provided context.
func WithContext(_ context.Context) *slog.Logger {
return logger
}
// Logger returns the underlying slog.Logger instance.
func Logger() *slog.Logger {
return logger
}