feat: complete API-driven projects with infrastructure and business acceptance
This commit is contained in:
133
logx/redaction.go
Normal file
133
logx/redaction.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const redacted = "[REDACTED]"
|
||||
|
||||
// Redaction applies to structured keys, including nested maps and structs.
|
||||
// Free text (messages, errors, URLs embedded in strings) is not parsed.
|
||||
func redactor(extra []string) func([]string, slog.Attr) slog.Attr {
|
||||
keys := make(map[string]bool)
|
||||
for _, key := range append([]string{"password", "passwd", "secret", "clientSecret", "token", "accessToken", "refreshToken", "authorization", "cookie", "setCookie", "apiKey", "privateKey", "signedUrl"}, extra...) {
|
||||
keys[normalizedKey(key)] = true
|
||||
}
|
||||
return func(groups []string, a slog.Attr) slog.Attr {
|
||||
for _, group := range groups {
|
||||
if keys[normalizedKey(group)] {
|
||||
return slog.String(a.Key, redacted)
|
||||
}
|
||||
}
|
||||
if keys[normalizedKey(a.Key)] {
|
||||
return slog.String(a.Key, redacted)
|
||||
}
|
||||
a.Value = a.Value.Resolve()
|
||||
if a.Value.Kind() == slog.KindAny {
|
||||
a.Value = slog.AnyValue(sanitize(reflect.ValueOf(a.Value.Any()), keys, 0))
|
||||
}
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
func normalizedKey(key string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
||||
return unicode.ToLower(r)
|
||||
}
|
||||
return -1
|
||||
}, key)
|
||||
}
|
||||
|
||||
func sanitize(v reflect.Value, keys map[string]bool, depth int) any {
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
if depth >= 16 {
|
||||
return "[TRUNCATED]"
|
||||
}
|
||||
if (v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface) && v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
if v.CanInterface() {
|
||||
switch x := v.Interface().(type) {
|
||||
case *slog.Source:
|
||||
return sanitize(reflect.ValueOf(map[string]any{"function": x.Function, "file": x.File, "line": x.Line}), keys, depth+1)
|
||||
case time.Time:
|
||||
return x
|
||||
case error:
|
||||
return x.Error()
|
||||
case []slog.Attr:
|
||||
out := make(map[string]any, len(x))
|
||||
for _, a := range x {
|
||||
if keys[normalizedKey(a.Key)] {
|
||||
out[a.Key] = redacted
|
||||
} else {
|
||||
out[a.Key] = sanitize(reflect.ValueOf(a.Value.Resolve().Any()), keys, depth+1)
|
||||
}
|
||||
}
|
||||
return out
|
||||
case slog.LogValuer:
|
||||
value := slog.AnyValue(x).Resolve()
|
||||
return sanitize(reflect.ValueOf(value.Any()), keys, depth+1)
|
||||
}
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
return sanitize(v.Elem(), keys, depth+1)
|
||||
case reflect.Map:
|
||||
if v.Type().Key().Kind() != reflect.String {
|
||||
return "[UNSUPPORTED MAP]"
|
||||
}
|
||||
out := make(map[string]any, v.Len())
|
||||
it := v.MapRange()
|
||||
for it.Next() {
|
||||
key := it.Key().String()
|
||||
if keys[normalizedKey(key)] {
|
||||
out[key] = redacted
|
||||
} else {
|
||||
out[key] = sanitize(it.Value(), keys, depth+1)
|
||||
}
|
||||
}
|
||||
return out
|
||||
case reflect.Struct:
|
||||
out := make(map[string]any)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Type().Field(i)
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
name := strings.Split(field.Tag.Get("json"), ",")[0]
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
if name == "" {
|
||||
name = field.Name
|
||||
}
|
||||
if keys[normalizedKey(name)] || keys[normalizedKey(field.Name)] {
|
||||
out[name] = redacted
|
||||
} else {
|
||||
out[name] = sanitize(v.Field(i), keys, depth+1)
|
||||
}
|
||||
}
|
||||
return out
|
||||
case reflect.Slice, reflect.Array:
|
||||
if v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
return "[BINARY]"
|
||||
}
|
||||
out := make([]any, v.Len())
|
||||
for i := range out {
|
||||
out[i] = sanitize(v.Index(i), keys, depth+1)
|
||||
}
|
||||
return out
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
return v.Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user