Skip to content

Commit

Permalink
feat_: api logging signals (#6150)
Browse files Browse the repository at this point in the history
* feat_: api logging signals

* fix_: proper marshal any type

* fix_: linter

* chore_: rename to redactionPlaceholder
  • Loading branch information
igor-sirotin authored Dec 7, 2024
1 parent dfb5918 commit 616a760
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 9 deletions.
34 changes: 30 additions & 4 deletions mobile/callog/status_request_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/status-im/status-go/internal/sentry"
)

const redactionPlaceholder = "***"

var sensitiveKeys = []string{
"password",
"newPassword",
Expand All @@ -32,6 +34,7 @@ var sensitiveKeys = []string{
"alchemyOptimismSepoliaToken",
"verifyENSURL",
"verifyTransactionURL",
"gifs/api-key",
}

var sensitiveRegexString = fmt.Sprintf(`(?i)(".*?(%s).*?")\s*:\s*("[^"]*")`, strings.Join(sensitiveKeys, "|"))
Expand Down Expand Up @@ -88,7 +91,7 @@ func Call(logger, requestLogger *zap.Logger, fn any, params ...any) any {

if requestLogger != nil {
methodName := getShortFunctionName(fn)
Log(requestLogger, methodName, params, resp, startTime)
LogCall(requestLogger, methodName, params, resp, startTime)
}

return resp
Expand All @@ -106,7 +109,7 @@ func removeSensitiveInfo(jsonStr string) string {
// see related test for the usage of this function
return sensitiveRegex.ReplaceAllStringFunc(jsonStr, func(match string) string {
parts := sensitiveRegex.FindStringSubmatch(match)
return fmt.Sprintf(`%s:"***"`, parts[1])
return fmt.Sprintf(`%s:"%s"`, parts[1], redactionPlaceholder)
})
}

Expand All @@ -132,7 +135,7 @@ func Recover(logger *zap.Logger) {
panic(err)
}

func Log(logger *zap.Logger, method string, params any, resp any, startTime time.Time) {
func LogCall(logger *zap.Logger, method string, params any, resp any, startTime time.Time) {
if logger == nil {
return
}
Expand All @@ -145,11 +148,34 @@ func Log(logger *zap.Logger, method string, params any, resp any, startTime time
)
}

func LogSignal(logger *zap.Logger, eventType string, event interface{}) {
if logger == nil {
return
}
logger.Debug("signal",
zap.String("type", eventType),
dataField("event", event),
)
}

func dataField(name string, data any) zap.Field {
dataString := removeSensitiveInfo(fmt.Sprintf("%+v", data))
dataString := removeSensitiveInfo(marshalData(data))
var paramsParsed any
if json.Unmarshal([]byte(dataString), &paramsParsed) == nil {
return zap.Any(name, paramsParsed)
}
return zap.String(name, dataString)
}

func marshalData(data any) string {
switch d := data.(type) {
case string:
return d
default:
bytes, err := json.Marshal(d)
if err != nil {
return "<failed to marshal value>"
}
return string(bytes)
}
}
64 changes: 60 additions & 4 deletions mobile/callog/status_request_log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package callog

import (
"bytes"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
Expand All @@ -11,6 +13,8 @@ import (

"github.com/stretchr/testify/require"

"github.com/brianvoe/gofakeit/v6"

"github.com/status-im/status-go/logutils/requestlog"
)

Expand All @@ -23,22 +27,22 @@ func TestRemoveSensitiveInfo(t *testing.T) {
{
name: "basic test",
input: `{"username":"user1","password":"secret123","mnemonic":"mnemonic123 xyz"}`,
expected: `{"username":"user1","password":"***","mnemonic":"***"}`,
expected: fmt.Sprintf(`{"username":"user1","password":"%s","mnemonic":"%s"}`, redactionPlaceholder, redactionPlaceholder),
},
{
name: "uppercase password field",
input: `{"USERNAME":"user1","PASSWORD":"secret123"}`,
expected: `{"USERNAME":"user1","PASSWORD":"***"}`,
expected: fmt.Sprintf(`{"USERNAME":"user1","PASSWORD":"%s"}`, redactionPlaceholder),
},
{
name: "password field with spaces",
input: `{"username":"user1", "password" : "secret123"}`,
expected: `{"username":"user1", "password":"***"}`,
expected: fmt.Sprintf(`{"username":"user1", "password":"%s"}`, redactionPlaceholder),
},
{
name: "multiple password fields",
input: `{"password":"secret123","data":{"nested_password":"nested_secret"}}`,
expected: `{"password":"***","data":{"nested_password":"***"}}`,
expected: fmt.Sprintf(`{"password":"%s","data":{"nested_password":"%s"}}`, redactionPlaceholder, redactionPlaceholder),
},
{
name: "no password field",
Expand Down Expand Up @@ -179,3 +183,55 @@ func TestDataField(t *testing.T) {
require.NotNil(t, buf)
require.Equal(t, `{"root":"{non-json content}"}`+"\n", buf.String())
}

func TestSignal(t *testing.T) {
entry := zapcore.Entry{}
enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{})

// Simulate pairing.AccountData and pairing.Event without importing the package
type Data struct {
Name string `json:"name" fake:"{firstname}"`
Password string `json:"password"`
}
type Event struct {
Data any `json:"data,omitempty"`
}

data := Data{}
err := gofakeit.Struct(&data)
require.NoError(t, err)

event := Event{Data: data}

f := dataField("event", event)
require.NotNil(t, f)
require.Equal(t, "event", f.Key)
require.Equal(t, zapcore.ReflectType, f.Type)

buf, err := enc.EncodeEntry(entry, []zapcore.Field{f})
require.NoError(t, err)
require.NotNil(t, buf)

t.Logf("encoded event: %s", buf.String())

var result map[string]interface{}
err = json.Unmarshal(buf.Bytes(), &result)
require.NoError(t, err)

resultEvent, ok := result["event"]
require.True(t, ok)
require.NotNil(t, resultEvent)

resultEventMap, ok := resultEvent.(map[string]interface{})
require.True(t, ok)
require.NotNil(t, resultEventMap)

resultData, ok := resultEventMap["data"]
require.True(t, ok)
require.NotNil(t, resultData)

resultDataMap, ok := resultData.(map[string]interface{})
require.True(t, ok)
require.NotNil(t, resultDataMap)
require.Equal(t, redactionPlaceholder, resultDataMap["password"])
}
2 changes: 1 addition & 1 deletion mobile/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func InitializeApplication(requestJSON string) string {

startTime := time.Now()
response := initializeApplication(requestJSON)
callog.Log(
callog.LogCall(
requestlog.GetRequestLogger(),
"InitializeApplication",
requestJSON,
Expand Down
4 changes: 4 additions & 0 deletions signal/signals.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"go.uber.org/zap"

"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/logutils/requestlog"
"github.com/status-im/status-go/mobile/callog"
)

// MobileSignalHandler is a simple callback function that gets called when any signal is received
Expand Down Expand Up @@ -50,6 +52,8 @@ func send(typ string, event interface{}) {
logger.Error("Marshalling signal envelope", zap.Error(err))
return
}
callog.LogSignal(requestlog.GetRequestLogger(), typ, event)

// If a Go implementation of signal handler is set, let's use it.
if mobileSignalHandler != nil {
mobileSignalHandler(data)
Expand Down

0 comments on commit 616a760

Please sign in to comment.