Write an openapi spec for the worker API and use `deepmap/oapi-codegen`
to generate scaffolding for the server-side using the `labstack/echo`
server.
Incidentally, echo by default returns the errors in the same format that
worker API always has:
{ "message": "..." }
The API itself is unchanged to make this change easier to understand. It
will be changed to better suit our needs in future commits.
416 lines
7.2 KiB
Go
416 lines
7.2 KiB
Go
package log
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"strconv"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/mattn/go-isatty"
|
|
"github.com/valyala/fasttemplate"
|
|
|
|
"github.com/labstack/gommon/color"
|
|
)
|
|
|
|
type (
|
|
Logger struct {
|
|
prefix string
|
|
level uint32
|
|
skip int
|
|
output io.Writer
|
|
template *fasttemplate.Template
|
|
levels []string
|
|
color *color.Color
|
|
bufferPool sync.Pool
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
Lvl uint8
|
|
|
|
JSON map[string]interface{}
|
|
)
|
|
|
|
const (
|
|
DEBUG Lvl = iota + 1
|
|
INFO
|
|
WARN
|
|
ERROR
|
|
OFF
|
|
panicLevel
|
|
fatalLevel
|
|
)
|
|
|
|
var (
|
|
global = New("-")
|
|
defaultHeader = `{"time":"${time_rfc3339_nano}","level":"${level}","prefix":"${prefix}",` +
|
|
`"file":"${short_file}","line":"${line}"}`
|
|
)
|
|
|
|
func init() {
|
|
global.skip = 3
|
|
}
|
|
|
|
func New(prefix string) (l *Logger) {
|
|
l = &Logger{
|
|
level: uint32(INFO),
|
|
skip: 2,
|
|
prefix: prefix,
|
|
template: l.newTemplate(defaultHeader),
|
|
color: color.New(),
|
|
bufferPool: sync.Pool{
|
|
New: func() interface{} {
|
|
return bytes.NewBuffer(make([]byte, 256))
|
|
},
|
|
},
|
|
}
|
|
l.initLevels()
|
|
l.SetOutput(output())
|
|
return
|
|
}
|
|
|
|
func (l *Logger) initLevels() {
|
|
l.levels = []string{
|
|
"-",
|
|
l.color.Blue("DEBUG"),
|
|
l.color.Green("INFO"),
|
|
l.color.Yellow("WARN"),
|
|
l.color.Red("ERROR"),
|
|
"",
|
|
l.color.Yellow("PANIC", color.U),
|
|
l.color.Red("FATAL", color.U),
|
|
}
|
|
}
|
|
|
|
func (l *Logger) newTemplate(format string) *fasttemplate.Template {
|
|
return fasttemplate.New(format, "${", "}")
|
|
}
|
|
|
|
func (l *Logger) DisableColor() {
|
|
l.color.Disable()
|
|
l.initLevels()
|
|
}
|
|
|
|
func (l *Logger) EnableColor() {
|
|
l.color.Enable()
|
|
l.initLevels()
|
|
}
|
|
|
|
func (l *Logger) Prefix() string {
|
|
return l.prefix
|
|
}
|
|
|
|
func (l *Logger) SetPrefix(p string) {
|
|
l.prefix = p
|
|
}
|
|
|
|
func (l *Logger) Level() Lvl {
|
|
return Lvl(atomic.LoadUint32(&l.level))
|
|
}
|
|
|
|
func (l *Logger) SetLevel(level Lvl) {
|
|
atomic.StoreUint32(&l.level, uint32(level))
|
|
}
|
|
|
|
func (l *Logger) Output() io.Writer {
|
|
return l.output
|
|
}
|
|
|
|
func (l *Logger) SetOutput(w io.Writer) {
|
|
l.output = w
|
|
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
|
|
l.DisableColor()
|
|
}
|
|
}
|
|
|
|
func (l *Logger) Color() *color.Color {
|
|
return l.color
|
|
}
|
|
|
|
func (l *Logger) SetHeader(h string) {
|
|
l.template = l.newTemplate(h)
|
|
}
|
|
|
|
func (l *Logger) Print(i ...interface{}) {
|
|
l.log(0, "", i...)
|
|
// fmt.Fprintln(l.output, i...)
|
|
}
|
|
|
|
func (l *Logger) Printf(format string, args ...interface{}) {
|
|
l.log(0, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Printj(j JSON) {
|
|
l.log(0, "json", j)
|
|
}
|
|
|
|
func (l *Logger) Debug(i ...interface{}) {
|
|
l.log(DEBUG, "", i...)
|
|
}
|
|
|
|
func (l *Logger) Debugf(format string, args ...interface{}) {
|
|
l.log(DEBUG, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Debugj(j JSON) {
|
|
l.log(DEBUG, "json", j)
|
|
}
|
|
|
|
func (l *Logger) Info(i ...interface{}) {
|
|
l.log(INFO, "", i...)
|
|
}
|
|
|
|
func (l *Logger) Infof(format string, args ...interface{}) {
|
|
l.log(INFO, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Infoj(j JSON) {
|
|
l.log(INFO, "json", j)
|
|
}
|
|
|
|
func (l *Logger) Warn(i ...interface{}) {
|
|
l.log(WARN, "", i...)
|
|
}
|
|
|
|
func (l *Logger) Warnf(format string, args ...interface{}) {
|
|
l.log(WARN, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Warnj(j JSON) {
|
|
l.log(WARN, "json", j)
|
|
}
|
|
|
|
func (l *Logger) Error(i ...interface{}) {
|
|
l.log(ERROR, "", i...)
|
|
}
|
|
|
|
func (l *Logger) Errorf(format string, args ...interface{}) {
|
|
l.log(ERROR, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Errorj(j JSON) {
|
|
l.log(ERROR, "json", j)
|
|
}
|
|
|
|
func (l *Logger) Fatal(i ...interface{}) {
|
|
l.log(fatalLevel, "", i...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func (l *Logger) Fatalf(format string, args ...interface{}) {
|
|
l.log(fatalLevel, format, args...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func (l *Logger) Fatalj(j JSON) {
|
|
l.log(fatalLevel, "json", j)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func (l *Logger) Panic(i ...interface{}) {
|
|
l.log(panicLevel, "", i...)
|
|
panic(fmt.Sprint(i...))
|
|
}
|
|
|
|
func (l *Logger) Panicf(format string, args ...interface{}) {
|
|
l.log(panicLevel, format, args...)
|
|
panic(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (l *Logger) Panicj(j JSON) {
|
|
l.log(panicLevel, "json", j)
|
|
panic(j)
|
|
}
|
|
|
|
func DisableColor() {
|
|
global.DisableColor()
|
|
}
|
|
|
|
func EnableColor() {
|
|
global.EnableColor()
|
|
}
|
|
|
|
func Prefix() string {
|
|
return global.Prefix()
|
|
}
|
|
|
|
func SetPrefix(p string) {
|
|
global.SetPrefix(p)
|
|
}
|
|
|
|
func Level() Lvl {
|
|
return global.Level()
|
|
}
|
|
|
|
func SetLevel(level Lvl) {
|
|
global.SetLevel(level)
|
|
}
|
|
|
|
func Output() io.Writer {
|
|
return global.Output()
|
|
}
|
|
|
|
func SetOutput(w io.Writer) {
|
|
global.SetOutput(w)
|
|
}
|
|
|
|
func SetHeader(h string) {
|
|
global.SetHeader(h)
|
|
}
|
|
|
|
func Print(i ...interface{}) {
|
|
global.Print(i...)
|
|
}
|
|
|
|
func Printf(format string, args ...interface{}) {
|
|
global.Printf(format, args...)
|
|
}
|
|
|
|
func Printj(j JSON) {
|
|
global.Printj(j)
|
|
}
|
|
|
|
func Debug(i ...interface{}) {
|
|
global.Debug(i...)
|
|
}
|
|
|
|
func Debugf(format string, args ...interface{}) {
|
|
global.Debugf(format, args...)
|
|
}
|
|
|
|
func Debugj(j JSON) {
|
|
global.Debugj(j)
|
|
}
|
|
|
|
func Info(i ...interface{}) {
|
|
global.Info(i...)
|
|
}
|
|
|
|
func Infof(format string, args ...interface{}) {
|
|
global.Infof(format, args...)
|
|
}
|
|
|
|
func Infoj(j JSON) {
|
|
global.Infoj(j)
|
|
}
|
|
|
|
func Warn(i ...interface{}) {
|
|
global.Warn(i...)
|
|
}
|
|
|
|
func Warnf(format string, args ...interface{}) {
|
|
global.Warnf(format, args...)
|
|
}
|
|
|
|
func Warnj(j JSON) {
|
|
global.Warnj(j)
|
|
}
|
|
|
|
func Error(i ...interface{}) {
|
|
global.Error(i...)
|
|
}
|
|
|
|
func Errorf(format string, args ...interface{}) {
|
|
global.Errorf(format, args...)
|
|
}
|
|
|
|
func Errorj(j JSON) {
|
|
global.Errorj(j)
|
|
}
|
|
|
|
func Fatal(i ...interface{}) {
|
|
global.Fatal(i...)
|
|
}
|
|
|
|
func Fatalf(format string, args ...interface{}) {
|
|
global.Fatalf(format, args...)
|
|
}
|
|
|
|
func Fatalj(j JSON) {
|
|
global.Fatalj(j)
|
|
}
|
|
|
|
func Panic(i ...interface{}) {
|
|
global.Panic(i...)
|
|
}
|
|
|
|
func Panicf(format string, args ...interface{}) {
|
|
global.Panicf(format, args...)
|
|
}
|
|
|
|
func Panicj(j JSON) {
|
|
global.Panicj(j)
|
|
}
|
|
|
|
func (l *Logger) log(level Lvl, format string, args ...interface{}) {
|
|
if level >= l.Level() || level == 0 {
|
|
buf := l.bufferPool.Get().(*bytes.Buffer)
|
|
buf.Reset()
|
|
defer l.bufferPool.Put(buf)
|
|
_, file, line, _ := runtime.Caller(l.skip)
|
|
message := ""
|
|
|
|
if format == "" {
|
|
message = fmt.Sprint(args...)
|
|
} else if format == "json" {
|
|
b, err := json.Marshal(args[0])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
message = string(b)
|
|
} else {
|
|
message = fmt.Sprintf(format, args...)
|
|
}
|
|
|
|
_, err := l.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
|
|
switch tag {
|
|
case "time_rfc3339":
|
|
return w.Write([]byte(time.Now().Format(time.RFC3339)))
|
|
case "time_rfc3339_nano":
|
|
return w.Write([]byte(time.Now().Format(time.RFC3339Nano)))
|
|
case "level":
|
|
return w.Write([]byte(l.levels[level]))
|
|
case "prefix":
|
|
return w.Write([]byte(l.prefix))
|
|
case "long_file":
|
|
return w.Write([]byte(file))
|
|
case "short_file":
|
|
return w.Write([]byte(path.Base(file)))
|
|
case "line":
|
|
return w.Write([]byte(strconv.Itoa(line)))
|
|
}
|
|
return 0, nil
|
|
})
|
|
|
|
if err == nil {
|
|
s := buf.String()
|
|
i := buf.Len() - 1
|
|
if s[i] == '}' {
|
|
// JSON header
|
|
buf.Truncate(i)
|
|
buf.WriteByte(',')
|
|
if format == "json" {
|
|
buf.WriteString(message[1:])
|
|
} else {
|
|
buf.WriteString(`"message":`)
|
|
buf.WriteString(strconv.Quote(message))
|
|
buf.WriteString(`}`)
|
|
}
|
|
} else {
|
|
// Text header
|
|
buf.WriteByte(' ')
|
|
buf.WriteString(message)
|
|
}
|
|
buf.WriteByte('\n')
|
|
l.mutex.Lock()
|
|
defer l.mutex.Unlock()
|
|
l.output.Write(buf.Bytes())
|
|
}
|
|
}
|
|
}
|