Files
overnight-trading-bot/internal/logging/logging.go
T
2026-06-09 21:04:01 +00:00

121 lines
2.7 KiB
Go

package logging
import (
"fmt"
"io"
"log/slog"
"os"
"regexp"
"strings"
)
func New(level string, out io.Writer) *slog.Logger {
if out == nil {
out = os.Stdout
}
var slogLevel slog.Level
switch strings.ToLower(level) {
case "debug":
slogLevel = slog.LevelDebug
case "warn", "warning":
slogLevel = slog.LevelWarn
case "error":
slogLevel = slog.LevelError
default:
slogLevel = slog.LevelInfo
}
return slog.New(slog.NewJSONHandler(out, &slog.HandlerOptions{
Level: slogLevel,
ReplaceAttr: redactAttr,
}))
}
type SDKLogger struct {
Logger *slog.Logger
}
type NoopSDKLogger struct{}
func NewSDKLogger(logger *slog.Logger) SDKLogger {
return SDKLogger{Logger: logger}
}
func (l SDKLogger) Infof(template string, args ...any) {
if l.Logger != nil {
l.Logger.Info(RedactString(template), "args", redactArgs(args))
}
}
func (l SDKLogger) Errorf(template string, args ...any) {
if l.Logger != nil {
l.Logger.Error(RedactString(template), "args", redactArgs(args))
}
}
func (l SDKLogger) Fatalf(template string, args ...any) {
if l.Logger != nil {
l.Logger.Error(RedactString(template), "args", redactArgs(args))
}
}
func (NoopSDKLogger) Infof(string, ...any) {}
func (NoopSDKLogger) Errorf(string, ...any) {}
func (NoopSDKLogger) Fatalf(string, ...any) {}
var sensitiveStringPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)((?:account[_-]?id|token)\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s,}]+)`),
regexp.MustCompile(`(?i)("(?:accountID|accountId|account_id|token)"\s*:\s*)("[^"]*"|null)`),
}
var sensitiveAttrKeyPattern = regexp.MustCompile(`(?i)^(account[_-]?id|accountID|accountId|token)$`)
func redactAttr(_ []string, attr slog.Attr) slog.Attr {
if sensitiveAttrKeyPattern.MatchString(attr.Key) {
attr.Value = slog.StringValue("[REDACTED]")
return attr
}
if attr.Value.Kind() == slog.KindString {
attr.Value = slog.StringValue(RedactString(attr.Value.String()))
}
return attr
}
func redactArgs(args []any) []any {
out := make([]any, len(args))
for i, arg := range args {
out[i] = redactAny(arg)
}
return out
}
func redactAny(value any) any {
switch typed := value.(type) {
case string:
return RedactString(typed)
case []string:
out := make([]string, len(typed))
for i, item := range typed {
out[i] = RedactString(item)
}
return out
case []any:
out := make([]any, len(typed))
for i, item := range typed {
out[i] = redactAny(item)
}
return out
case fmt.Stringer:
return RedactString(typed.String())
default:
return value
}
}
func RedactString(raw string) string {
redacted := raw
for _, pattern := range sensitiveStringPatterns {
redacted = pattern.ReplaceAllString(redacted, `${1}"[REDACTED]"`)
}
return redacted
}