mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-18 14:47:03 +00:00
Currently there is no symbol for Fatal-level log entries, so they appear with "-2", which is distracting. This patch makes the Fatal log entries have their own symbol, ☠.
252 lines
4.9 KiB
Go
252 lines
4.9 KiB
Go
// Package log implements a simple logger.
|
|
//
|
|
// It implements an API somewhat similar to "github.com/google/glog" with a
|
|
// focus towards logging to stderr, which is useful for systemd-based
|
|
// environments.
|
|
//
|
|
// There are command line flags (defined using the flag package) to control
|
|
// the behaviour of the default logger. By default, it will write to stderr
|
|
// without timestamps; this is suitable for systemd (or equivalent) logging.
|
|
package log
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log/syslog"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Flags that control the default logging.
|
|
var (
|
|
vLevel = flag.Int("v", 0, "Verbosity level (1 = debug)")
|
|
|
|
logFile = flag.String("logfile", "",
|
|
"file to log to (enables logtime)")
|
|
|
|
logToSyslog = flag.String("logtosyslog", "",
|
|
"log to syslog, with the given tag")
|
|
|
|
logTime = flag.Bool("logtime", false,
|
|
"include the time when writing the log to stderr")
|
|
|
|
alsoLogToStderr = flag.Bool("alsologtostderr", false,
|
|
"also log to stderr, in addition to the file")
|
|
)
|
|
|
|
// Logging levels.
|
|
type Level int
|
|
|
|
const (
|
|
Fatal = Level(-2)
|
|
Error = Level(-1)
|
|
Info = Level(0)
|
|
Debug = Level(1)
|
|
)
|
|
|
|
var levelToLetter = map[Level]string{
|
|
Fatal: "☠",
|
|
Error: "E",
|
|
Info: "_",
|
|
Debug: ".",
|
|
}
|
|
|
|
// A Logger represents a logging object that writes logs to the given writer.
|
|
type Logger struct {
|
|
Level Level
|
|
LogTime bool
|
|
|
|
CallerSkip int
|
|
|
|
w io.WriteCloser
|
|
sync.Mutex
|
|
}
|
|
|
|
func New(w io.WriteCloser) *Logger {
|
|
return &Logger{
|
|
w: w,
|
|
CallerSkip: 0,
|
|
Level: Info,
|
|
LogTime: true,
|
|
}
|
|
}
|
|
|
|
func NewFile(path string) (*Logger, error) {
|
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
l := New(f)
|
|
l.LogTime = true
|
|
return l, nil
|
|
}
|
|
|
|
func NewSyslog(priority syslog.Priority, tag string) (*Logger, error) {
|
|
w, err := syslog.New(priority, tag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
l := New(w)
|
|
l.LogTime = false
|
|
return l, nil
|
|
}
|
|
|
|
func (l *Logger) Close() {
|
|
l.w.Close()
|
|
}
|
|
|
|
func (l *Logger) V(level Level) bool {
|
|
return level <= l.Level
|
|
}
|
|
|
|
func (l *Logger) Log(level Level, skip int, format string, a ...interface{}) {
|
|
if !l.V(level) {
|
|
return
|
|
}
|
|
|
|
// Message.
|
|
msg := fmt.Sprintf(format, a...)
|
|
|
|
// Caller.
|
|
_, file, line, ok := runtime.Caller(1 + l.CallerSkip + skip)
|
|
if !ok {
|
|
file = "unknown"
|
|
}
|
|
fl := fmt.Sprintf("%s:%-4d", filepath.Base(file), line)
|
|
if len(fl) > 18 {
|
|
fl = fl[len(fl)-18:]
|
|
}
|
|
msg = fmt.Sprintf("%-18s", fl) + " " + msg
|
|
|
|
// Level.
|
|
letter, ok := levelToLetter[level]
|
|
if !ok {
|
|
letter = strconv.Itoa(int(level))
|
|
}
|
|
msg = letter + " " + msg
|
|
|
|
// Time.
|
|
if l.LogTime {
|
|
msg = time.Now().Format("20060102 15:04:05.000000 ") + msg
|
|
}
|
|
|
|
if !strings.HasSuffix(msg, "\n") {
|
|
msg += "\n"
|
|
}
|
|
|
|
l.Lock()
|
|
l.w.Write([]byte(msg))
|
|
l.Unlock()
|
|
}
|
|
|
|
func (l *Logger) Debugf(format string, a ...interface{}) {
|
|
l.Log(Debug, 1, format, a...)
|
|
}
|
|
|
|
func (l *Logger) Infof(format string, a ...interface{}) {
|
|
l.Log(Info, 1, format, a...)
|
|
}
|
|
|
|
func (l *Logger) Errorf(format string, a ...interface{}) error {
|
|
l.Log(Error, 1, format, a...)
|
|
return fmt.Errorf(format, a...)
|
|
}
|
|
|
|
func (l *Logger) Fatalf(format string, a ...interface{}) {
|
|
l.Log(-2, 1, format, a...)
|
|
// TODO: Log traceback?
|
|
os.Exit(1)
|
|
}
|
|
|
|
// The default logger, used by the top-level functions below.
|
|
var Default = &Logger{
|
|
w: os.Stderr,
|
|
CallerSkip: 1,
|
|
Level: Info,
|
|
LogTime: false,
|
|
}
|
|
|
|
// Init the default logger, based on the command-line flags.
|
|
// Must be called after flag.Parse().
|
|
func Init() {
|
|
var err error
|
|
|
|
if *logToSyslog != "" {
|
|
Default, err = NewSyslog(syslog.LOG_DAEMON|syslog.LOG_INFO, *logToSyslog)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
} else if *logFile != "" {
|
|
Default, err = NewFile(*logFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
*logTime = true
|
|
}
|
|
|
|
if *alsoLogToStderr && Default.w != os.Stderr {
|
|
Default.w = multiWriteCloser(Default.w, os.Stderr)
|
|
}
|
|
|
|
Default.CallerSkip = 1
|
|
Default.Level = Level(*vLevel)
|
|
Default.LogTime = *logTime
|
|
}
|
|
|
|
func V(level Level) bool {
|
|
return Default.V(level)
|
|
}
|
|
|
|
func Log(level Level, skip int, format string, a ...interface{}) {
|
|
Default.Log(level, skip, format, a...)
|
|
}
|
|
|
|
func Debugf(format string, a ...interface{}) {
|
|
Default.Debugf(format, a...)
|
|
}
|
|
|
|
func Infof(format string, a ...interface{}) {
|
|
Default.Infof(format, a...)
|
|
}
|
|
|
|
func Errorf(format string, a ...interface{}) error {
|
|
return Default.Errorf(format, a...)
|
|
}
|
|
|
|
func Fatalf(format string, a ...interface{}) {
|
|
Default.Fatalf(format, a...)
|
|
}
|
|
|
|
// multiWriteCloser creates a WriteCloser that duplicates its writes and
|
|
// closes to all the provided writers.
|
|
func multiWriteCloser(wc ...io.WriteCloser) io.WriteCloser {
|
|
return mwc(wc)
|
|
}
|
|
|
|
type mwc []io.WriteCloser
|
|
|
|
func (m mwc) Write(p []byte) (n int, err error) {
|
|
for _, w := range m {
|
|
if n, err = w.Write(p); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
func (m mwc) Close() error {
|
|
for _, w := range m {
|
|
if err := w.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|