diff --git a/.gitignore b/.gitignore index 4d72fe9..54f6f8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ -test/ \ No newline at end of file +test/ +*.log \ No newline at end of file diff --git a/README.md b/README.md index 8be554a..4906a5e 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ # ScuroLogger (slog) + diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 0000000..99a03c6 --- /dev/null +++ b/examples/main.go @@ -0,0 +1,21 @@ +package main + +import "git.nix13.pw/scuroneko/slog" + +func main() { + logger := slog.CreateLogger().JsonPretty(false) + defer logger.Close() + logger.AddWriter(logger.CreateTextStdoutWriter()) + f, err := logger.CreateJsonFileWriter("main.log") + if err != nil { + panic(err) + } + logger.AddWriter(f) + logger.Infoln("Test") + logger.Warnln("Test") + logger.Errorln("Test") + logger.Debugln("Test") // No output + logger.Level(slog.DEBUG) + logger.Debugln("Test") // Now we have output + logger.Fatalln("Test") // Exit code 1 +} diff --git a/go.sum b/go.sum index cfcbdff..213eb05 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,9 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= diff --git a/io.go b/io.go new file mode 100644 index 0000000..3dbaaf5 --- /dev/null +++ b/io.go @@ -0,0 +1,91 @@ +package slog + +import ( + "fmt" + "os" +) + +func (l *Logger) Infof(format string, args ...any) { + l.print(INFO, fmt.Sprintf(format, args...)) +} +func (l *Logger) Info(m ...any) { + l.print(INFO, m...) +} +func (l *Logger) Infoln(m ...any) { + l.println(INFO, m...) +} + +func (l *Logger) Warnf(format string, args ...any) { + l.print(WARN, fmt.Sprintf(format, args...)) +} +func (l *Logger) Warn(m ...any) { + l.print(WARN, m...) +} +func (l *Logger) Warnln(m ...any) { + l.println(WARN, m...) +} + +func (l *Logger) Errorf(format string, args ...any) { + l.print(ERROR, fmt.Sprintf(format, args...)) +} +func (l *Logger) Error(m ...any) { + l.print(ERROR, m...) +} +func (l *Logger) Errorln(m ...any) { + l.println(ERROR, m...) +} + +func (l *Logger) Fatalf(format string, args ...any) { + l.print(FATAL, fmt.Sprintf(format, args...)) + os.Exit(1) +} +func (l *Logger) Fatal(m ...any) { + l.print(FATAL, m...) + os.Exit(1) +} +func (l *Logger) Fatalln(m ...any) { + l.println(FATAL, m...) + os.Exit(1) +} + +func (l *Logger) Debugf(format string, args ...any) { + l.print(DEBUG, fmt.Sprintf(format, args...)) +} +func (l *Logger) Debug(m ...any) { + l.print(DEBUG, m...) +} +func (l *Logger) Debugln(m ...any) { + l.println(DEBUG, m...) +} + +// Write message without trailing "\n" +// Good for database +func (l *Logger) print(level LogLevel, m ...any) { + if l.level.n < level.n { + return + } + + tb := getFullTraceback(0) + for _, writer := range l.writers { + err := writer.Print(level, l.prefix, tb, m...) + if err != nil { + l.Error(err) + } + } +} + +// Docker requires "\n" at end to write to log. +// print not work for docker, otherwise it will work and write into stdout +func (l *Logger) println(level LogLevel, m ...any) { + if l.level.n < level.n { + return + } + + tb := getFullTraceback(0) + for _, writer := range l.writers { + err := writer.Print(level, l.prefix, tb, append(m, "\n")...) + if err != nil { + l.Error(err) + } + } +} diff --git a/logger.go b/logger.go index b4ae674..4c589dc 100644 --- a/logger.go +++ b/logger.go @@ -1,105 +1,22 @@ -package laniakea +package slog import ( - "bufio" - "encoding/json" "fmt" "io" - "os" - "path/filepath" - "runtime" - "sort" "strings" "time" "github.com/fatih/color" ) -type LoggerWriter interface { - Close() error - Write(p []byte) (n int, err error) - Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error -} -type LoggerTextWriter struct { - LoggerWriter - writer io.Writer - printTraceback bool - printTime bool -} - -func (w *LoggerTextWriter) Write(p []byte) (n int, err error) { - n, err = w.writer.Write(p) - if err != nil { - return n, err - } - err = bufio.NewWriter(w.writer).Flush() - return n, err -} -func (w *LoggerTextWriter) Print(level LogLevel, prefix string, tb []*MethodTraceback, messages ...any) error { - s := buildString(level, prefix, true, w.printTraceback, w.printTime, messages...) - _, err := w.Write([]byte(s)) - return err -} -func (w *LoggerTextWriter) Close() error { - return w.writer.(io.Closer).Close() -} - -type LoggerJsonWriter struct { - LoggerWriter - writer io.Writer - pretty bool -} -type LoggerJsonMessage struct { - Time time.Time `json:"time"` - Level string `json:"level"` - Prefix string `json:"prefix"` - Message string `json:"message"` - Traceback []*MethodTraceback `json:"traceback"` -} - -func (w *LoggerJsonWriter) Write(data []byte) (int, error) { - n, err := w.writer.Write(data) - if err != nil { - return n, err - } - err = bufio.NewWriter(w.writer).Flush() - return n, err -} -func (w *LoggerJsonWriter) Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error { - msg := Map(messages, func(el any) string { - return fmt.Sprintf("%v", el) - }) - m := LoggerJsonMessage{ - Time: time.Now(), - Level: level.GetName(), - Prefix: prefix, - Message: strings.Join(msg, " "), - Traceback: traceback, - } - var data []byte - var err error - if w.pretty { - data, err = json.MarshalIndent(m, "", " ") - } else { - data, err = json.Marshal(m) - } - if err != nil { - return err - } - _, err = w.Write(append(data, []byte("\n")...)) - return err -} -func (w *LoggerJsonWriter) Close() error { - return w.writer.(io.Closer).Close() -} - type Logger struct { - prefix string - level LogLevel + prefix string + level LogLevel + writers []LoggerWriter + printTraceback bool printTime bool jsonPretty bool - writers []LoggerWriter } type LogLevel struct { @@ -113,12 +30,11 @@ func (l *LogLevel) GetName() string { } type MethodTraceback struct { - Package string `json:"package"` Method string `json:"method"` - fullPath string - signature string Filename string `json:"filename"` Line int `json:"line"` + Signature string `json:"signature"` + FullPath string `json:"fullPath"` } var ( @@ -138,60 +54,6 @@ func CreateLogger() *Logger { } } -func (l *Logger) CreateTextFileWriter(path string) (*LoggerTextWriter, error) { - err := os.MkdirAll(filepath.Dir(path), os.ModePerm) - if err != nil { - return nil, err - } - file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return nil, err - } - - writer := &LoggerTextWriter{ - writer: file, printTraceback: l.printTraceback, printTime: l.printTime, - } - return writer, nil -} -func (l *Logger) CreateTextStdoutWriter() *LoggerTextWriter { - writer := &LoggerTextWriter{ - writer: os.Stdout, printTraceback: l.printTraceback, printTime: l.printTime, - } - return writer -} -func (l *Logger) CreateJsonStdoutWriter() *LoggerJsonWriter { - writer := &LoggerJsonWriter{ - writer: os.Stdout, pretty: l.jsonPretty, - } - return writer -} -func (l *Logger) CreateJsonFileWriter(path string) (*LoggerJsonWriter, error) { - err := os.MkdirAll(filepath.Dir(path), os.ModePerm) - if err != nil { - return nil, err - } - file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return nil, err - } - writer := &LoggerJsonWriter{ - writer: file, pretty: l.jsonPretty, - } - return writer, nil -} -func (l *Logger) CreateTextWriter(w io.Writer) *LoggerTextWriter { - writer := &LoggerTextWriter{ - writer: w, printTraceback: l.printTraceback, printTime: l.printTime, - } - return writer -} -func (l *Logger) CreateJsonWriter(w io.Writer) *LoggerJsonWriter { - writer := &LoggerJsonWriter{ - writer: w, pretty: l.jsonPretty, - } - return writer -} - func (l *Logger) Prefix(prefix string) *Logger { l.prefix = prefix return l @@ -220,153 +82,65 @@ func (l *Logger) AddWriter(writer LoggerWriter) *Logger { l.writers = append(l.writers, writer) return l } - -func (l *Logger) Infof(format string, args ...any) { - l.print(INFO, fmt.Sprintf(format, args...)) -} -func (l *Logger) Info(m ...any) { - l.println(INFO, m...) +func (l *Logger) Close() error { + for _, writer := range l.writers { + err := writer.Close() + if err != nil { + l.Error(err) + } + } + return nil } -func (l *Logger) Warnf(format string, args ...any) { - l.print(WARN, fmt.Sprintf(format, args...)) +func (l *Logger) CreateTextWriter(w io.Writer) *LoggerTextWriter { + return CreateTextWriter(w, l.printTraceback, l.printTime) } -func (l *Logger) Warn(m ...any) { - l.println(WARN, m...) +func (l *Logger) CreateTextStdoutWriter() *LoggerTextWriter { + return CreateTextStdoutWriter(l.printTraceback, l.printTime) +} +func (l *Logger) CreateTextFileWriter(filename string) (*LoggerTextWriter, error) { + return CreateTextFileWriter(filename, l.printTraceback, l.printTime) +} +func (l *Logger) CreateJsonWriter(w io.Writer) *LoggerJsonWriter { + return CreateJsonWriter(w, l.jsonPretty) +} +func (l *Logger) CreateJsonStdoutWriter() *LoggerJsonWriter { + return CreateJsonStdoutWriter(l.jsonPretty) +} +func (l *Logger) CreateJsonFileWriter(filename string) (*LoggerJsonWriter, error) { + return CreateJsonFileWriter(filename, l.jsonPretty) } -func (l *Logger) Error(m ...any) { - l.println(ERROR, m...) -} - -func (l *Logger) Fatal(m ...any) { - l.println(FATAL, m...) - os.Exit(1) -} - -func (l *Logger) Debug(m ...any) { - l.println(DEBUG, m...) -} - -func formatTime(t time.Time) string { +func FormatTime(t time.Time) string { return fmt.Sprintf("%02d.%02d.%02d %02d:%02d:%02d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second()) } -func formatTraceback(mt *MethodTraceback) string { +func FormatTraceback(mt *MethodTraceback) string { return fmt.Sprintf("%s:%s:%d", mt.Filename, mt.Method, mt.Line) } func FormatFullTraceback(tracebacks []*MethodTraceback) string { formatted := make([]string, 0) for _, tb := range tracebacks { - formatted = append(formatted, formatTraceback(tb)) + formatted = append(formatted, FormatTraceback(tb)) } return strings.Join(formatted, "->") } - -func getTraceback() *MethodTraceback { - caller, _, _, _ := runtime.Caller(4) - details := runtime.FuncForPC(caller) - signature := details.Name() - path, line := details.FileLine(caller) - splitPath := strings.Split(path, "/") - - splitSignature := strings.Split(signature, ".") - pkg, method := splitSignature[0], splitSignature[len(splitSignature)-1] - - tb := &MethodTraceback{ - Filename: splitPath[len(splitPath)-1], - fullPath: path, - Line: line, - signature: signature, - Package: pkg, - Method: method, - } - - return tb -} -func getFullTraceback(skip int) []*MethodTraceback { - pc := make([]uintptr, 15) - runtime.Callers(skip, pc) - list := make([]*MethodTraceback, 0) - frames := runtime.CallersFrames(pc) - for { - frame, more := frames.Next() - if !more { - break - } - details := runtime.FuncForPC(frame.PC) - signature := details.Name() - path, line := details.FileLine(frame.PC) - splitPath := strings.Split(path, "/") - - splitSignature := strings.Split(signature, ".") - pkg, method := splitSignature[0], splitSignature[len(splitSignature)-1] - - tb := &MethodTraceback{ - Filename: splitPath[len(splitPath)-1], - fullPath: path, - Line: line, - signature: signature, - Package: pkg, - Method: method, - } - list = append(list, tb) - } - sort.Slice(list, func(i, j int) bool { - return j < i - }) - return list -} - -func buildString(level LogLevel, prefix string, newline, printTime, printTraceback bool, m ...any) string { +func BuildString(level LogLevel, prefix string, printTime, printTraceback bool, m ...any) string { args := []string{ fmt.Sprintf("[%s]", prefix), fmt.Sprintf("[%s]", strings.ToUpper(level.t)), } if printTraceback { - args = append(args, fmt.Sprintf("[%s]", formatTraceback(getTraceback()))) + args = append(args, fmt.Sprintf("[%s]", FormatTraceback(getTraceback()))) } if printTime { - args = append(args, fmt.Sprintf("[%s]", formatTime(time.Now()))) + args = append(args, fmt.Sprintf("[%s]", FormatTime(time.Now()))) } msg := Map(m, func(el any) string { return fmt.Sprintf("%v", el) }) s := fmt.Sprintf("%s %s", strings.Join(args, " "), strings.Join(msg, " ")) - if newline { - s += "\n" - } return s } - -func (l *Logger) print(level LogLevel, m ...any) { - if l.level.n < level.n { - return - } - - tb := getFullTraceback(0) - for _, writer := range l.writers { - err := writer.Print(level, l.prefix, tb, m...) - if err != nil { - l.Error(err) - } - } -} - -// Docker requires "\n" at end to write to log. -// print not work for docker, otherwise it will work and write into stdout -func (l *Logger) println(level LogLevel, m ...any) { - if l.level.n < level.n { - return - } - - tb := getFullTraceback(0) - for _, writer := range l.writers { - err := writer.Print(level, l.prefix, tb, m...) - if err != nil { - l.Error(err) - } - } -} diff --git a/traceback.go b/traceback.go new file mode 100644 index 0000000..699345a --- /dev/null +++ b/traceback.go @@ -0,0 +1,60 @@ +package slog + +import ( + "runtime" + "sort" + "strings" +) + +func getTraceback() *MethodTraceback { + caller, _, _, _ := runtime.Caller(5) + details := runtime.FuncForPC(caller) + signature := details.Name() + path, line := details.FileLine(caller) + splitPath := strings.Split(path, "/") + + splitSignature := strings.Split(signature, ".") + method := splitSignature[len(splitSignature)-1] + + tb := &MethodTraceback{ + Filename: splitPath[len(splitPath)-1], + FullPath: path, + Line: line, + Signature: signature, + Method: method, + } + + return tb +} +func getFullTraceback(skip int) []*MethodTraceback { + pc := make([]uintptr, 15) + runtime.Callers(skip, pc) + list := make([]*MethodTraceback, 0) + frames := runtime.CallersFrames(pc) + for { + frame, more := frames.Next() + if !more { + break + } + details := runtime.FuncForPC(frame.PC) + signature := details.Name() + path, line := details.FileLine(frame.PC) + splitPath := strings.Split(path, "/") + + splitSignature := strings.Split(signature, ".") + method := splitSignature[len(splitSignature)-1] + + tb := &MethodTraceback{ + Filename: splitPath[len(splitPath)-1], + FullPath: path, + Line: line, + Signature: signature, + Method: method, + } + list = append(list, tb) + } + sort.Slice(list, func(i, j int) bool { + return j < i + }) + return list +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..2235817 --- /dev/null +++ b/utils.go @@ -0,0 +1,9 @@ +package slog + +func Map[T, R any](s []T, f func(T) R) []R { + out := make([]R, len(s)) + for i, el := range s { + out[i] = f(el) + } + return out +} diff --git a/writers.go b/writers.go new file mode 100644 index 0000000..0c8f9df --- /dev/null +++ b/writers.go @@ -0,0 +1,134 @@ +package slog + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" +) + +type LoggerWriter interface { + Close() error + Write(p []byte) (n int, err error) + Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error +} + +type LoggerTextWriter struct { + LoggerWriter + writer io.Writer + printTraceback bool + printTime bool +} + +func (w *LoggerTextWriter) Write(p []byte) (n int, err error) { + n, err = w.writer.Write(p) + if err != nil { + return n, err + } + err = bufio.NewWriter(w.writer).Flush() + return n, err +} +func (w *LoggerTextWriter) Print(level LogLevel, prefix string, _ []*MethodTraceback, messages ...any) error { + s := BuildString(level, prefix, w.printTime, w.printTraceback, messages...) + _, err := w.Write([]byte(s)) + return err +} +func (w *LoggerTextWriter) Close() error { + return w.writer.(io.Closer).Close() +} + +// LoggerJsonWriter write into writer JSON +type LoggerJsonWriter struct { + LoggerWriter + writer io.Writer + pretty bool +} +type LoggerJsonMessage struct { + Time time.Time `json:"time"` + Level string `json:"level"` + Prefix string `json:"prefix"` + Message string `json:"message"` + Traceback []*MethodTraceback `json:"traceback"` +} + +func (w *LoggerJsonWriter) Write(data []byte) (int, error) { + n, err := w.writer.Write(data) + if err != nil { + return n, err + } + err = bufio.NewWriter(w.writer).Flush() + return n, err +} +func (w *LoggerJsonWriter) Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error { + msg := Map(messages, func(el any) string { + return fmt.Sprintf("%v", el) + }) + m := LoggerJsonMessage{ + Time: time.Now(), + Level: level.GetName(), + Prefix: prefix, + Message: strings.Join(msg, " "), + Traceback: traceback, + } + var data []byte + var err error + if w.pretty { + data, err = json.MarshalIndent(m, "", " ") + } else { + data, err = json.Marshal(m) + } + if err != nil { + return err + } + _, err = w.Write(append(data, []byte("\n")...)) + return err +} +func (w *LoggerJsonWriter) Close() error { + return w.writer.(io.Closer).Close() +} + +func CreateTextWriter(w io.Writer, printTraceback, printTime bool) *LoggerTextWriter { + writer := &LoggerTextWriter{ + writer: w, printTraceback: printTraceback, printTime: printTime, + } + return writer +} +func CreateTextFileWriter(path string, printTraceback, printTime bool) (*LoggerTextWriter, error) { + err := os.MkdirAll(filepath.Dir(path), os.ModePerm) + if err != nil { + return nil, err + } + file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + return CreateTextWriter(file, printTraceback, printTime), nil +} +func CreateTextStdoutWriter(printTraceback, printTime bool) *LoggerTextWriter { + return CreateTextWriter(os.Stdout, printTraceback, printTime) +} + +func CreateJsonWriter(w io.Writer, pretty bool) *LoggerJsonWriter { + writer := &LoggerJsonWriter{ + writer: w, pretty: pretty, + } + return writer +} +func CreateJsonStdoutWriter(pretty bool) *LoggerJsonWriter { + return CreateJsonWriter(os.Stdout, pretty) +} +func CreateJsonFileWriter(path string, pretty bool) (*LoggerJsonWriter, error) { + err := os.MkdirAll(filepath.Dir(path), os.ModePerm) + if err != nil { + return nil, err + } + file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + return CreateJsonWriter(file, pretty), nil +}