diff --git a/pkg/frontier/edgebound/edge_dataplane.go b/pkg/frontier/edgebound/edge_dataplane.go index a454c4f..daf37a0 100644 --- a/pkg/frontier/edgebound/edge_dataplane.go +++ b/pkg/frontier/edgebound/edge_dataplane.go @@ -1,6 +1,7 @@ package edgebound import ( + "github.com/singchia/frontier/pkg/frontier/misc" "github.com/singchia/geminio" "k8s.io/klog/v2" ) @@ -9,7 +10,7 @@ func (em *edgeManager) acceptStream(stream geminio.Stream) { edgeID := stream.ClientID() streamID := stream.StreamID() meta := stream.Meta() - klog.V(2).Infof("edge accept stream, edgeID: %d, streamID: %d, meta: %s", edgeID, streamID, meta) + klog.V(2).Infof("edge accept stream, edgeID: %d, streamID: %d, meta: %s", edgeID, streamID, misc.Redact(string(meta))) // cache em.streams.MSet(edgeID, streamID, stream) @@ -23,7 +24,7 @@ func (em *edgeManager) closedStream(stream geminio.Stream) { edgeID := stream.ClientID() streamID := stream.StreamID() meta := stream.Meta() - klog.V(2).Infof("edge closed stream, edgeID: %d, streamID: %d, meta: %s", edgeID, streamID, meta) + klog.V(2).Infof("edge closed stream, edgeID: %d, streamID: %d, meta: %s", edgeID, streamID, misc.Redact(string(meta))) // cache em.streams.MDel(edgeID, streamID) // when the stream ends, the exchange can be noticed by functional error, so we don't update exchange @@ -33,7 +34,7 @@ func (em *edgeManager) closedStream(stream geminio.Stream) { func (em *edgeManager) forward(end geminio.End) { edgeID := end.ClientID() meta := end.Meta() - klog.V(2).Infof("edge forward raw message and rpc, edgeID: %d, meta: %s", edgeID, meta) + klog.V(2).Infof("edge forward raw message and rpc, edgeID: %d, meta: %s", edgeID, misc.Redact(string(meta))) if em.exchange != nil { em.exchange.ForwardToService(end) } diff --git a/pkg/frontier/edgebound/edge_manager.go b/pkg/frontier/edgebound/edge_manager.go index 6575e28..ea7de29 100644 --- a/pkg/frontier/edgebound/edge_manager.go +++ b/pkg/frontier/edgebound/edge_manager.go @@ -8,6 +8,7 @@ import ( "strings" "sync" + "github.com/jumboframes/armorigo/log" "github.com/jumboframes/armorigo/rproxy" "github.com/jumboframes/armorigo/synchub" "github.com/singchia/frontier/pkg/frontier/apis" @@ -141,6 +142,9 @@ func (em *edgeManager) Serve() error { func (em *edgeManager) handleConn(conn net.Conn) error { // options for geminio End opt := server.NewEndOptions() + // route SDK logs through klog: the SDK default logger prints raw + // connection meta to stdout on error paths, bypassing redaction. + opt.SetLog(log.NewKLog()) opt.SetTimer(em.tmr) opt.SetDelegate(em) // stream handler diff --git a/pkg/frontier/edgebound/edge_onoff.go b/pkg/frontier/edgebound/edge_onoff.go index 38e5a8b..947200e 100644 --- a/pkg/frontier/edgebound/edge_onoff.go +++ b/pkg/frontier/edgebound/edge_onoff.go @@ -8,6 +8,7 @@ import ( "github.com/jumboframes/armorigo/synchub" "github.com/singchia/frontier/pkg/frontier/apis" + "github.com/singchia/frontier/pkg/frontier/misc" "github.com/singchia/frontier/pkg/frontier/repo/model" "github.com/singchia/frontier/pkg/frontier/repo/query" "github.com/singchia/geminio" @@ -145,7 +146,7 @@ func (em *edgeManager) ConnOnline(d delegate.ConnDescriber) error { return err } } - klog.V(2).Infof("edge online, edgeID: %d, meta: %s, addr: %s", edgeID, string(meta), addr) + klog.V(2).Infof("edge online, edgeID: %d, meta: %s, addr: %s", edgeID, misc.Redact(string(meta)), addr) return nil } @@ -154,12 +155,12 @@ func (em *edgeManager) ConnOffline(d delegate.ConnDescriber) error { meta := d.Meta() addr := d.RemoteAddr() - klog.V(2).Infof("edge offline, edgeID: %d, meta: %s, addr: %s", edgeID, string(meta), addr) + klog.V(2).Infof("edge offline, edgeID: %d, meta: %s, addr: %s", edgeID, misc.Redact(string(meta)), addr) // offline the cache err := em.offline(edgeID, meta, addr) if err != nil { klog.Errorf("edge offline, cache or db offline err: %s, edgeID: %d, meta: %s, addr: %s", - err, edgeID, string(meta), addr) + err, edgeID, misc.Redact(string(meta)), addr) return err } return nil @@ -169,7 +170,7 @@ func (em *edgeManager) Heartbeat(d delegate.ConnDescriber) error { edgeID := d.ClientID() meta := string(d.Meta()) addr := d.RemoteAddr() - klog.V(3).Infof("edge heartbeat, edgeID: %d, meta: %s, addr: %s", edgeID, string(meta), addr) + klog.V(3).Infof("edge heartbeat, edgeID: %d, meta: %s, addr: %s", edgeID, misc.Redact(string(meta)), addr) if em.informer != nil { em.informer.EdgeHeartbeat(edgeID, d.Meta(), addr) } @@ -199,14 +200,14 @@ func (em *edgeManager) GetClientID(_ uint64, meta []byte) (uint64, error) { if em.exchange != nil { edgeID, err = em.exchange.GetEdgeID(meta) if err == nil { - klog.V(2).Infof("edge get edgeID: %d from exchange, meta: %s", edgeID, string(meta)) + klog.V(2).Infof("edge get edgeID: %d from exchange, meta: %s", edgeID, misc.Redact(string(meta))) return edgeID, nil } } if (err == apis.ErrServiceNotOnline || err == apis.ErrRPCNotOnline) && em.conf.Edgebound.EdgeIDAllocWhenNoIDServiceOn { edgeID = em.idFactory.GetID() - klog.V(2).Infof("edge get edgeID: %d, meta: %s, after no ID acquired from exchange", edgeID, string(meta)) + klog.V(2).Infof("edge get edgeID: %d, meta: %s, after no ID acquired from exchange", edgeID, misc.Redact(string(meta))) return em.idFactory.GetID(), nil } return 0, err diff --git a/pkg/frontier/exchange/oob.go b/pkg/frontier/exchange/oob.go index 11c5a06..e8aded8 100644 --- a/pkg/frontier/exchange/oob.go +++ b/pkg/frontier/exchange/oob.go @@ -17,7 +17,7 @@ import ( func (ex *exchange) GetEdgeID(meta []byte) (uint64, error) { svc, err := ex.Servicebound.GetServiceByRPC(apis.RPCGetEdgeID) if err != nil { - klog.V(2).Infof("exchange get edgeID, get service err: %s, meta: %s", err, string(meta)) + klog.V(2).Infof("exchange get edgeID, get service err: %s, meta: %s", err, misc.Redact(string(meta))) if err == apis.ErrRecordNotFound { return 0, apis.ErrServiceNotOnline } @@ -32,7 +32,7 @@ func (ex *exchange) GetEdgeID(meta []byte) (uint64, error) { opt.SetTimeout(30 * time.Second) rsp, err := svc.Call(context.TODO(), apis.RPCGetEdgeID, req, opt) if err != nil { - klog.V(2).Infof("exchange call service: %d, get edgeID err: %s, meta: %s", svc.ClientID(), err, meta) + klog.V(2).Infof("exchange call service: %d, get edgeID err: %s, meta: %s", svc.ClientID(), err, misc.Redact(string(meta))) return 0, err } data := rsp.Data() @@ -45,7 +45,7 @@ func (ex *exchange) GetEdgeID(meta []byte) (uint64, error) { func (ex *exchange) EdgeOnline(edgeID uint64, meta []byte, addr net.Addr) error { svcs, err := ex.Servicebound.GetServicesByRPC(apis.RPCEdgeOnline) if err != nil { - klog.V(2).Infof("exchange edge online, get service err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, string(meta), addr) + klog.V(2).Infof("exchange edge online, get service err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, misc.Redact(string(meta)), addr) if err == apis.ErrRecordNotFound { return apis.ErrServiceNotOnline } @@ -60,7 +60,7 @@ func (ex *exchange) EdgeOnline(edgeID uint64, meta []byte, addr net.Addr) error } data, err := json.Marshal(event) if err != nil { - klog.Errorf("exchange edge online, json marshal err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, string(meta), addr) + klog.Errorf("exchange edge online, json marshal err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, misc.Redact(string(meta)), addr) return err } @@ -72,7 +72,7 @@ func (ex *exchange) EdgeOnline(edgeID uint64, meta []byte, addr net.Addr) error opt.SetTimeout(30 * time.Second) _, err = svc.Call(context.TODO(), apis.RPCEdgeOnline, req, opt) if err != nil { - klog.V(2).Infof("exchange call service: %d, edge online err: %s, meta: %s, addr: %s", svc.ClientID(), err, meta, addr) + klog.V(2).Infof("exchange call service: %d, edge online err: %s, meta: %s, addr: %s", svc.ClientID(), err, misc.Redact(string(meta)), addr) return err } return nil @@ -81,7 +81,7 @@ func (ex *exchange) EdgeOnline(edgeID uint64, meta []byte, addr net.Addr) error func (ex *exchange) EdgeOffline(edgeID uint64, meta []byte, addr net.Addr) error { svcs, err := ex.Servicebound.GetServicesByRPC(apis.RPCEdgeOffline) if err != nil { - klog.V(2).Infof("exchange edge offline, get service err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, string(meta), addr) + klog.V(2).Infof("exchange edge offline, get service err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, misc.Redact(string(meta)), addr) if err == apis.ErrRecordNotFound { return apis.ErrServiceNotOnline } @@ -98,7 +98,7 @@ func (ex *exchange) EdgeOffline(edgeID uint64, meta []byte, addr net.Addr) error } data, err := json.Marshal(event) if err != nil { - klog.Errorf("exchange edge offline, json marshal err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, string(meta), addr) + klog.Errorf("exchange edge offline, json marshal err: %s, edgeID: %d, meta: %s, addr: %s", err, edgeID, misc.Redact(string(meta)), addr) return err } // call service @@ -107,7 +107,7 @@ func (ex *exchange) EdgeOffline(edgeID uint64, meta []byte, addr net.Addr) error opt.SetTimeout(30 * time.Second) _, err = svc.Call(context.TODO(), apis.RPCEdgeOffline, req, opt) if err != nil { - klog.V(2).Infof("exchange call service: %d, edge offline err: %s, meta: %s, addr: %s", svc.ClientID(), err, meta, addr) + klog.V(2).Infof("exchange call service: %d, edge offline err: %s, meta: %s, addr: %s", svc.ClientID(), err, misc.Redact(string(meta)), addr) return err } return nil diff --git a/pkg/frontier/misc/redact.go b/pkg/frontier/misc/redact.go new file mode 100644 index 0000000..aa744a3 --- /dev/null +++ b/pkg/frontier/misc/redact.go @@ -0,0 +1,124 @@ +package misc + +import ( + "bytes" + "encoding/json" + "strings" +) + +// redactedValue is the uniform replacement for sensitive field values. +const redactedValue = "***" + +// unparsableMetaPlaceholder is the fail-closed output for non-JSON input: +// the raw value is never echoed back, so malformed payloads cannot smuggle +// credentials into logs. +const unparsableMetaPlaceholder = "" + +// nonObjectMetaPlaceholder is the fail-closed output for JSON input whose +// top-level value is not an object or an array (e.g. a bare string). A +// top-level string is a common client mistake that carries an +// already-serialized object, so it is masked as a whole instead of being +// passed through. +const nonObjectMetaPlaceholder = "" + +// oversizedMetaPlaceholder is the fail-closed output for oversized meta. +// Meta is client-controlled and should not flood logs anyway; returning a +// placeholder upfront also avoids parsing hostile large JSON payloads. +const oversizedMetaPlaceholder = "" + +// maxMetaLen is the length limit for meta accepted by the log redaction path. +const maxMetaLen = 8 * 1024 + +// sensitiveKeyFragments holds lowercase substrings used to detect +// credential-like keys. Clients commonly carry access_key/secret_key/token +// and friends in connection meta, and those must be masked before logging. +// Substring matching covers naming variants (accessKey, secret_key_2, ...). +// It intentionally errs on the side of over-masking: innocuous keys such as +// "keyword" or "author" are masked too, which is preferred over leaking. +var sensitiveKeyFragments = []string{ + "key", + "secret", + "token", + "password", + "passwd", + "pass", + "pwd", + "credential", + "auth", + "signature", + "bearer", + "session", + "cookie", + "cert", + "private", +} + +// Redact masks credential-like fields in a JSON meta string and returns the +// result, for use at every call site that logs meta. Contract: +// - empty/whitespace-only input is returned as is; +// - input longer than maxMetaLen fails closed with a placeholder; +// - values of sensitive keys (case-insensitive substring match) are +// replaced with "***" at any level (objects, nested objects, objects +// inside arrays), regardless of whether the value is a string or a +// composite; +// - a top-level value that is not an object or an array fails closed with +// a placeholder; +// - non-JSON input fails closed with a placeholder, never echoing the raw +// value. If valid JSON is followed by trailing garbage, only the first +// JSON value is parsed and the trailing content is dropped. +func Redact(meta string) string { + if strings.TrimSpace(meta) == "" { + return meta + } + if len(meta) > maxMetaLen { + return oversizedMetaPlaceholder + } + var v any + dec := json.NewDecoder(bytes.NewReader([]byte(meta))) + dec.UseNumber() + if err := dec.Decode(&v); err != nil { + return unparsableMetaPlaceholder + } + switch v.(type) { + case map[string]any, []any: + default: + return nonObjectMetaPlaceholder + } + redactValue(v) + out, err := json.Marshal(v) + if err != nil { + return unparsableMetaPlaceholder + } + return string(out) +} + +// redactValue recursively masks in place: map keys are matched one by one +// (a hit replaces the whole value), slices are descended element-wise, and +// other scalars are left untouched. +func redactValue(v any) { + switch val := v.(type) { + case map[string]any: + for k, item := range val { + if isSensitiveKey(k) { + val[k] = redactedValue + continue + } + redactValue(item) + } + case []any: + for _, item := range val { + redactValue(item) + } + } +} + +// isSensitiveKey matches the lowercased key against the fragment table. +func isSensitiveKey(key string) bool { + lower := strings.ToLower(key) + for _, frag := range sensitiveKeyFragments { + if strings.Contains(lower, frag) { + return true + } + } + return false +} diff --git a/pkg/frontier/misc/redact_test.go b/pkg/frontier/misc/redact_test.go new file mode 100644 index 0000000..ee1c7c2 --- /dev/null +++ b/pkg/frontier/misc/redact_test.go @@ -0,0 +1,139 @@ +package misc + +import ( + "strings" + "testing" +) + +// Table-driven tests: values of credential-like JSON keys must be masked, +// non-sensitive fields must be preserved, and every non-object input shape +// (non-JSON, oversized, top-level scalar) must fail closed without echoing +// the raw value. +func TestRedact(t *testing.T) { + testcases := []struct { + name string + in string + want string + }{ + { + name: "empty string returned as is", + in: "", + want: "", + }, + { + name: "whitespace-only string returned as is", + in: " ", + want: " ", + }, + { + name: "access_key and secret_key masked", + in: `{"access_key":"AK123","secret_key":"SK456"}`, + want: `{"access_key":"***","secret_key":"***"}`, + }, + { + name: "non-sensitive fields preserved", + in: `{"hostname":"edge5","version":"1.0"}`, + want: `{"hostname":"edge5","version":"1.0"}`, + }, + { + name: "token/password/passwd/credential variants masked", + in: `{"token":"t1","password":"p1","passwd":"p2","credentials":"c1"}`, + want: `{"credentials":"***","passwd":"***","password":"***","token":"***"}`, + }, + { + name: "extended fragment table: pwd/passphrase/bearer/session/cookie/cert/private masked", + in: `{"pwd":"p","passphrase":"pp","bearer":"bt","session_id":"s1","cookie":"ck","client_cert":"c","private":"pr"}`, + want: `{"bearer":"***","client_cert":"***","cookie":"***","passphrase":"***","private":"***","pwd":"***","session_id":"***"}`, + }, + { + name: "case-insensitive matching", + in: `{"AccessKey":"AK","SECRET_KEY":"SK"}`, + want: `{"AccessKey":"***","SECRET_KEY":"***"}`, + }, + { + name: "sensitive field inside nested object masked", + in: `{"client":{"secret_key":"SK","name":"edge"},"keep":"v"}`, + want: `{"client":{"name":"edge","secret_key":"***"},"keep":"v"}`, + }, + { + name: "sensitive field inside object in array masked", + in: `[{"token":"t"},{"ok":1}]`, + want: `[{"token":"***"},{"ok":1}]`, + }, + { + name: "non-string value under sensitive key also masked", + in: `{"access_key":{"nested":"x"},"secret_key":[1,2]}`, + want: `{"access_key":"***","secret_key":"***"}`, + }, + { + name: "key fragment matches anywhere in key name", + in: `{"edge_access_key_2":"AK","region":"cn"}`, + want: `{"edge_access_key_2":"***","region":"cn"}`, + }, + { + name: "authorization and signature variants masked", + in: `{"authorization":"Bearer eyJ","signature":"sig1","region":"cn"}`, + want: `{"authorization":"***","region":"cn","signature":"***"}`, + }, + { + name: "over-redaction of innocuous lookalike keys is accepted", + in: `{"keyword":"k","author":"a","monkey":"m"}`, + want: `{"author":"***","keyword":"***","monkey":"***"}`, + }, + { + name: "non-JSON input fails closed", + in: `not a json`, + want: ``, + }, + { + name: "top-level credential-bearing JSON string fails closed", + in: `"plain-secret-token-value"`, + want: ``, + }, + { + name: "double-encoded JSON with credentials fails closed", + in: `"{\"secret_key\":\"SK\"}"`, + want: ``, + }, + { + name: "top-level scalar number fails closed", + in: `123`, + want: ``, + }, + { + name: "top-level null fails closed", + in: `null`, + want: ``, + }, + { + name: "trailing garbage after valid JSON is dropped", + in: `{"a":1} trailing-secret`, + want: `{"a":1}`, + }, + { + name: "input at exactly maxMetaLen bytes still redacts", + in: `{"filler":"` + strings.Repeat("x", maxMetaLen-13) + `"}`, + want: `{"filler":"` + strings.Repeat("x", maxMetaLen-13) + `"}`, + }, + { + name: "input one byte over maxMetaLen fails closed", + in: `{"filler":"` + strings.Repeat("x", maxMetaLen-12) + `"}`, + want: ``, + }, + { + name: "oversized meta fails closed", + in: `{"filler":"` + strings.Repeat("x", 9*1024) + `"}`, + want: ``, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + got := Redact(tc.in) + if got != tc.want { + t.Errorf("Redact(%q) = %q, want %q", tc.in, got, tc.want) + } + }) + } +} diff --git a/pkg/frontier/servicebound/service_dataplane.go b/pkg/frontier/servicebound/service_dataplane.go index f1bcf05..8b8ec09 100644 --- a/pkg/frontier/servicebound/service_dataplane.go +++ b/pkg/frontier/servicebound/service_dataplane.go @@ -2,6 +2,7 @@ package servicebound import ( "github.com/singchia/frontier/pkg/frontier/apis" + "github.com/singchia/frontier/pkg/frontier/misc" "github.com/singchia/geminio" "k8s.io/klog/v2" ) @@ -10,7 +11,7 @@ func (sm *serviceManager) acceptStream(stream geminio.Stream) { serviceID := stream.ClientID() streamID := stream.StreamID() service := stream.Meta() - klog.V(2).Infof("service accept stream, serviceID: %d, streamID: %d, service: %s", serviceID, streamID, service) + klog.V(2).Infof("service accept stream, serviceID: %d, streamID: %d, service: %s", serviceID, streamID, misc.Redact(string(service))) // cache sm.streams.MSet(serviceID, streamID, stream) @@ -24,7 +25,7 @@ func (sm *serviceManager) closedStream(stream geminio.Stream) { serviceID := stream.ClientID() streamID := stream.StreamID() service := stream.Meta() - klog.V(2).Infof("service closed stream, serviceID: %d, streamID: %d, service: %s", serviceID, streamID, service) + klog.V(2).Infof("service closed stream, serviceID: %d, streamID: %d, service: %s", serviceID, streamID, misc.Redact(string(service))) // cache sm.streams.MDel(serviceID, streamID) // when the stream ends, the exchange can be noticed by functional error, so we don't update exchange @@ -33,6 +34,8 @@ func (sm *serviceManager) closedStream(stream geminio.Stream) { // forward to exchange func (sm *serviceManager) forward(meta *apis.Meta, end geminio.End) { serviceID := end.ClientID() + // meta.Service is a plain routing name parsed from a validated JSON + // object, not a credential carrier; log it as is. service := meta.Service klog.V(2).Infof("service forward raw message and rpc, serviceID: %d, service: %s", serviceID, service) if sm.exchange != nil { diff --git a/pkg/frontier/servicebound/service_manager.go b/pkg/frontier/servicebound/service_manager.go index 0754128..51d90e5 100644 --- a/pkg/frontier/servicebound/service_manager.go +++ b/pkg/frontier/servicebound/service_manager.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/jumboframes/armorigo/log" "github.com/jumboframes/armorigo/synchub" "github.com/singchia/frontier/pkg/frontier/apis" "github.com/singchia/frontier/pkg/frontier/config" @@ -110,6 +111,9 @@ func (sm *serviceManager) Serve() error { func (sm *serviceManager) handleConn(conn net.Conn) error { // options for geminio End opt := server.NewEndOptions() + // route SDK logs through klog: the SDK default logger prints raw + // connection meta to stdout on error paths, bypassing redaction. + opt.SetLog(log.NewKLog()) opt.SetTimer(sm.tmr) opt.SetDelegate(sm) // stream handler diff --git a/pkg/frontier/servicebound/service_onoff.go b/pkg/frontier/servicebound/service_onoff.go index e41e4f5..bde3f24 100644 --- a/pkg/frontier/servicebound/service_onoff.go +++ b/pkg/frontier/servicebound/service_onoff.go @@ -7,6 +7,7 @@ import ( "github.com/jumboframes/armorigo/synchub" "github.com/singchia/frontier/pkg/frontier/apis" + "github.com/singchia/frontier/pkg/frontier/misc" "github.com/singchia/frontier/pkg/frontier/repo/model" "github.com/singchia/frontier/pkg/frontier/repo/query" "github.com/singchia/geminio" @@ -118,7 +119,7 @@ func (sm *serviceManager) ConnOnline(d delegate.ConnDescriber) error { serviceID := d.ClientID() meta := string(d.Meta()) addr := d.RemoteAddr() - klog.V(1).Infof("service online, serviceID: %d, service: %s, addr: %s", serviceID, meta, addr) + klog.V(1).Infof("service online, serviceID: %d, service: %s, addr: %s", serviceID, misc.Redact(meta), addr) // notification for others if sm.informer != nil { sm.informer.ServiceOnline(serviceID, meta, addr) @@ -130,12 +131,12 @@ func (sm *serviceManager) ConnOffline(d delegate.ConnDescriber) error { serviceID := d.ClientID() meta := string(d.Meta()) addr := d.RemoteAddr() - klog.V(1).Infof("service offline, serviceID: %d, service: %s, remote addr: %s", serviceID, meta, addr) + klog.V(1).Infof("service offline, serviceID: %d, service: %s, remote addr: %s", serviceID, misc.Redact(meta), addr) // offline the cache err := sm.offline(serviceID, addr) if err != nil { klog.Errorf("service offline, cache or db offline err: %s, serviceID: %d, meta: %s, addr: %s", - err, serviceID, meta, addr) + err, serviceID, misc.Redact(meta), addr) return err } if sm.informer != nil { @@ -149,7 +150,7 @@ func (sm *serviceManager) Heartbeat(d delegate.ConnDescriber) error { serviceID := d.ClientID() meta := string(d.Meta()) addr := d.RemoteAddr() - klog.V(3).Infof("service heartbeat, serviceID: %d, meta: %s, addr: %s", serviceID, string(meta), addr) + klog.V(3).Infof("service heartbeat, serviceID: %d, meta: %s, addr: %s", serviceID, misc.Redact(meta), addr) if sm.informer != nil { sm.informer.ServiceHeartbeat(serviceID, meta, addr) }