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 } }