weather/pkg/logger/logger.go

166 lines
4.1 KiB
Go

package logger
import (
"bytes"
"go/weather/pkg/headers"
"io"
"io/ioutil"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/aws/aws-sdk-go/aws"
"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 {
// dt := apm.DefaultTracer
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ww := ResponseWriter{
ResponseWriter: w,
}
// span, ctx := apm.StartSpan(r.Context(), "updateRequestCount", "custom")
// defer span.End()
wl.LogHTTPRequest(r)
next.ServeHTTP(&ww, r)
wl.LogHTTPResponse(ww)
})
}
//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 = struct {
io.Reader
io.Closer
}{&buf, r.Body}
lwith := wl.Logger.
With(apmzap.TraceContext(r.Context())...).
With([]zap.Field{
zap.String("http.request.method", r.Method),
// zap.Int64("http.request.bytes", r.Header ContentLength), // total body+header len
zap.String("http.request.mime_type", r.Header.Get(headers.ContentType)),
// zap.String(""),
// zap.String(""),
}...)
if len(b) > 0 {
lwith.With([]zap.Field{
zap.ByteString("http.request.body.content", b),
zap.Int("http.request.body.bytes", len(b)),
}...)
}
// realy log
lwith.Debug("Request")
}
//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))
}
}
//GetAwsLevelnconvert log zapcore.Level to aws.LogLevelType
func (wl *WeatherLogger) GetAwsLevel() aws.LogLevelType {
switch wl.Level.Level() {
case zapcore.DebugLevel:
return aws.LogDebug
default:
return aws.LogOff
}
}