feat(niki): add logger

This commit is contained in:
mohammad mahdi rezaei 2023-12-17 16:33:01 +03:30
parent 7bc597a09c
commit 370ca1da1f
5 changed files with 160 additions and 2 deletions

3
.gitignore vendored
View File

@ -23,5 +23,4 @@ bin
#env
*.env
logs/

2
go.mod
View File

@ -1,3 +1,5 @@
module git.gocasts.ir/ebhomengo/niki
go 1.21.3
require gopkg.in/natefinch/lumberjack.v2 v2.2.1

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=

58
logger/logger.go Normal file
View File

@ -0,0 +1,58 @@
package logger
import (
"io"
"log/slog"
"os"
"gopkg.in/natefinch/lumberjack.v2"
)
const (
defaultFilePath = "logs/logs.json"
defaultUseLocalTime = false
defaultFileMaxSizeInMB = 10
defaultFileAgeInDays = 30
)
type Config struct {
FilePath string
UseLocalTime bool
FileMaxSizeInMB int
FileMaxAgeInDays int
}
var l *slog.Logger
// init is default logger and Singleton that lets you ensure that a logger has only one instance, while providing a global access point to this instance.
func init() {
fileWriter := &lumberjack.Logger{
Filename: defaultFilePath,
LocalTime: defaultUseLocalTime,
MaxSize: defaultFileMaxSizeInMB,
MaxAge: defaultFileAgeInDays,
}
l = slog.New(
slog.NewJSONHandler(io.MultiWriter(fileWriter, os.Stdout), &slog.HandlerOptions{}),
)
}
func L() *slog.Logger {
return l
}
// New is constructor logger with special settings
func New(cfg Config, opt *slog.HandlerOptions) *slog.Logger {
fileWriter := &lumberjack.Logger{
Filename: cfg.FilePath,
LocalTime: cfg.UseLocalTime,
MaxSize: cfg.FileMaxSizeInMB,
MaxAge: cfg.FileMaxAgeInDays,
}
logger := slog.New(
slog.NewJSONHandler(io.MultiWriter(fileWriter, os.Stdout), opt),
)
return logger
}

97
logger/logger_test.go Normal file
View File

@ -0,0 +1,97 @@
package logger_test
import (
"bufio"
"fmt"
"log/slog"
"os"
"strconv"
"testing"
"git.gocasts.ir/ebhomengo/niki/logger"
)
func TestLogger(t *testing.T) {
cfg := logger.Config{
FilePath: "./logs.json",
UseLocalTime: false,
FileMaxSizeInMB: 10,
FileMaxAgeInDays: 1,
}
opt := slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
// remove time because it makes test wrong `time is so fast :)`
return slog.Attr{}
}
return a
},
}
l := logger.New(cfg, &opt)
tests := []struct {
f func()
want string
}{
{
f: func() {
l.Info("INFO", "key", "value")
},
want: `{"level":"INFO","msg":"INFO","key":"value"}`,
},
{
f: func() {
l.Warn("WARN", "key", "value")
},
want: `{"level":"WARN","msg":"WARN","key":"value"}`,
},
{
f: func() {
l.Error("ERROR", "key", "value")
},
want: `{"level":"ERROR","msg":"ERROR","key":"value"}`,
},
{
f: func() {
l.With(
slog.Group("user",
slog.String("id", "user-123"),
),
).
With("environment", "dev").
With("error", fmt.Errorf("an error")).
Error("A message")
},
want: `{"level":"ERROR","msg":"A message","user":{"id":"user-123"},"environment":"dev","error":"an error"}`,
},
}
// first run logs
for _, test := range tests {
test.f()
}
f, err := os.Open(cfg.FilePath)
defer f.Close()
defer os.Remove(cfg.FilePath)
if err != nil {
t.Fatalf("can't open file: %s", err)
}
scanner := bufio.NewScanner(f)
var logs []string
for scanner.Scan() {
logs = append(logs, scanner.Text())
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
if test.want != logs[i] {
t.Fatalf("want: %+v, got: %+v", test.want, logs[i])
}
})
}
}