mirror of
https://github.com/eduardolat/pgbackweb.git
synced 2026-05-23 05:19:08 -05:00
Add logger package with key-value logging functionality
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Logger
|
||||
|
||||
This package includes a custom logger for the project.
|
||||
|
||||
Functions are exposed for logging messages easily; it is imperative that
|
||||
these functions are used throughout the project to maintain a standard in
|
||||
log messages.
|
||||
|
||||
If necessary, the `logWriter` can be edited so that in addition to writing to
|
||||
stdout, it can also write to a file or send the logs to a server like
|
||||
Better Stack or New Relic.
|
||||
@@ -0,0 +1,39 @@
|
||||
package logger
|
||||
|
||||
import "github.com/eduardolat/pgbackweb/internal/util/maputil"
|
||||
|
||||
// KV is a record of key-value pair to be logged
|
||||
type KV map[string]any
|
||||
|
||||
// kvToArgs converts a slice of KV to a slice of any
|
||||
func kvToArgs(kv ...KV) []any {
|
||||
pickedKv := KV{}
|
||||
if len(kv) > 0 {
|
||||
pickedKv = kv[0]
|
||||
}
|
||||
|
||||
sortedKeys := maputil.GetSortedStringKeys(pickedKv)
|
||||
args := make([]any, 0, len(sortedKeys)*2)
|
||||
|
||||
for _, k := range sortedKeys {
|
||||
args = append(args, k, pickedKv[k])
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// kvToArgsNs converts a slice of KV to a slice of any
|
||||
// and adds a namespace to the resulting slice
|
||||
func kvToArgsNs(ns string, kv ...KV) []any {
|
||||
pickedKv := KV{}
|
||||
if len(kv) > 0 {
|
||||
pickedKv = kv[0]
|
||||
}
|
||||
|
||||
sortedKeys := maputil.GetSortedStringKeys(pickedKv)
|
||||
args := make([]any, 0, len(sortedKeys)*2+2)
|
||||
args = append(args, "ns", ns)
|
||||
for _, k := range sortedKeys {
|
||||
args = append(args, k, pickedKv[k])
|
||||
}
|
||||
return args
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKvToArgsNoArgs(t *testing.T) {
|
||||
result := kvToArgs()
|
||||
assert.Equal(t, []any{}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsOneArg(t *testing.T) {
|
||||
kv := KV{"key": "value"}
|
||||
result := kvToArgs(kv)
|
||||
assert.Equal(t, []any{"key", "value"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsMultipleArgs(t *testing.T) {
|
||||
kv1 := KV{"key1": "value1", "key2": "value2"}
|
||||
kv2 := KV{"key3": "value3"}
|
||||
result := kvToArgs(kv1, kv2)
|
||||
assert.Equal(t, []any{"key1", "value1", "key2", "value2"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsNsNoArgs(t *testing.T) {
|
||||
result := kvToArgsNs("namespace")
|
||||
assert.Equal(t, []any{"ns", "namespace"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsNsOneArg(t *testing.T) {
|
||||
kv := KV{"key": "value"}
|
||||
result := kvToArgsNs("namespace", kv)
|
||||
assert.Equal(t, []any{"ns", "namespace", "key", "value"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsNsMultipleArgs(t *testing.T) {
|
||||
kv1 := KV{"key1": "value1", "key2": "value2"}
|
||||
kv2 := KV{"key3": "value3"}
|
||||
result := kvToArgsNs("namespace", kv1, kv2)
|
||||
assert.Equal(t, []any{"ns", "namespace", "key1", "value1", "key2", "value2"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsPickOnlyFirst(t *testing.T) {
|
||||
kv1 := KV{"key1": "value1"}
|
||||
kv2 := KV{"key2": "value2"}
|
||||
result := kvToArgs(kv1, kv2)
|
||||
assert.Equal(t, []any{"key1", "value1"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsNsPickOnlyFirst(t *testing.T) {
|
||||
kv1 := KV{"key1": "value1"}
|
||||
kv2 := KV{"key2": "value2"}
|
||||
result := kvToArgsNs("namespace", kv1, kv2)
|
||||
assert.Equal(t, []any{"ns", "namespace", "key1", "value1"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsOrder(t *testing.T) {
|
||||
kv := KV{"z": "value1", "a": "value2"}
|
||||
result := kvToArgs(kv)
|
||||
assert.Equal(t, []any{"a", "value2", "z", "value1"}, result)
|
||||
}
|
||||
|
||||
func TestKvToArgsNsOrder(t *testing.T) {
|
||||
kv := KV{"z": "value1", "a": "value2"}
|
||||
result := kvToArgsNs("namespace", kv)
|
||||
assert.Equal(t, []any{"ns", "namespace", "a", "value2", "z", "value1"}, result)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var logger *slog.Logger
|
||||
var loggerOnce sync.Once
|
||||
|
||||
// getLogger returns a logger singleton configured to log to
|
||||
// custom writer in JSON format
|
||||
func getLogger() *slog.Logger {
|
||||
loggerOnce.Do(func() {
|
||||
w := &logWriter{}
|
||||
logger = slog.New(slog.NewJSONHandler(
|
||||
w, nil,
|
||||
))
|
||||
})
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// Debug logs a debug message
|
||||
func Debug(msg string, args ...KV) {
|
||||
getLogger().Debug(msg, kvToArgs(args...)...)
|
||||
}
|
||||
|
||||
// DebugNs logs a debug message with a namespace
|
||||
// It's for grouping logs consistently
|
||||
func DebugNs(ns string, msg string, args ...KV) {
|
||||
getLogger().Debug(msg, kvToArgsNs(ns, args...)...)
|
||||
}
|
||||
|
||||
// Info logs a info message
|
||||
func Info(msg string, args ...KV) {
|
||||
getLogger().Info(msg, kvToArgs(args...)...)
|
||||
}
|
||||
|
||||
// InfoNs logs a info message with a namespace
|
||||
// It's for grouping logs consistently
|
||||
func InfoNs(ns string, msg string, args ...KV) {
|
||||
getLogger().Info(msg, kvToArgsNs(ns, args...)...)
|
||||
}
|
||||
|
||||
// Warn logs a warn message
|
||||
func Warn(msg string, args ...KV) {
|
||||
getLogger().Warn(msg, kvToArgs(args...)...)
|
||||
}
|
||||
|
||||
// WarnNs logs a warn message with a namespace
|
||||
// It's for grouping logs consistently
|
||||
func WarnNs(ns string, msg string, args ...KV) {
|
||||
getLogger().Warn(msg, kvToArgsNs(ns, args...)...)
|
||||
}
|
||||
|
||||
// Error logs a error message
|
||||
func Error(msg string, args ...KV) {
|
||||
getLogger().Error(msg, kvToArgs(args...)...)
|
||||
}
|
||||
|
||||
// ErrorNs logs a error message with a namespace
|
||||
// It's for grouping logs consistently
|
||||
func ErrorNs(ns string, msg string, args ...KV) {
|
||||
getLogger().Error(msg, kvToArgsNs(ns, args...)...)
|
||||
}
|
||||
|
||||
// FatalError is equivalent to Error() followed by a call to os.Exit(1)
|
||||
func FatalError(msg string, args ...KV) {
|
||||
Error(msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// FatalErrorNs is equivalent to FatalError() with a namespace
|
||||
func FatalErrorNs(ns string, msg string, args ...KV) {
|
||||
ErrorNs(ns, msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package logger
|
||||
|
||||
import "os"
|
||||
|
||||
// logWriter is a simple io.Writer that can redirect logs to os.Stdout
|
||||
// or any other required place
|
||||
type logWriter struct{}
|
||||
|
||||
// Write writes the log message to os.Stdout or any other required place
|
||||
func (w *logWriter) Write(p []byte) (n int, err error) {
|
||||
return os.Stdout.Write(p)
|
||||
}
|
||||
Reference in New Issue
Block a user