Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dm: Add marker option to redact info log #12070

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/dm-worker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func main() {
Format: cfg.LogFormat,
Level: strings.ToLower(cfg.LogLevel),
})
log.SetRedactLog(cfg.RedactInfoLog)
log.SetRedactType(cfg.RedactInfoLog)
if err != nil {
common.PrintLinesf("init logger error %s", terror.Message(err))
os.Exit(2)
Expand Down
158 changes: 143 additions & 15 deletions dm/pkg/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package log

import (
"context"
"encoding/json"
"fmt"
"strings"
"sync/atomic"
Expand Down Expand Up @@ -196,31 +197,158 @@ func WithCtx(ctx context.Context) Logger {
return Logger{appLogger.With(getZapFieldsFromCtx(ctx)...)}
}

var enabledRedactLog atomic.Bool
// RedactInfoLogType is the behavior of redacting sensitive information in logs.
type RedactInfoLogType int

const (
// RedactInfoLogOFF means log redaction is disabled.
RedactInfoLogOFF RedactInfoLogType = iota
// RedactInfoLogON means log redaction is enabled, and will replace the sensitive information with "?".
RedactInfoLogON
// RedactInfoLogMarker means log redaction is enabled, and will use single guillemets ‹› to enclose the sensitive information.
RedactInfoLogMarker
)

// String implements flag.Value interface.
func (t *RedactInfoLogType) String() string {
switch *t {
case RedactInfoLogOFF:
return "false"
case RedactInfoLogON:
return "true"
case RedactInfoLogMarker:
return "marker"
default:
return "false"
}
}

// Set implements flag.Value interface.
func (t *RedactInfoLogType) Set(s string) error {
switch strings.ToLower(s) {
case "false":
*t = RedactInfoLogOFF
return nil
case "true":
*t = RedactInfoLogON
return nil
case "marker":
*t = RedactInfoLogMarker
return nil
default:
return fmt.Errorf("invalid redact-info-log value %q, must be false/true/marker", s)
}
}

// Type implements pflag.Value interface.
func (t *RedactInfoLogType) Type() string {
return "RedactInfoLogType"
}

// MarshalJSON implements the `json.Marshaler` interface to ensure the compatibility.
func (t RedactInfoLogType) MarshalJSON() ([]byte, error) {
switch t {
case RedactInfoLogON:
return json.Marshal(true)
case RedactInfoLogMarker:
return json.Marshal("MARKER")
default:
}
return json.Marshal(false)
}

const invalidRedactInfoLogTypeErrMsg = `the "redact-info-log" value is invalid; it should be either false, true, or "MARKER"`

// UnmarshalJSON implements the `json.Marshaler` interface to ensure the compatibility.
func (t *RedactInfoLogType) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err == nil && strings.ToUpper(s) == "MARKER" {
*t = RedactInfoLogMarker
return nil
}
var b bool
err = json.Unmarshal(data, &b)
if err != nil {
return errors.New(invalidRedactInfoLogTypeErrMsg)
}
if b {
*t = RedactInfoLogON
} else {
*t = RedactInfoLogOFF
}
return nil
}

// UnmarshalTOML implements the `toml.Unmarshaler` interface to ensure the compatibility.
func (t *RedactInfoLogType) UnmarshalTOML(data any) error {
switch v := data.(type) {
case bool:
if v {
*t = RedactInfoLogON
} else {
*t = RedactInfoLogOFF
}
return nil
case string:
if strings.ToUpper(v) == "MARKER" {
*t = RedactInfoLogMarker
return nil
}
return errors.New(invalidRedactInfoLogTypeErrMsg)
default:
}
return errors.New(invalidRedactInfoLogTypeErrMsg)
}

var curRedactType atomic.Value

func init() {
SetRedactLog(false)
SetRedactType(RedactInfoLogOFF)
}

// IsRedactLogEnabled indicates whether the log desensitization is enabled.
func IsRedactLogEnabled() bool {
return enabledRedactLog.Load()
func getRedactType() RedactInfoLogType {
return curRedactType.Load().(RedactInfoLogType)
}

// SetRedactLog sets enabledRedactLog.
func SetRedactLog(enabled bool) {
enabledRedactLog.Store(enabled)
func SetRedactType(redactInfoLogType RedactInfoLogType) {
curRedactType.Store(redactInfoLogType)
}

// RedactString receives string argument and return omitted information if redact log enabled.
func RedactString(arg string) string {
if IsRedactLogEnabled() {
return "?"
const (
leftMark = '‹'
rightMark = '›'
)

func redactInfo(input string) string {
res := &strings.Builder{}
res.Grow(len(input) + 2)
_, _ = res.WriteRune(leftMark)
for _, c := range input {
// Double the mark character if it is already in the input string.
// to avoid the ambiguity of the redacted content.
if c == leftMark || c == rightMark {
_, _ = res.WriteRune(c)
_, _ = res.WriteRune(c)
} else {
_, _ = res.WriteRune(c)
}
}
return arg
_, _ = res.WriteRune(rightMark)
return res.String()
}

// ZapRedactString receives string argument and return omitted information in zap.Field if redact log enabled.
// ZapRedactString receives string argument and returns a zap.Field with the value either:
// - unchanged if redaction is disabled
// - replaced with "?" if redaction is enabled
// - wrapped in markers ‹› if marker mode is enabled
func ZapRedactString(key, arg string) zap.Field {
return zap.String(key, RedactString(arg))
switch getRedactType() {
case RedactInfoLogON:
return zap.String(key, "?")
case RedactInfoLogMarker:
return zap.String(key, redactInfo(arg))
default:
}
return zap.String(key, arg)
}
5 changes: 3 additions & 2 deletions dm/worker/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func NewConfig() *Config {
fs.StringVar(&cfg.LogLevel, "L", "info", "log level: debug, info, warn, error, fatal")
fs.StringVar(&cfg.LogFile, "log-file", "", "log file path")
fs.StringVar(&cfg.LogFormat, "log-format", "text", `the format of the log, "text" or "json"`)
fs.BoolVar(&cfg.RedactInfoLog, "redact-info-log", false, "whether to enable the log redaction")
fs.Var(&cfg.RedactInfoLog, "redact-info-log", "whether to enable log redaction (false/true/marker)")
// fs.StringVar(&cfg.LogRotate, "log-rotate", "day", "log file rotate type, hour/day")
// NOTE: add `advertise-addr` for dm-master if needed.
fs.StringVar(&cfg.Join, "join", "", `join to an existing cluster (usage: dm-master cluster's "${master-addr}")`)
Expand Down Expand Up @@ -95,7 +95,8 @@ type Config struct {
// RedactInfoLog indicates that whether to enable the log redaction. It can be the following values:
// - false: disable redact log.
// - true: enable redact log, which will replace the sensitive information with "?".
RedactInfoLog bool `toml:"redact-info-log" json:"redact-info-log"`
// - marker: enable redact log, which will use single guillemets ‹› to enclose the sensitive information.
RedactInfoLog log.RedactInfoLogType `toml:"redact-info-log" json:"redact-info-log"`

Join string `toml:"join" json:"join" `
WorkerAddr string `toml:"worker-addr" json:"worker-addr"`
Expand Down
Loading