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 metrics import ( "sync"
A thread-safe sliding-window average in Go
concurrency
sliding-window
running-average
Intermediate
8 steps
go
package middleware import ( "context"
Per-tenant daily rate limiting in Gin
rate-limiting
middleware
redis
Intermediate
8 steps
rust
use axum::{ body::{Body, Bytes}, extract::Request, http::StatusCode,
Logging request and response sizes in Axum
middleware
http
streaming-bodies
Advanced
8 steps
go
func (h *ExportHandler) StreamExport(c *gin.Context) { datasetID := c.Param("id") ctx := c.Request.Context()
Streaming NDJSON progress with Gin
streaming
goroutines
channels
Advanced
8 steps
go
package handler type flightResult struct { status int
Deduping in-flight requests in Gin
middleware
concurrency
deduplication
Advanced
9 steps
typescript
type Flatten = Record<string, unknown>; function isPlainObject(value: unknown): value is Record<string, unknown> { return (
Flattening nested objects into dotted keys
recursion
reduce
type-guards
Intermediate
7 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.