forked from ebhomengo/niki
				
			feat(niki): add logger
This commit is contained in:
		
							parent
							
								
									7bc597a09c
								
							
						
					
					
						commit
						370ca1da1f
					
				| 
						 | 
					@ -23,5 +23,4 @@ bin
 | 
				
			||||||
#env
 | 
					#env
 | 
				
			||||||
*.env
 | 
					*.env
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logs/
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
module git.gocasts.ir/ebhomengo/niki
 | 
					module git.gocasts.ir/ebhomengo/niki
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.21.3
 | 
					go 1.21.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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=
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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])
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue