go
71 lines · 10 steps
Redacting secrets with Go reflection
A reflection-based walker deep-copies any value, masking sensitive fields so secrets never reach your logs.
Explained by
highlit
1package logging
2
3import (
4 "fmt"
5 "reflect"
6 "strings"
7)
8
9const redactedMask = "***REDACTED***"
10
11func Redact(v interface{}) interface{} {
12 return redactValue(reflect.ValueOf(v)).Interface()
13}
14
15func redactValue(v reflect.Value) reflect.Value {
16 switch v.Kind() {
17 case reflect.Ptr, reflect.Interface:
18 if v.IsNil() {
19 return v
20 }
21 return redactValue(v.Elem())
22
23 case reflect.Struct:
24 out := reflect.New(v.Type()).Elem()
25 for i := 0; i < v.NumField(); i++ {
26 field := v.Type().Field(i)
27 if field.PkgPath != "" {
28 continue
29 }
30 dst := out.Field(i)
31 if isSensitive(field) {
32 if dst.Kind() == reflect.String {
33 dst.SetString(redactedMask)
34 } else {
35 dst.Set(reflect.Zero(dst.Type()))
36 }
37 continue
38 }
39 dst.Set(redactValue(v.Field(i)))
40 }
41 return out
42
43 case reflect.Slice, reflect.Array:
44 out := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
45 for i := 0; i < v.Len(); i++ {
46 out.Index(i).Set(redactValue(v.Index(i)))
47 }
48 return out
49
50 default:
51 return v
52 }
53}
54
55func isSensitive(f reflect.StructField) bool {
56 tag := f.Tag.Get("log")
57 if tag == "redact" || tag == "secret" {
58 return true
59 }
60 name := strings.ToLower(f.Name)
61 for _, needle := range []string{"password", "secret", "token", "apikey", "ssn"} {
62 if strings.Contains(name, needle) {
63 return true
64 }
65 }
66 return false
67}
68
69func (u User) LogValue() string {
70 return fmt.Sprintf("%+v", Redact(u))
71}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Reflection lets you build generic transformations that work across any struct type without knowing it ahead of time.
- 2Recursing on Kind handles nested pointers, structs, and slices uniformly with a single dispatch switch.
- 3Detecting sensitivity via both struct tags and field-name heuristics catches secrets even when tags are forgotten.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
php
<?php final class RotatingFileLogger {
How a rotating file logger works in PHP
logging
file-rotation
io
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/redacting-secrets-with-go-reflection-explained-go-257a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.