137 lines
3.3 KiB
Go
137 lines
3.3 KiB
Go
package secret
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
var (
|
|
debugEnabled bool
|
|
debugLogger *slog.Logger
|
|
)
|
|
|
|
func init() {
|
|
initDebugLogging()
|
|
}
|
|
|
|
// initDebugLogging initializes the debug logging system based on GODEBUG environment variable
|
|
func initDebugLogging() {
|
|
godebug := os.Getenv("GODEBUG")
|
|
debugEnabled = strings.Contains(godebug, "berlin.sneak.pkg.secret")
|
|
|
|
if !debugEnabled {
|
|
// Create a no-op logger that discards all output
|
|
debugLogger = slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
return
|
|
}
|
|
|
|
// Disable stderr buffering for immediate debug output when debugging is enabled
|
|
_, _, _ = syscall.Syscall(syscall.SYS_FCNTL, os.Stderr.Fd(), syscall.F_SETFL, syscall.O_SYNC)
|
|
|
|
// Check if STDERR is a TTY
|
|
isTTY := term.IsTerminal(int(syscall.Stderr))
|
|
|
|
var handler slog.Handler
|
|
if isTTY {
|
|
// TTY output: colorized structured format
|
|
handler = newColorizedHandler(os.Stderr)
|
|
} else {
|
|
// Non-TTY output: JSON Lines format
|
|
handler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
|
|
Level: slog.LevelDebug,
|
|
})
|
|
}
|
|
|
|
debugLogger = slog.New(handler)
|
|
}
|
|
|
|
// IsDebugEnabled returns true if debug logging is enabled
|
|
func IsDebugEnabled() bool {
|
|
return debugEnabled
|
|
}
|
|
|
|
// Debug logs a debug message with optional attributes
|
|
func Debug(msg string, args ...any) {
|
|
if !debugEnabled {
|
|
return
|
|
}
|
|
debugLogger.Debug(msg, args...)
|
|
}
|
|
|
|
// DebugF logs a formatted debug message with optional attributes
|
|
func DebugF(format string, args ...any) {
|
|
if !debugEnabled {
|
|
return
|
|
}
|
|
debugLogger.Debug(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// DebugWith logs a debug message with structured attributes
|
|
func DebugWith(msg string, attrs ...slog.Attr) {
|
|
if !debugEnabled {
|
|
return
|
|
}
|
|
debugLogger.LogAttrs(context.Background(), slog.LevelDebug, msg, attrs...)
|
|
}
|
|
|
|
// colorizedHandler implements a TTY-friendly structured log handler
|
|
type colorizedHandler struct {
|
|
output io.Writer
|
|
}
|
|
|
|
func newColorizedHandler(output io.Writer) slog.Handler {
|
|
return &colorizedHandler{output: output}
|
|
}
|
|
|
|
func (h *colorizedHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|
// Explicitly check that debug is enabled AND the level is DEBUG or higher
|
|
// This ensures we don't default to INFO level when debug is enabled
|
|
return debugEnabled && level >= slog.LevelDebug
|
|
}
|
|
|
|
func (h *colorizedHandler) Handle(_ context.Context, record slog.Record) error {
|
|
if !debugEnabled {
|
|
return nil
|
|
}
|
|
|
|
// Format: [DEBUG] message {key=value, key2=value2}
|
|
output := fmt.Sprintf("\033[36m[DEBUG]\033[0m \033[1m%s\033[0m", record.Message)
|
|
|
|
if record.NumAttrs() > 0 {
|
|
output += " \033[33m{"
|
|
first := true
|
|
record.Attrs(func(attr slog.Attr) bool {
|
|
if !first {
|
|
output += ", "
|
|
}
|
|
first = false
|
|
output += fmt.Sprintf("%s=%#v", attr.Key, attr.Value.Any())
|
|
return true
|
|
})
|
|
output += "}\033[0m"
|
|
}
|
|
|
|
output += "\n"
|
|
_, err := h.output.Write([]byte(output))
|
|
return err
|
|
}
|
|
|
|
func (h *colorizedHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
// For simplicity, return the same handler
|
|
// In a more complex implementation, we'd create a new handler with the attrs
|
|
return h
|
|
}
|
|
|
|
func (h *colorizedHandler) WithGroup(name string) slog.Handler {
|
|
// For simplicity, return the same handler
|
|
// In a more complex implementation, we'd create a new handler with the group
|
|
return h
|
|
}
|