Skip to content

Commit

Permalink
Adding redactable records
Browse files Browse the repository at this point in the history
  • Loading branch information
gildas committed May 5, 2024
1 parent ba9b1d2 commit 24923ab
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 39 deletions.
35 changes: 21 additions & 14 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// Logger is a Logger that creates Bunyan's compatible logs (see: https://github.com/trentm/node-bunyan)
type Logger struct {
stream Streamer
record Record
record *Record
redactors []Redactor
}

Expand Down Expand Up @@ -84,7 +84,7 @@ func Create(name string, parameters ...interface{}) (logger *Logger) {
}

for _, record := range records {
for key, value := range record {
for key, value := range record.Data {
logger.record.Set(key, value)
}
}
Expand Down Expand Up @@ -151,6 +151,13 @@ func (log *Logger) Record(key string, value interface{}) *Logger {
return &Logger{log, NewRecord().Set(key, value), log.redactors}
}

// RecordWithKeysToRedact adds the given Record to the Log
func (log *Logger) RecordWithKeysToRedact(key string, value interface{}, keyToRedact ...string) *Logger {
// This func requires Logger to be a Stream
// that allows us to nest Loggers
return &Logger{log, NewRecord().Set(key, value).AddKeysToRedact(keyToRedact...), log.redactors}
}

// Recordf adds the given Record with formatted arguments
func (log *Logger) Recordf(key, value string, args ...interface{}) *Logger {
return log.Record(key, fmt.Sprintf(value, args...))
Expand Down Expand Up @@ -182,15 +189,15 @@ func (log *Logger) Records(params ...interface{}) *Logger {
// Topic sets the Topic of this Logger
func (log *Logger) Topic(topic interface{}) *Logger {
if topic == nil {
topic = log.record["topic"]
topic = log.record.Get("topic")
}
return log.Record("topic", topic)
}

// Scope sets the Scope if this Logger
func (log *Logger) Scope(scope interface{}) *Logger {
if scope == nil {
scope = log.record["scope"]
scope = log.record.Get("scope")
}
return log.Record("scope", scope)
}
Expand All @@ -199,10 +206,10 @@ func (log *Logger) Scope(scope interface{}) *Logger {
func (log *Logger) Child(topic, scope interface{}, params ...interface{}) *Logger {
var key string
if topic == nil {
topic = log.record["topic"]
topic = log.record.Get("topic")
}
if scope == nil {
scope = log.record["scope"]
scope = log.record.Get("scope")
}
record := NewRecord().Set("topic", topic).Set("scope", scope)
newlog := &Logger{log, record, log.redactors}
Expand Down Expand Up @@ -231,7 +238,7 @@ func (log *Logger) Child(topic, scope interface{}, params ...interface{}) *Logge

// GetRecord returns the Record field value for a given key
func (log *Logger) GetRecord(key string) interface{} {
if value, found := log.record[key]; found {
if value := log.record.Get(key); value != nil {
return value
}
if parent, ok := log.stream.(*Logger); ok {
Expand Down Expand Up @@ -350,8 +357,8 @@ func (log *Logger) send(level Level, msg string, args ...interface{}) {
if log.ShouldWrite(level, log.GetTopic(), log.GetScope()) {
record, release := NewPooledRecord()
defer release()
record["time"] = time.Now().UTC()
record["level"] = level
record.Set("time", time.Now().UTC())
record.Set("level", level)
if log.stream.ShouldLogSourceInfo() {
if counter, file, line, ok := runtime.Caller(2); ok {
funcname := runtime.FuncForPC(counter).Name()
Expand All @@ -361,10 +368,10 @@ func (log *Logger) send(level Level, msg string, args ...interface{}) {
}
i += strings.Index(funcname[i:], ".")

record["file"] = filepath.Base(file)
record["line"] = line
record["func"] = funcname[i+1:]
record["package"] = funcname[:i]
record.Set("file", filepath.Base(file))
record.Set("line", line)
record.Set("func", funcname[i+1:])
record.Set("package", funcname[:i])
}
}
message := fmt.Sprintf(msg, args...)
Expand All @@ -374,7 +381,7 @@ func (log *Logger) send(level Level, msg string, args ...interface{}) {
break
}
}
record["msg"] = message
record.Set("msg", message)
if err := log.Write(record); err != nil {
fmt.Fprintf(os.Stderr, "Logger error: %+v\n", errors.RuntimeError.Wrap(err))
}
Expand Down
12 changes: 5 additions & 7 deletions mappool.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@ func NewMapPool() *MapPool {
}

// Get selects an arbitrary map from the Pool, removes it from the Pool, and returns it to the caller.
func (pool *MapPool) Get() (m map[string]interface{}) {
return (*sync.Pool)(pool).Get().(map[string]interface{})
func (pool *MapPool) Get() (*Record) {
return (*sync.Pool)(pool).Get().(*Record)
}

// Put adds a map to the Pool.
func (pool *MapPool) Put(m map[string]interface{}) {
for key := range m {
delete(m, key)
}
(*sync.Pool)(pool).Put(m)
func (pool *MapPool) Put(record *Record) {
record.Reset()
(*sync.Pool)(pool).Put(record)
}
63 changes: 47 additions & 16 deletions record.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,74 @@ import (
// Record is the map that contains all records of a log entry
//
// If the value at a key is a func() interface the func will be called when the record is marshaled
type Record map[string]interface{}
type Record struct {
Data map[string]interface{}
KeysToRedact []string
}

// NewRecord creates a new empty record
func NewRecord() Record {
return Record(make(map[string]interface{}))
func NewRecord() *Record {
return &Record {
Data: make(map[string]interface{}),
KeysToRedact: nil,
}
}

// Reset resets the record
func (record *Record) Reset() {
for key := range record.Data {
delete(record.Data, key)
}
record.KeysToRedact = nil
}

// NewPooledRecord creates a new empty record
func NewPooledRecord() (record Record, release func()) {
record = Record(mapPool.Get())
func NewPooledRecord() (record *Record, release func()) {
record = mapPool.Get()
return record, func() { record.Close() }
}

// Close returns the record to the pool
func (record Record) Close() {
func (record *Record) Close() {
mapPool.Put(record)
}

// Get gets the value at a key
func (record *Record) Get(key string) interface{} {
return record.Data[key]
}

// Set sets the key and value if not yet set
func (record Record) Set(key string, value interface{}) Record {
func (record *Record) Set(key string, value interface{}) *Record {
if value == nil {
return record
}
if _, ok := record[key]; !ok {
record[key] = value
if _, ok := record.Data[key]; !ok {
record.Data[key] = value
}
return record
}

// AddKeysToRedact adds keys to redact
func (record *Record) AddKeysToRedact(keys ...string) *Record {
record.KeysToRedact = append(record.KeysToRedact, keys...)
return record
}

// Merge merges a source Record into this Record
//
// values already set in this record cannot be overridden
func (record Record) Merge(source Record) Record {
for key, value := range source {
func (record *Record) Merge(source *Record) *Record {
for key, value := range source.Data {
record.Set(key, value)
}
record.KeysToRedact = append(record.KeysToRedact, source.KeysToRedact...)
return record
}

// MarshalJSON marshals this into JSON
func (record Record) MarshalJSON() ([]byte, error) {
if len(record) == 0 {
if len(record.Data) == 0 {
return []byte("null"), nil
}

Expand All @@ -64,7 +90,7 @@ func (record Record) MarshalJSON() ([]byte, error) {
defer bufferPool.Put(buffer)

buffer.WriteString("{")
for key, raw := range record {
for key, raw := range record.Data {
if raw == nil {
continue
}
Expand All @@ -76,7 +102,7 @@ func (record Record) MarshalJSON() ([]byte, error) {
buffer.WriteString(`"`)
buffer.WriteString(key)
buffer.WriteString(`":`)
jsonValue(raw, buffer)
jsonValue(raw, buffer, record.KeysToRedact...)
}
buffer.WriteString("}")
return buffer.Bytes(), nil
Expand All @@ -88,14 +114,19 @@ func (record *Record) UnmarshalJSON(payload []byte) error {
if err := json.Unmarshal(payload, &placeholder); err != nil {
return errors.JSONUnmarshalError.Wrap(err)
}
*record = Record(placeholder)
*record = Record{
Data: placeholder,
KeysToRedact: nil,
}
return nil
}

func jsonValue(object interface{}, buffer *bytes.Buffer) {
func jsonValue(object interface{}, buffer *bytes.Buffer, keyToRedact ...string) {
switch value := object.(type) {
case func() interface{}:
object = value()
case RedactableWithKeys:
object = value.Redact(keyToRedact...)
case Redactable:
object = value.Redact()
}
Expand Down
7 changes: 7 additions & 0 deletions redactable.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ package logger
type Redactable interface {
Redact() interface{}
}

// RedactableWithKeys can be used by structs that want to redact their fields
//
// When the Logger writes the Record contains a RedactableWithKeys, it will call Redact
type RedactableWithKeys interface {
Redact(key ...string) interface{}
}
2 changes: 1 addition & 1 deletion stream-logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
// Write writes the given Record
//
// implements logger.Streamer
func (log *Logger) Write(record Record) error {
func (log *Logger) Write(record *Record) error {
// implements logger.Stream
record.Merge(log.record)
return log.stream.Write(record)
Expand Down
2 changes: 1 addition & 1 deletion stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
// Streamer is the interface a Logger writes to
type Streamer interface {
// Write writes the given Record
Write(record Record) error
Write(record *Record) error

// ShouldWrite tells if the given level should be written to this stream
ShouldWrite(level Level, topic, scope string) bool
Expand Down

0 comments on commit 24923ab

Please sign in to comment.