123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- package lgr
- import (
- "fmt"
- "io"
- "os"
- "runtime"
- "strings"
- "sync"
- "time"
- )
- var levels = []string{"DEBUG", "INFO", "WARN", "ERROR", "PANIC", "FATAL"}
- // Logger provided simple logger with basic support of levels. Thread safe
- type Logger struct {
- stdout, stderr io.Writer
- dbg bool
- lock sync.Mutex
- callerFile, callerFunc bool
- now nowFn
- fatal panicFn
- skipCallers int
- levelBraces bool
- }
- type nowFn func() time.Time
- type panicFn func()
- // New makes new leveled logger. Accepts dbg flag turing on info about the caller and allowing DEBUG messages/
- // Two writers can be passed optionally - first for out and second for err
- func New(options ...Option) *Logger {
- res := Logger{
- now: time.Now,
- fatal: func() { os.Exit(1) },
- stdout: os.Stdout,
- stderr: os.Stderr,
- skipCallers: 1,
- }
- for _, opt := range options {
- opt(&res)
- }
- return &res
- }
- // Logf implements L interface to output with printf style.
- // Each line prefixed with ts, level and optionally (dbg mode only) by caller info.
- // ERROR and FATAL also send the same line to err writer.
- // FATAL adds runtime stack and os.exit(1), like panic.
- func (l *Logger) Logf(format string, args ...interface{}) {
- lv, msg := l.extractLevel(fmt.Sprintf(format, args...))
- var bld strings.Builder
- bld.WriteString(l.now().Format("2006/01/02 15:04:05.000 "))
- bld.WriteString(lv)
- if l.dbg && (l.callerFile || l.callerFunc) {
- if pc, file, line, ok := runtime.Caller(l.skipCallers); ok {
- funcName := ""
- if l.callerFunc {
- funcNameElems := strings.Split(runtime.FuncForPC(pc).Name(), "/")
- funcName = funcNameElems[len(funcNameElems)-1]
- }
- fileInfo := ""
- if l.callerFile {
- fnameElems := strings.Split(file, "/")
- fileInfo = fmt.Sprintf("%s:%d", strings.Join(fnameElems[len(fnameElems)-2:], "/"), line)
- if l.callerFunc {
- fileInfo += " "
- }
- }
- srcFileInfo := fmt.Sprintf("{%s%s} ", fileInfo, funcName)
- bld.WriteString(srcFileInfo)
- }
- }
- if lv == "DEBUG " && !l.dbg {
- return
- }
- bld.WriteString(msg) //nolint
- bld.WriteString("\n") //nolint
- l.lock.Lock()
- msgb := []byte(bld.String())
- l.stdout.Write(msgb) //nolint
- switch lv {
- case "PANIC ", "FATAL ":
- l.stderr.Write(msgb) //nolint
- l.stderr.Write(getDump()) //nolint
- l.fatal()
- case "ERROR ":
- l.stderr.Write(msgb) //nolint
- }
- l.lock.Unlock()
- }
- func (l *Logger) extractLevel(line string) (level, msg string) {
- brace := func(b string) string {
- if l.levelBraces {
- return b
- }
- return ""
- }
- spaces := " "
- for _, lv := range levels {
- if strings.HasPrefix(line, lv) {
- if len(lv) == 4 {
- spaces = " "
- }
- return brace("[") + lv + brace("]") + spaces, line[len(lv)+1:]
- }
- if strings.HasPrefix(line, "["+lv+"]") {
- if len(lv) == 4 {
- spaces = " "
- }
- return brace("[") + lv + brace("]") + spaces, line[len(lv)+3:]
- }
- }
- return "", line
- }
- // getDump reads runtime stack and returns as a string
- func getDump() []byte {
- maxSize := 5 * 1024 * 1024
- stacktrace := make([]byte, maxSize)
- length := runtime.Stack(stacktrace, true)
- if length > maxSize {
- length = maxSize
- }
- return stacktrace[:length]
- }
- // Option func type
- type Option func(l *Logger)
- // Out sets out writer
- func Out(w io.Writer) Option {
- return func(l *Logger) {
- l.stdout = w
- }
- }
- // Err sets error writer
- func Err(w io.Writer) Option {
- return func(l *Logger) {
- l.stderr = w
- }
- }
- // Debug turn on dbg mode
- func Debug(l *Logger) {
- l.dbg = true
- }
- // CallerFile adds caller info with file, and line number
- func CallerFile(l *Logger) {
- l.callerFile = true
- }
- // CallerFunc adds caller info with function name
- func CallerFunc(l *Logger) {
- l.callerFunc = true
- }
- // LevelBraces adds [] to level
- func LevelBraces(l *Logger) {
- l.levelBraces = true
- }
|