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

feat: add stdlog support #2

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
271 changes: 271 additions & 0 deletions polystd/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
package polystd

import (
"fmt"
"log"
"strings"
"sync"
"time"

"github.com/pokt-network/polylog"
)

const (
errorFieldKey = "error"
// TODO_IMPROVE: Support configurable timestamp format via an options.
defaultTimeLayout = time.RFC3339
)

var _ polylog.Event = (*stdLogEvent)(nil)

type stdLogEvent struct {
levelString string
fieldsMu sync.Mutex
fields stdLogFields
discardedMu sync.Mutex
discarded bool
}

type stdLogFields map[string]any

func newEvent(level Level) polylog.Event {
return &stdLogEvent{
levelString: getLevelLabel(level),
fields: make(stdLogFields),
}
}

func (sle *stdLogEvent) Str(key, value string) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Bool(key string, value bool) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Int(key string, value int) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Int8(key string, value int8) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Int16(key string, value int16) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Int32(key string, value int32) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Int64(key string, value int64) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Uint(key string, value uint) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Uint8(key string, value uint8) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Uint16(key string, value uint16) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Uint32(key string, value uint32) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Uint64(key string, value uint64) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Float32(key string, value float32) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Float64(key string, value float64) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value
return sle
}

func (sle *stdLogEvent) Err(err error) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[errorFieldKey] = err
return sle
}

func (sle *stdLogEvent) Timestamp() polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

// TODO_IMPROVE: this key should be configurable via an option.
sle.fields["time"] = time.Now().Format(defaultTimeLayout)
return sle
}

func (sle *stdLogEvent) Time(key string, value time.Time) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value.Format(defaultTimeLayout)
return sle
}

func (sle *stdLogEvent) Dur(key string, value time.Duration) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

sle.fields[key] = value.String()
return sle
}

func (sle *stdLogEvent) Fields(fields any) polylog.Event {
sle.fieldsMu.Lock()
defer sle.fieldsMu.Unlock()

switch fieldsVal := fields.(type) {
case map[string]any:
for key, value := range fieldsVal {
sle.fields[key] = value
}
case []any:
var nextKey string
for fieldIdx, value := range fieldsVal {
if fieldIdx%2 == 0 {
nextKey = value.(string)
} else {
sle.fields[nextKey] = value
}
}
}
return sle
}

func (sle *stdLogEvent) Func(fn func(polylog.Event)) polylog.Event {
if sle.Enabled() {
fn(sle)
}
return sle
}

func (sle *stdLogEvent) Enabled() bool {
sle.discardedMu.Lock()
defer sle.discardedMu.Unlock()

return !sle.discarded
}

func (sle *stdLogEvent) Discard() polylog.Event {
sle.discardedMu.Lock()
defer sle.discardedMu.Unlock()

sle.discarded = true
return sle
}

func (sle *stdLogEvent) Msg(msg string) {
log.Println(sle.levelString, sle.fields.String(), msg)
}

func (sle *stdLogEvent) Msgf(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
log.Println(sle.levelString, sle.fields.String(), msg)
}

func (sle *stdLogEvent) Send() {
log.Println(sle.levelString, sle.fields.String())
}

// TODO_IMPROVE: Support configurable key/value and field delimiters via options.
func (stf stdLogFields) String() string {
var fieldLines []string
for key, value := range stf {
var line string
switch concreteVal := value.(type) {
case string:
line = fmt.Sprintf("%q:%q", key, value)
case error:
line = fmt.Sprintf("%q:%q", key, concreteVal.Error())
default:
line = fmt.Sprintf("%q:%v", key, value)
}
fieldLines = append(fieldLines, line)
}
return strings.Join(fieldLines, ",")
}

func getLevelLabel(level Level) string {
switch level {
case DebugLevel:
return "[DEBUG]"
case InfoLevel:
return "[INFO]"
case WarnLevel:
return "[WARN]"
case ErrorLevel:
return "[ERROR]"
default:
return "[UNKNOWN]"
}
}
39 changes: 39 additions & 0 deletions polystd/levels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package polystd

const (
DebugLevel Level = iota
InfoLevel
WarnLevel
ErrorLevel
// TODO_IN_THIS_COMMIT: consider fatal and panic levels.
)

type Level int

func Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
}
}

func (l Level) String() string {
switch l {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warn"
case ErrorLevel:
return "error"
default:
return "unknown"
}
}

func (l Level) Int() int {
return int(l)
}
74 changes: 74 additions & 0 deletions polystd/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package polystd

import (
"context"
"fmt"
"log"

"github.com/pokt-network/polylog"
)

var _ polylog.Logger = (*stdLogLogger)(nil)

type stdLogLogger struct {
level Level
}

func NewLogger(opts ...polylog.LoggerOption) polylog.Logger {
logger := &stdLogLogger{}

for _, opt := range opts {
opt(logger)
}

return logger
}

func (st *stdLogLogger) Debug() polylog.Event {
return newEvent(DebugLevel)
}

func (st *stdLogLogger) Info() polylog.Event {
return newEvent(InfoLevel)
}

func (st *stdLogLogger) Warn() polylog.Event {
return newEvent(WarnLevel)
}

func (st *stdLogLogger) Error() polylog.Event {
return newEvent(ErrorLevel)
}

// WithContext ...
//
// TODO_TEST/TODO_COMMUNITY: test-drive (TDD) out `polystd.Logger#WithContext()`.
func (st *stdLogLogger) WithContext(ctx context.Context) context.Context {
panic("not yet implemented")
}

func (st *stdLogLogger) With(keyVals ...any) polylog.Logger {
// TODO_TECHDEBT:TODO_COMMUNITY: implement this to have analogous behavior
// to that of `polyzero.Logger`'s. Investigate `log.SetPrefix()` and consider
// combining the level label with formatted keyVals as a prefix.
panic("not yet implemented")
}

func (st *stdLogLogger) WithLevel(level polylog.Level) polylog.Event {
switch level.String() {
case DebugLevel.String():
return st.Debug()
case InfoLevel.String():
return st.Info()
case WarnLevel.String():
return st.Warn()
case ErrorLevel.String():
return st.Error()
default:
panic(fmt.Sprintf("level not supported: %s", level.String()))
}
}

func (st *stdLogLogger) Write(p []byte) (n int, err error) {
return log.Writer().Write(p)
}
Loading