0.2.63 update
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -16,11 +17,25 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// LevelTrace is deliberately below DEBUG. It is disabled unless MEMBY_LOG_LEVEL=TRACE,
|
||||
// so tight diagnostic loops can be instrumented without making ordinary DEBUG unusable.
|
||||
const LevelTrace slog.Level = slog.LevelDebug - 4
|
||||
|
||||
// LevelName presents custom levels consistently in every configured output format.
|
||||
func LevelName(level slog.Level) string {
|
||||
if level == LevelTrace {
|
||||
return "TRACE"
|
||||
}
|
||||
return level.String()
|
||||
}
|
||||
|
||||
// ParseLevel returns a supported slog level, defaulting to INFO for empty or invalid
|
||||
// values. Keeping this forgiving prevents a typo in Docker configuration from stopping
|
||||
// the gateway.
|
||||
func ParseLevel(value string) slog.Level {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "TRACE":
|
||||
return LevelTrace
|
||||
case "DEBUG":
|
||||
return slog.LevelDebug
|
||||
case "WARN", "WARNING":
|
||||
@@ -128,6 +143,11 @@ func NewBuffered(
|
||||
if attr.Key == slog.TimeKey {
|
||||
return slog.String(slog.TimeKey, attr.Value.Time().UTC().Format(time.RFC3339))
|
||||
}
|
||||
if attr.Key == slog.LevelKey {
|
||||
if level, ok := attr.Value.Any().(slog.Level); ok {
|
||||
return slog.String(slog.LevelKey, LevelName(level))
|
||||
}
|
||||
}
|
||||
return attr
|
||||
},
|
||||
}
|
||||
@@ -233,7 +253,7 @@ func (h *captureHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||
})
|
||||
h.buffer.append(Event{
|
||||
OccurredAt: record.Time.UTC(),
|
||||
Level: record.Level.String(),
|
||||
Level: LevelName(record.Level),
|
||||
Message: record.Message,
|
||||
Attributes: attributes,
|
||||
})
|
||||
@@ -266,7 +286,35 @@ func addAttribute(target map[string]string, groups []string, attr slog.Attr) {
|
||||
}
|
||||
return
|
||||
}
|
||||
target[key] = attributeValue(attr.Value)
|
||||
target[key] = safeAttribute(key, attributeValue(attr.Value))
|
||||
}
|
||||
|
||||
// safeAttribute is the final guard before a structured record reaches the browser-visible
|
||||
// buffer and optional on-disk history. Call sites should never log credentials, but this
|
||||
// makes one accidental token-bearing URL or header non-disclosive by construction.
|
||||
func safeAttribute(key, value string) string {
|
||||
lower := strings.ToLower(key)
|
||||
if strings.Contains(lower, "password") || strings.Contains(lower, "token") ||
|
||||
strings.Contains(lower, "secret") || strings.Contains(lower, "cookie") ||
|
||||
strings.Contains(lower, "authorization") || strings.Contains(lower, "api_key") || strings.Contains(lower, "apikey") {
|
||||
return "[redacted]"
|
||||
}
|
||||
if parsed, err := url.Parse(value); err == nil && parsed.RawQuery != "" {
|
||||
query := parsed.Query()
|
||||
changed := false
|
||||
for key := range query {
|
||||
lowerKey := strings.ToLower(key)
|
||||
if strings.Contains(lowerKey, "token") || strings.Contains(lowerKey, "key") || strings.Contains(lowerKey, "auth") || lowerKey == "t" {
|
||||
query.Set(key, "[redacted]")
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
parsed.RawQuery = query.Encode()
|
||||
return parsed.String()
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (b *Buffer) append(event Event) {
|
||||
|
||||
Reference in New Issue
Block a user