224 lines
6 KiB
Go
224 lines
6 KiB
Go
// mauLogger - A logger for Go programs
|
|
// Copyright (c) 2016-2021 Tulir Asokan
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package maulogger
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// LoggerFileFormat ...
|
|
type LoggerFileFormat func(now string, i int) string
|
|
|
|
type BasicLogger struct {
|
|
PrintLevel int
|
|
FlushLineThreshold int
|
|
FileTimeFormat string
|
|
FileFormat LoggerFileFormat
|
|
TimeFormat string
|
|
FileMode os.FileMode
|
|
DefaultSub Logger
|
|
|
|
JSONFile bool
|
|
JSONStdout bool
|
|
|
|
stdoutEncoder *json.Encoder
|
|
fileEncoder *json.Encoder
|
|
|
|
writer *os.File
|
|
writerLock sync.Mutex
|
|
StdoutLock sync.Mutex
|
|
StderrLock sync.Mutex
|
|
lines int
|
|
|
|
metadata map[string]interface{}
|
|
}
|
|
|
|
// Logger contains advanced logging functions
|
|
type Logger interface {
|
|
Sub(module string) Logger
|
|
Subm(module string, metadata map[string]interface{}) Logger
|
|
WithDefaultLevel(level Level) Logger
|
|
GetParent() Logger
|
|
|
|
Writer(level Level) io.WriteCloser
|
|
|
|
Log(level Level, parts ...interface{})
|
|
Logln(level Level, parts ...interface{})
|
|
Logf(level Level, message string, args ...interface{})
|
|
Logfln(level Level, message string, args ...interface{})
|
|
|
|
Debug(parts ...interface{})
|
|
Debugln(parts ...interface{})
|
|
Debugf(message string, args ...interface{})
|
|
Debugfln(message string, args ...interface{})
|
|
Info(parts ...interface{})
|
|
Infoln(parts ...interface{})
|
|
Infof(message string, args ...interface{})
|
|
Infofln(message string, args ...interface{})
|
|
Warn(parts ...interface{})
|
|
Warnln(parts ...interface{})
|
|
Warnf(message string, args ...interface{})
|
|
Warnfln(message string, args ...interface{})
|
|
Error(parts ...interface{})
|
|
Errorln(parts ...interface{})
|
|
Errorf(message string, args ...interface{})
|
|
Errorfln(message string, args ...interface{})
|
|
Fatal(parts ...interface{})
|
|
Fatalln(parts ...interface{})
|
|
Fatalf(message string, args ...interface{})
|
|
Fatalfln(message string, args ...interface{})
|
|
}
|
|
|
|
// Create a Logger
|
|
func Createm(metadata map[string]interface{}) Logger {
|
|
var log = &BasicLogger{
|
|
PrintLevel: 10,
|
|
FileTimeFormat: "2006-01-02",
|
|
FileFormat: func(now string, i int) string { return fmt.Sprintf("%[1]s-%02[2]d.log", now, i) },
|
|
TimeFormat: "15:04:05 02.01.2006",
|
|
FileMode: 0600,
|
|
FlushLineThreshold: 5,
|
|
lines: 0,
|
|
metadata: metadata,
|
|
}
|
|
log.DefaultSub = log.Sub("")
|
|
return log
|
|
}
|
|
|
|
func Create() Logger {
|
|
return Createm(map[string]interface{}{})
|
|
}
|
|
|
|
func (log *BasicLogger) EnableJSONStdout() {
|
|
log.JSONStdout = true
|
|
log.stdoutEncoder = json.NewEncoder(os.Stdout)
|
|
}
|
|
|
|
func (log *BasicLogger) GetParent() Logger {
|
|
return nil
|
|
}
|
|
|
|
// SetWriter formats the given parts with fmt.Sprint and logs the result with the SetWriter level
|
|
func (log *BasicLogger) SetWriter(w *os.File) {
|
|
log.writer = w
|
|
if log.JSONFile {
|
|
log.fileEncoder = json.NewEncoder(w)
|
|
}
|
|
}
|
|
|
|
// OpenFile formats the given parts with fmt.Sprint and logs the result with the OpenFile level
|
|
func (log *BasicLogger) OpenFile() error {
|
|
now := time.Now().Format(log.FileTimeFormat)
|
|
i := 1
|
|
for ; ; i++ {
|
|
if _, err := os.Stat(log.FileFormat(now, i)); os.IsNotExist(err) {
|
|
break
|
|
}
|
|
}
|
|
writer, err := os.OpenFile(log.FileFormat(now, i), os.O_WRONLY|os.O_CREATE|os.O_APPEND, log.FileMode)
|
|
if err != nil {
|
|
return err
|
|
} else if writer == nil {
|
|
return os.ErrInvalid
|
|
}
|
|
log.SetWriter(writer)
|
|
return nil
|
|
}
|
|
|
|
// Close formats the given parts with fmt.Sprint and logs the result with the Close level
|
|
func (log *BasicLogger) Close() error {
|
|
if log.writer != nil {
|
|
return log.writer.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type logLine struct {
|
|
log *BasicLogger
|
|
|
|
Command string `json:"command"`
|
|
Time time.Time `json:"time"`
|
|
Level string `json:"level"`
|
|
Module string `json:"module"`
|
|
Message string `json:"message"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
func (ll logLine) String() string {
|
|
if len(ll.Module) == 0 {
|
|
return fmt.Sprintf("[%s] [%s] %s", ll.Time.Format(ll.log.TimeFormat), ll.Level, ll.Message)
|
|
} else {
|
|
return fmt.Sprintf("[%s] [%s/%s] %s", ll.Time.Format(ll.log.TimeFormat), ll.Module, ll.Level, ll.Message)
|
|
}
|
|
}
|
|
|
|
func reduceItem(m1, m2 map[string]interface{}) map[string]interface{} {
|
|
m3 := map[string]interface{}{}
|
|
|
|
_merge := func(m map[string]interface{}) {
|
|
for ia, va := range m {
|
|
m3[ia] = va
|
|
}
|
|
}
|
|
|
|
_merge(m1)
|
|
_merge(m2)
|
|
|
|
return m3
|
|
}
|
|
|
|
// Raw formats the given parts with fmt.Sprint and logs the result with the Raw level
|
|
func (log *BasicLogger) Raw(level Level, extraMetadata map[string]interface{}, module, origMessage string) {
|
|
message := logLine{log, "log", time.Now(), level.Name, module, strings.TrimSpace(origMessage), reduceItem(log.metadata, extraMetadata)}
|
|
|
|
if log.writer != nil {
|
|
log.writerLock.Lock()
|
|
var err error
|
|
if log.JSONFile {
|
|
err = log.fileEncoder.Encode(&message)
|
|
} else {
|
|
_, err = log.writer.WriteString(message.String())
|
|
_, _ = log.writer.WriteString("\n")
|
|
}
|
|
log.writerLock.Unlock()
|
|
if err != nil {
|
|
log.StderrLock.Lock()
|
|
_, _ = os.Stderr.WriteString("Failed to write to log file:")
|
|
_, _ = os.Stderr.WriteString(err.Error())
|
|
log.StderrLock.Unlock()
|
|
}
|
|
}
|
|
|
|
if level.Severity >= log.PrintLevel {
|
|
if log.JSONStdout {
|
|
log.StdoutLock.Lock()
|
|
_ = log.stdoutEncoder.Encode(&message)
|
|
log.StdoutLock.Unlock()
|
|
} else if level.Severity >= LevelError.Severity {
|
|
log.StderrLock.Lock()
|
|
_, _ = os.Stderr.WriteString(level.GetColor())
|
|
_, _ = os.Stderr.WriteString(message.String())
|
|
_, _ = os.Stderr.WriteString(level.GetReset())
|
|
_, _ = os.Stderr.WriteString("\n")
|
|
log.StderrLock.Unlock()
|
|
} else {
|
|
log.StdoutLock.Lock()
|
|
_, _ = os.Stdout.WriteString(level.GetColor())
|
|
_, _ = os.Stdout.WriteString(message.String())
|
|
_, _ = os.Stdout.WriteString(level.GetReset())
|
|
_, _ = os.Stdout.WriteString("\n")
|
|
log.StdoutLock.Unlock()
|
|
}
|
|
}
|
|
}
|