mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-07 15:10:01 +08:00
feat: support masking sensitive data in logx (#5003)
Signed-off-by: kevin <wanjunfeng@gmail.com>
This commit is contained in:
21
core/logx/sensitive.go
Normal file
21
core/logx/sensitive.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package logx
|
||||
|
||||
// Sensitive is an interface that defines a method for masking sensitive information in logs.
|
||||
// It is typically implemented by types that contain sensitive data,
|
||||
// such as passwords or personal information.
|
||||
// Infov, Errorv, Debugv, and Slowv methods will call this method to mask sensitive data.
|
||||
// The values in LogField will also be masked if they implement the Sensitive interface.
|
||||
type Sensitive interface {
|
||||
// MaskSensitive masks sensitive information in the log.
|
||||
MaskSensitive() any
|
||||
}
|
||||
|
||||
// maskSensitive returns the value returned by MaskSensitive method,
|
||||
// if the value implements Sensitive interface.
|
||||
func maskSensitive(v any) any {
|
||||
if s, ok := v.(Sensitive); ok {
|
||||
return s.MaskSensitive()
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
50
core/logx/sensitive_test.go
Normal file
50
core/logx/sensitive_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const maskedContent = "******"
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Pass string
|
||||
}
|
||||
|
||||
func (u User) MaskSensitive() any {
|
||||
return User{
|
||||
Name: u.Name,
|
||||
Pass: maskedContent,
|
||||
}
|
||||
}
|
||||
|
||||
type NonSensitiveUser struct {
|
||||
Name string
|
||||
Pass string
|
||||
}
|
||||
|
||||
func TestMaskSensitive(t *testing.T) {
|
||||
t.Run("sensitive", func(t *testing.T) {
|
||||
user := User{
|
||||
Name: "kevin",
|
||||
Pass: "123",
|
||||
}
|
||||
|
||||
mu := maskSensitive(user)
|
||||
assert.Equal(t, user.Name, mu.(User).Name)
|
||||
assert.Equal(t, maskedContent, mu.(User).Pass)
|
||||
})
|
||||
|
||||
t.Run("non-sensitive", func(t *testing.T) {
|
||||
user := NonSensitiveUser{
|
||||
Name: "kevin",
|
||||
Pass: "123",
|
||||
}
|
||||
|
||||
mu := maskSensitive(user)
|
||||
assert.Equal(t, user.Name, mu.(NonSensitiveUser).Name)
|
||||
assert.Equal(t, user.Pass, mu.(NonSensitiveUser).Pass)
|
||||
})
|
||||
}
|
||||
@@ -365,19 +365,22 @@ func mergeGlobalFields(fields []LogField) []LogField {
|
||||
}
|
||||
|
||||
func output(writer io.Writer, level string, val any, fields ...LogField) {
|
||||
// only truncate string content, don't know how to truncate the values of other types.
|
||||
if v, ok := val.(string); ok {
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
// only truncate string content, don't know how to truncate the values of other types.
|
||||
maxLen := atomic.LoadUint32(&maxContentLength)
|
||||
if maxLen > 0 && len(v) > int(maxLen) {
|
||||
val = v[:maxLen]
|
||||
fields = append(fields, truncatedField)
|
||||
}
|
||||
case Sensitive:
|
||||
val = v.MaskSensitive()
|
||||
}
|
||||
|
||||
// +3 for timestamp, level and content
|
||||
entry := make(logEntry, len(fields)+3)
|
||||
for _, field := range fields {
|
||||
entry[field.Key] = field.Value
|
||||
entry[field.Key] = maskSensitive(field.Value)
|
||||
}
|
||||
|
||||
switch atomic.LoadUint32(&encoding) {
|
||||
|
||||
@@ -225,6 +225,48 @@ func TestWritePlainDuplicate(t *testing.T) {
|
||||
assert.Contains(t, buf.String(), "second=c")
|
||||
}
|
||||
|
||||
func TestLogWithSensitive(t *testing.T) {
|
||||
old := atomic.SwapUint32(&encoding, plainEncodingType)
|
||||
t.Cleanup(func() {
|
||||
atomic.StoreUint32(&encoding, old)
|
||||
})
|
||||
|
||||
t.Run("sensitive", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
output(&buf, levelInfo, User{
|
||||
Name: "kevin",
|
||||
Pass: "123",
|
||||
}, LogField{
|
||||
Key: "first",
|
||||
Value: "a",
|
||||
}, LogField{
|
||||
Key: "first",
|
||||
Value: "b",
|
||||
})
|
||||
assert.Contains(t, buf.String(), maskedContent)
|
||||
assert.NotContains(t, buf.String(), "first=a")
|
||||
assert.Contains(t, buf.String(), "first=b")
|
||||
})
|
||||
|
||||
t.Run("sensitive fields", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
output(&buf, levelInfo, "foo", LogField{
|
||||
Key: "first",
|
||||
Value: User{
|
||||
Name: "kevin",
|
||||
Pass: "123",
|
||||
},
|
||||
}, LogField{
|
||||
Key: "second",
|
||||
Value: "b",
|
||||
})
|
||||
assert.Contains(t, buf.String(), "foo")
|
||||
assert.Contains(t, buf.String(), "first")
|
||||
assert.Contains(t, buf.String(), maskedContent)
|
||||
assert.Contains(t, buf.String(), "second=b")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogWithLimitContentLength(t *testing.T) {
|
||||
maxLen := atomic.LoadUint32(&maxContentLength)
|
||||
atomic.StoreUint32(&maxContentLength, 10)
|
||||
|
||||
Reference in New Issue
Block a user