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: new helper functions + tests #22

Merged
merged 1 commit into from
May 13, 2024
Merged
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 .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup Golang
uses: actions/setup-go@v3
with:
go-version: 1.20
go-version: 1.22
check-latest: true

- name: Run linter
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.idea/.gitignore
/.idea/modules.xml
/.idea/scriptorium.iml
/.idea/vcs.xml
16 changes: 16 additions & 0 deletions clog/attrs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package clog

import "log/slog"

type fieldKey string

type fields map[fieldKey]interface{}

// convertToAttrs converts a map of custom fields to a slice of slog.Attr
func convertToAttrs(fields fields) []any {
var attrs []any
for k, v := range fields {
attrs = append(attrs, slog.Any(string(k), v))
}
return attrs
}
75 changes: 75 additions & 0 deletions clog/clog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package clog

import (
"context"
"fmt"
"io"
"log/slog"
"sync"
)

func NewCustomLogger(writer io.Writer, level slog.Level, addSource bool) *CustomLogger {
return &CustomLogger{
Logger: slog.New(
slog.NewJSONHandler(
writer,
&slog.HandlerOptions{
AddSource: addSource,
Level: level,
},
),
),
ctxKeys: []fieldKey{},
}
}

type CustomLogger struct {
*slog.Logger

mu sync.RWMutex
ctxKeys []fieldKey
}

// ErrorfCtx logs an error message with fmt.SprintF()
func (l *CustomLogger) ErrorfCtx(ctx context.Context, err error, msg string, args ...any) {
l.With(convertToAttrs(l.fromCtx(ctx))...).With(slog.String("error", err.Error())).ErrorContext(ctx, fmt.Sprintf(msg, args...))
}

// InfofCtx logs an informational message with fmt.SprintF()
func (l *CustomLogger) InfofCtx(ctx context.Context, msg string, args ...any) {
l.With(convertToAttrs(l.fromCtx(ctx))...).InfoContext(ctx, fmt.Sprintf(msg, args...))
}

// DebugfCtx logs a debug message with fmt.SprintF()
func (l *CustomLogger) DebugfCtx(ctx context.Context, msg string, args ...any) {
l.With(convertToAttrs(l.fromCtx(ctx))...).DebugContext(ctx, fmt.Sprintf(msg, args...))
}

// WarnfCtx logs a debug message with fmt.SprintF()
func (l *CustomLogger) WarnfCtx(ctx context.Context, msg string, args ...any) {
l.With(convertToAttrs(l.fromCtx(ctx))...).WarnContext(ctx, fmt.Sprintf(msg, args...))
}

func (l *CustomLogger) AddKeysValuesToCtx(ctx context.Context, kv map[string]interface{}) context.Context {
l.mu.Lock()
defer l.mu.Unlock()

for k, v := range kv {
ctx = context.WithValue(ctx, fieldKey(k), v)
l.ctxKeys = append(l.ctxKeys, fieldKey(k))
}

return ctx
}

func (l *CustomLogger) fromCtx(ctx context.Context) fields {
l.mu.Lock()
defer l.mu.Unlock()

f := make(fields)
for _, ctxKey := range l.ctxKeys {
f[ctxKey] = ctx.Value(ctxKey)
}

return f
}
134 changes: 134 additions & 0 deletions clog/clog_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package clog

import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/stretchr/testify/require"
"log/slog"
"sync"
"testing"
)

const msgKey = "msg"

func TestCustomLogger(t *testing.T) {
var buf bytes.Buffer

logger := NewCustomLogger(&buf, slog.LevelDebug, true)

ctx := context.Background()
ctx = logger.AddKeysValuesToCtx(ctx, map[string]interface{}{"user": "testUser"})

tests := []struct {
name string
logFunc func(ctx context.Context, msg string, args ...any)
expected map[string]interface{}
errorInput error
}{
{
name: "ErrorfCtx",
logFunc: func(ctx context.Context, msg string, args ...any) {
logger.ErrorfCtx(ctx, fmt.Errorf("test error"), msg, args...)
},
expected: map[string]interface{}{"level": "ERROR", "user": "testUser", "error": "test error", msgKey: "an error occurred"},
errorInput: fmt.Errorf("test error"),
},
{
name: "InfofCtx",
logFunc: func(ctx context.Context, msg string, args ...any) {
logger.InfofCtx(ctx, msg, args...)
},
expected: map[string]interface{}{"level": "INFO", "user": "testUser", msgKey: "informational message"},
},
{
name: "DebugfCtx",
logFunc: func(ctx context.Context, msg string, args ...any) {
logger.DebugfCtx(ctx, msg, args...)
},
expected: map[string]interface{}{"level": "DEBUG", "user": "testUser", msgKey: "debugging message"},
},
{
name: "WarnfCtx",
logFunc: func(ctx context.Context, msg string, args ...any) {
logger.WarnfCtx(ctx, msg, args...)
},
expected: map[string]interface{}{"level": "WARN", "user": "testUser", msgKey: "warning message"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
buf.Reset()
tc.logFunc(ctx, tc.expected[msgKey].(string))

var actual map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &actual); err != nil {
t.Fatalf("Failed to unmarshal log output: %v", err)
}

for key, expectedValue := range tc.expected {
if actual[key] != expectedValue {
t.Errorf("%s did not log correctly. Expected %v for %s, got %v", tc.name, expectedValue, key, actual[key])
}
}
})
}
}

func TestCustomLogger_Level(t *testing.T) {
var buf bytes.Buffer

logger := NewCustomLogger(&buf, slog.LevelInfo, true)

ctx := context.Background()
ctx = logger.AddKeysValuesToCtx(ctx, map[string]interface{}{"user": "testUser"})

tests := []struct {
name string
logFunc func(ctx context.Context, msg string, args ...any)
expected map[string]interface{}
errorInput error
}{
{
name: "DebugfCtx",
logFunc: func(ctx context.Context, msg string, args ...any) {
logger.DebugfCtx(ctx, msg, args...)
},
expected: map[string]interface{}{"level": "DEBUG", "user": "testUser", msgKey: "debugging message"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
buf.Reset()
tc.logFunc(ctx, tc.expected[msgKey].(string))

var actual map[string]interface{}
require.Nil(t, actual)
})
}
}

func TestConvertToAttrsConcurrentAccess(t *testing.T) {
testFields := fields{
"user": "testUser",
"session": "xyz123",
"role": "admin",
}

var wg sync.WaitGroup

repeat := 100
wg.Add(repeat)

for i := 0; i < repeat; i++ {
go func() {
defer wg.Done()
_ = convertToAttrs(testFields)
}()
}

wg.Wait()
}
21 changes: 17 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,35 +1,48 @@
module github.com/gateway-fm/scriptorium

go 1.20
go 1.22

require (
github.com/go-pg/pg/v10 v10.12.0
github.com/gofrs/uuid v4.2.0+incompatible
github.com/joho/godotenv v1.5.1
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.7.1
github.com/valyala/fasthttp v1.44.0
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/text v0.3.7
golang.org/x/crypto v0.21.0
golang.org/x/text v0.14.0
)

require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/bufpool v0.1.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/sys v0.18.0 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
mellium.im/sasl v0.3.1 // indirect
)
Loading
Loading