weather/pkg/logger/logger.go

154 lines
3.9 KiB
Go

package logger
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"os"
"os/signal"
"syscall"
"go.elastic.co/apm/module/apmzap"
"go.elastic.co/ecszap"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
// available level are
// DebugLevel
// InfoLevel
// WarnLevel
// ErrorLevel
// DPanicLevel
// PanicLevel
// FatalLevel
//WeatherLogger custom logger with totation and http Request and Response capability
type WeatherLogger struct {
*zap.Logger
Level zap.AtomicLevel
}
//NewLogger create new named logger
func NewLogger(loggerName, logPath string, level zap.AtomicLevel) *WeatherLogger {
rotationPolicy := &lumberjack.Logger{
Filename: logPath,
MaxSize: 5, // megabytes
MaxBackups: 2, // number of backup
// MaxAge: 28, // days
Compress: true, // disabled by default
}
rotateCatcher(rotationPolicy)
encoderConfig := ecszap.EncoderConfig{
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeDuration: zapcore.MillisDurationEncoder,
EncodeCaller: ecszap.FullCallerEncoder,
EnableStackTrace: false,
}
// log enabled by atomic level and error priority
errorPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= level.Level() && lvl >= zapcore.ErrorLevel
})
// log enabled by atomic level and low priority
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= level.Level() && lvl < zapcore.ErrorLevel
})
consoleDebugging := zapcore.Lock(os.Stdout)
consoleErrors := zapcore.Lock(os.Stderr)
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
core := zapcore.NewTee(
ecszap.NewCore(encoderConfig, zapcore.AddSync(rotationPolicy), level),
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
zapcore.NewCore(consoleEncoder, consoleErrors, errorPriority),
)
logger := zap.New(core, zap.AddCaller())
return &WeatherLogger{
Logger: logger.Named(loggerName),
Level: level,
}
}
//HTTPLogHandler http handler to log http request and http response
func (wl *WeatherLogger) HTTPLogHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ww := ResponseWriter{
ResponseWriter: w,
}
wl.logHTTPRequest(r)
next.ServeHTTP(&ww, r)
wl.logHTTPResponse(ww)
})
}
type readCloser struct {
io.Reader
io.Closer
}
func useTeeReader(rc io.ReadCloser, w io.Writer) io.ReadCloser {
tee := io.TeeReader(rc, w)
teeCloser := readCloser{tee, rc}
return teeCloser
}
//logHTTPRequest print request information
func (wl *WeatherLogger) logHTTPRequest(r *http.Request) {
var buf bytes.Buffer
b, err := ioutil.ReadAll(io.TeeReader(r.Body, &buf))
wl.handleError(err)
r.Body = readCloser{&buf, r.Body}
traceContextFields := apmzap.TraceContext(r.Context())
// var buf bytes.Buffer
// var buf2 bytes.Buffer
// b, err := io.Copy(io.MultiWriter(&buf, &buf2), r.Body)
// wl.handleError(err)
// r.Body = readCloser{&buf2, r.Body}
wl.Logger.With(traceContextFields...).Debug("Request",
zap.String("http.request.method", r.Method),
zap.ByteString("http.request.body.content", b),
zap.Int("http.request.body.bytes", len(b)),
// zap.Int64("http.request.bytes", r.Header ContentLength), // total body+header len
// zap.String(""),
// zap.String(""),
// zap.String(""),
)
}
//logHTTPResponse print response information
func (wl *WeatherLogger) logHTTPResponse(ww ResponseWriter) {
wl.Logger.Debug("Response",
zap.Int("http.response.status_code", ww.Status),
zap.Int("http.response.body.bytes", ww.Length),
)
}
//rotateCatcher force log rotation on SIGUSR1 signal
func rotateCatcher(rotationPolicy *lumberjack.Logger) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGUSR1)
go func() {
for {
<-c
rotationPolicy.Rotate()
}
}()
}
//handleError generic fatal error
func (wl *WeatherLogger) handleError(err error) {
if err != nil {
wl.Logger.Fatal("Logger error", zap.Error(err))
}
}