From fefc5338347dacf29b007584b1f0e4f515a0799e Mon Sep 17 00:00:00 2001 From: Jiun Lee <70408571+Skyenought@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:16:57 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Add=20Logger=20interface=20and?= =?UTF-8?q?=20fiberlog=20(#2499)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add log for fiber * replace log in fiber * add Log use to adapt for log libraries * Update app.go Co-authored-by: Tomás Warynyca <41587659+tomaswarynyca@users.noreply.github.com> * wip: add log docs * add WithLogger use to print key and value * remove CtxLogger and add WithContext use to bind Context * fix errcheck * fix errcheck * update log.md --------- Co-authored-by: Tomás Warynyca <41587659+tomaswarynyca@users.noreply.github.com> --- app.go | 10 +- docs/api/log.md | 156 ++++++++++++++++++++ helpers.go | 8 +- hooks.go | 6 +- internal/template/html/html.go | 6 +- listen.go | 4 +- log/default.go | 205 ++++++++++++++++++++++++++ log/default_test.go | 196 ++++++++++++++++++++++++ log/fiberlog.go | 141 ++++++++++++++++++ log/fiberlog_test.go | 24 +++ log/log.go | 100 +++++++++++++ middleware/cache/config.go | 6 +- middleware/cors/cors.go | 4 +- middleware/csrf/config.go | 8 +- middleware/idempotency/idempotency.go | 4 +- middleware/limiter/config.go | 8 +- middleware/proxy/proxy.go | 4 +- middleware/session/config.go | 4 +- middleware/timeout/timeout.go | 9 +- prefork.go | 4 +- 20 files changed, 865 insertions(+), 42 deletions(-) create mode 100644 docs/api/log.md create mode 100644 log/default.go create mode 100644 log/default_test.go create mode 100644 log/fiberlog.go create mode 100644 log/fiberlog_test.go create mode 100644 log/log.go diff --git a/app.go b/app.go index 40366ce6..71b874b7 100644 --- a/app.go +++ b/app.go @@ -14,7 +14,6 @@ import ( "encoding/xml" "errors" "fmt" - "log" "net" "net/http" "net/http/httputil" @@ -24,6 +23,7 @@ import ( "sync" "time" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/utils" "github.com/valyala/fasthttp" @@ -521,7 +521,7 @@ func New(config ...Config) *App { if app.config.ETag { if !IsChild() { - log.Printf("[Warning] Config.ETag is deprecated since v2.0.6, please use 'middleware/etag'.\n") + log.Warn("Config.ETag is deprecated since v2.0.6, please use 'middleware/etag'.") } } @@ -589,7 +589,7 @@ func (app *App) handleTrustedProxy(ipAddress string) { if strings.Contains(ipAddress, "/") { _, ipNet, err := net.ParseCIDR(ipAddress) if err != nil { - log.Printf("[Warning] IP range %q could not be parsed: %v\n", ipAddress, err) + log.Warnf("IP range %q could not be parsed: %v", ipAddress, err) } else { app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet) } @@ -987,7 +987,7 @@ func (app *App) init() *App { // Only load templates if a view engine is specified if app.config.Views != nil { if err := app.config.Views.Load(); err != nil { - log.Printf("[Warning]: failed to load views: %v\n", err) + log.Warnf("failed to load views: %v", err) } } @@ -1084,7 +1084,7 @@ func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { } if catch := app.ErrorHandler(c, err); catch != nil { - log.Printf("serverErrorHandler: failed to call ErrorHandler: %v\n", catch) + log.Errorf("serverErrorHandler: failed to call ErrorHandler: %v", catch) _ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here return } diff --git a/docs/api/log.md b/docs/api/log.md new file mode 100644 index 00000000..f73ee298 --- /dev/null +++ b/docs/api/log.md @@ -0,0 +1,156 @@ +--- +id: log +title: Log +description: Fiber's built-in log package +sidebar_position: 8 +--- +## Log + +We can use logs to observe program behavior, diagnose problems, or configure corresponding alarms. +And defining a well structured log can improve search efficiency and facilitate handling of problems. + +Fiber provides a default way to print logs in the standard output. +It also provides several global functions, such as `log.Info`, `log.Errorf`, `log.Warnw`, etc. + +## Log levels + +```go +const ( + LevelTrace Level = iota + LevelDebug + LevelInfo + LevelWarn + LevelError + LevelFatal + LevelPanic +) +``` + +## Custom log + +Fiber provides the `AllLogger` interface for adapting the various log libraries. + +```go +type CommonLogger interface { + Logger + FormatLogger + WithLogger +} + +type AllLogger interface { + CommonLogger + ControlLogger + WithLogger +} +``` + +## Print log +Note: The method of calling the Fatal level will interrupt the program running after printing the log, please use it with caution. +Directly print logs of different levels, which will be entered into messageKey, the default is msg. + +```go +log.Info("Hello, World!") +log.Debug("Are you OK?") +log.Info("42 is the answer to life, the universe, and everything") +log.Warn("We are under attack!") +log.Error("Houston, we have a problem.") +log.Fatal("So Long, and Thanks for All the Fislog.") +log.Panic("The system is down.") +``` +Format and print logs of different levels, all methods end with f + +```go +log.Debugf("Hello %s", "boy") +log.Infof("%d is the answer to life, the universe, and everything", 233) +log.Warnf("We are under attack %s!", "boss") +log.Errorf("%s, we have a problem.", "Master Shifu") +log.Fatalf("So Long, and Thanks for All the %s.", "banana") +``` + +Print a message with the key and value, or `KEYVALS UNPAIRED` if the key and value are not a pair. + +```go +log.Debugw("", "Hello", "boy") +log.Infow("", "number", 233) +log.Warnw("", "job", "boss") +log.Errorw("", "name", "Master Shifu") +log.Fatalw("", "fruit", "banana") +``` + +## Global log +If you are in a project and just want to use a simple log function that can be printed at any time in the global, we provide a global log. + +```go +import "github.com/gofiber/fiber/v2/log" + +log.Info("info") +log.Warn("warn") +``` + +The above is using the default `log.DefaultLogger` standard output. +You can also find an already implemented adaptation under contrib, or use your own implemented Logger and use `log.SetLogger` to set the global log logger. + +```go +import ( + "log" + fiberlog "github.com/gofiber/fiber/v2/log" +) + +var _ log.AllLogger = (*customLogger)(nil) + +type customLogger struct { + stdlog *log.Logger +} + +// ... +// inject your custom logger +fiberlog.SetLogger(customLogger) +``` + +## Set Level +`log.SetLevel` sets the level of logs below which logs will not be output. +The default logger is LevelTrace. + +Note that this method is not **concurrent-safe**. + +```go +import "github.com/gofiber/fiber/v2/log" + +log.SetLevel(log.LevelInfo) +``` +## Set output + +`log.SetOutput` sets the output destination of the logger. The default logger types the log in the console. + +```go +var logger AllLogger = &defaultLogger{ + stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), + depth: 4, +} +``` + +Set the output destination to the file. + +```go +// Output to ./test.log file +f, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) +if err != nil { + return +} +log.SetOutput(f) +``` +Set the output destination to the console and file. + +```go +// Output to ./test.log file +file, _ := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) +iw := io.MultiWriter(os.Stdout, file) +log.SetOutput(iw) +``` +## Bind context +Set the context, using the following method will return a `CommonLogger` instance bound to the specified context +```go +commonLogger := log.WithContext(ctx) +commonLogger.Info("info") +``` + diff --git a/helpers.go b/helpers.go index cedab4c5..cfe31e0d 100644 --- a/helpers.go +++ b/helpers.go @@ -10,7 +10,6 @@ import ( "fmt" "hash/crc32" "io" - "log" "net" "os" "path/filepath" @@ -19,6 +18,7 @@ import ( "time" "unsafe" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/utils" "github.com/valyala/bytebufferpool" @@ -75,7 +75,7 @@ func readContent(rf io.ReaderFrom, name string) (int64, error) { } defer func() { if err = f.Close(); err != nil { - log.Printf("Error closing file: %s\n", err) + log.Errorf("Error closing file: %s", err) } }() if n, err := rf.ReadFrom(f); err != nil { @@ -192,7 +192,7 @@ func setETag(c *Ctx, weak bool) { //nolint: revive // Accepting a bool param is if clientEtag[2:] == etag || clientEtag[2:] == etag[2:] { // W/1 == 1 || W/1 == W/1 if err := c.SendStatus(StatusNotModified); err != nil { - log.Printf("setETag: failed to SendStatus: %v\n", err) + log.Errorf("setETag: failed to SendStatus: %v", err) } c.fasthttp.ResetBody() return @@ -204,7 +204,7 @@ func setETag(c *Ctx, weak bool) { //nolint: revive // Accepting a bool param is if strings.Contains(clientEtag, etag) { // 1 == 1 if err := c.SendStatus(StatusNotModified); err != nil { - log.Printf("setETag: failed to SendStatus: %v\n", err) + log.Errorf("setETag: failed to SendStatus: %v", err) } c.fasthttp.ResetBody() return diff --git a/hooks.go b/hooks.go index 099cf056..6b0b860c 100644 --- a/hooks.go +++ b/hooks.go @@ -1,7 +1,7 @@ package fiber import ( - "log" + "github.com/gofiber/fiber/v2/log" ) // OnRouteHandler Handlers define a function to create hooks for Fiber. @@ -194,7 +194,7 @@ func (h *Hooks) executeOnListenHooks(listenData ListenData) error { func (h *Hooks) executeOnShutdownHooks() { for _, v := range h.onShutdown { if err := v(); err != nil { - log.Printf("failed to call shutdown hook: %v\n", err) + log.Errorf("failed to call shutdown hook: %v", err) } } } @@ -202,7 +202,7 @@ func (h *Hooks) executeOnShutdownHooks() { func (h *Hooks) executeOnForkHooks(pid int) { for _, v := range h.onFork { if err := v(pid); err != nil { - log.Printf("failed to call fork hook: %v\n", err) + log.Errorf("failed to call fork hook: %v", err) } } } diff --git a/internal/template/html/html.go b/internal/template/html/html.go index f892fdef..98e28262 100644 --- a/internal/template/html/html.go +++ b/internal/template/html/html.go @@ -4,7 +4,6 @@ import ( "fmt" "html/template" "io" - "log" "net/http" "os" "path/filepath" @@ -12,6 +11,7 @@ import ( "sync" "github.com/gofiber/fiber/v2/internal/template/utils" + "github.com/gofiber/fiber/v2/log" ) // Engine struct @@ -113,7 +113,7 @@ func (e *Engine) Debug(enabled bool) *Engine { // Parse is deprecated, please use Load() instead func (e *Engine) Parse() error { - log.Println("[Warning] Parse() is deprecated, please use Load() instead.") + log.Warn("Parse() is deprecated, please use Load() instead.") return e.Load() } @@ -170,7 +170,7 @@ func (e *Engine) Load() error { } // Debugging if e.debug { - log.Printf("views: parsed template: %s\n", name) + log.Infof("views: parsed template: %s", name) } return err } diff --git a/listen.go b/listen.go index ef48ab89..de4a8600 100644 --- a/listen.go +++ b/listen.go @@ -9,7 +9,6 @@ import ( "crypto/x509" "errors" "fmt" - "log" "net" "os" "path/filepath" @@ -20,6 +19,7 @@ import ( "strings" "text/tabwriter" + "github.com/gofiber/fiber/v2/log" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "github.com/mattn/go-runewidth" @@ -49,7 +49,7 @@ func (app *App) Listener(ln net.Listener) error { // Prefork is not supported for custom listeners if app.config.Prefork { - log.Printf("[Warning] Prefork isn't supported for custom listeners.\n") + log.Warn("Prefork isn't supported for custom listeners.") } // Start listening diff --git a/log/default.go b/log/default.go new file mode 100644 index 00000000..e78f1e3d --- /dev/null +++ b/log/default.go @@ -0,0 +1,205 @@ +package log + +import ( + "context" + "fmt" + "io" + "log" + "os" + "sync" + + "github.com/valyala/bytebufferpool" +) + +var _ AllLogger = (*defaultLogger)(nil) + +type defaultLogger struct { + stdlog *log.Logger + level Level + depth int +} + +// privateLog logs a message at a given level log the default logger. +// when the level is fatal, it will exit the program. +func (l *defaultLogger) privateLog(lv Level, fmtArgs []interface{}) { + if l.level > lv { + return + } + level := lv.toString() + buf := bytebufferpool.Get() + _, _ = buf.WriteString(level) //nolint:errcheck // It is fine to ignore the error + _, _ = buf.WriteString(fmt.Sprint(fmtArgs...)) //nolint:errcheck // It is fine to ignore the error + + _ = l.stdlog.Output(l.depth, buf.String()) //nolint:errcheck // It is fine to ignore the error + buf.Reset() + bytebufferpool.Put(buf) + if lv == LevelFatal { + os.Exit(1) //nolint:revive // we want to exit the program when Fatal is called + } +} + +// privateLog logs a message at a given level log the default logger. +// when the level is fatal, it will exit the program. +func (l *defaultLogger) privateLogf(lv Level, format string, fmtArgs []interface{}) { + if l.level > lv { + return + } + level := lv.toString() + buf := bytebufferpool.Get() + _, _ = buf.WriteString(level) //nolint:errcheck // It is fine to ignore the error + + if len(fmtArgs) > 0 { + _, _ = fmt.Fprintf(buf, format, fmtArgs...) + } else { + _, _ = fmt.Fprint(buf, fmtArgs...) + } + _ = l.stdlog.Output(l.depth, buf.String()) //nolint:errcheck // It is fine to ignore the error + buf.Reset() + bytebufferpool.Put(buf) + if lv == LevelFatal { + os.Exit(1) //nolint:revive // we want to exit the program when Fatal is called + } +} + +// privateLogw logs a message at a given level log the default logger. +// when the level is fatal, it will exit the program. +func (l *defaultLogger) privateLogw(lv Level, format string, keysAndValues []interface{}) { + if l.level > lv { + return + } + level := lv.toString() + buf := bytebufferpool.Get() + _, _ = buf.WriteString(level) //nolint:errcheck // It is fine to ignore the error + + // Write format privateLog buffer + if format != "" { + _, _ = buf.WriteString(format) //nolint:errcheck // It is fine to ignore the error + } + var once sync.Once + isFirst := true + // Write keys and values privateLog buffer + if len(keysAndValues) > 0 { + if (len(keysAndValues) & 1) == 1 { + keysAndValues = append(keysAndValues, "KEYVALS UNPAIRED") + } + + for i := 0; i < len(keysAndValues); i += 2 { + if format == "" && isFirst { + once.Do(func() { + _, _ = fmt.Fprintf(buf, "%s=%v", keysAndValues[i], keysAndValues[i+1]) + isFirst = false + }) + continue + } + _, _ = fmt.Fprintf(buf, " %s=%v", keysAndValues[i], keysAndValues[i+1]) + } + } + + _ = l.stdlog.Output(l.depth, buf.String()) //nolint:errcheck // It is fine to ignore the error + buf.Reset() + bytebufferpool.Put(buf) + if lv == LevelFatal { + os.Exit(1) //nolint:revive // we want to exit the program when Fatal is called + } +} + +func (l *defaultLogger) Trace(v ...interface{}) { + l.privateLog(LevelTrace, v) +} + +func (l *defaultLogger) Debug(v ...interface{}) { + l.privateLog(LevelDebug, v) +} + +func (l *defaultLogger) Info(v ...interface{}) { + l.privateLog(LevelInfo, v) +} + +func (l *defaultLogger) Warn(v ...interface{}) { + l.privateLog(LevelWarn, v) +} + +func (l *defaultLogger) Error(v ...interface{}) { + l.privateLog(LevelError, v) +} + +func (l *defaultLogger) Fatal(v ...interface{}) { + l.privateLog(LevelFatal, v) +} + +func (l *defaultLogger) Panic(v ...interface{}) { + l.privateLog(LevelPanic, v) +} + +func (l *defaultLogger) Tracef(format string, v ...interface{}) { + l.privateLogf(LevelTrace, format, v) +} + +func (l *defaultLogger) Debugf(format string, v ...interface{}) { + l.privateLogf(LevelDebug, format, v) +} + +func (l *defaultLogger) Infof(format string, v ...interface{}) { + l.privateLogf(LevelInfo, format, v) +} + +func (l *defaultLogger) Warnf(format string, v ...interface{}) { + l.privateLogf(LevelWarn, format, v) +} + +func (l *defaultLogger) Errorf(format string, v ...interface{}) { + l.privateLogf(LevelError, format, v) +} + +func (l *defaultLogger) Fatalf(format string, v ...interface{}) { + l.privateLogf(LevelFatal, format, v) +} + +func (l *defaultLogger) Panicf(format string, v ...interface{}) { + l.privateLogf(LevelPanic, format, v) +} + +func (l *defaultLogger) Tracew(msg string, keysAndValues ...interface{}) { + l.privateLogw(LevelTrace, msg, keysAndValues) +} + +func (l *defaultLogger) Debugw(msg string, keysAndValues ...interface{}) { + l.privateLogw(LevelDebug, msg, keysAndValues) +} + +func (l *defaultLogger) Infow(msg string, keysAndValues ...interface{}) { + l.privateLogw(LevelInfo, msg, keysAndValues) +} + +func (l *defaultLogger) Warnw(msg string, keysAndValues ...interface{}) { + l.privateLogw(LevelWarn, msg, keysAndValues) +} + +func (l *defaultLogger) Errorw(msg string, keysAndValues ...interface{}) { + l.privateLogw(LevelError, msg, keysAndValues) +} + +func (l *defaultLogger) Fatalw(msg string, keysAndValues ...interface{}) { + l.privateLogw(LevelFatal, msg, keysAndValues) +} + +func (l *defaultLogger) Panicw(msg string, keysAndValues ...interface{}) { + l.privateLogw(LevelPanic, msg, keysAndValues) +} + +func (l *defaultLogger) WithContext(_ context.Context) CommonLogger { + return l +} + +func (l *defaultLogger) SetLevel(level Level) { + l.level = level +} + +func (l *defaultLogger) SetOutput(writer io.Writer) { + l.stdlog.SetOutput(writer) +} + +// DefaultLogger returns the default logger. +func DefaultLogger() AllLogger { + return logger +} diff --git a/log/default_test.go b/log/default_test.go new file mode 100644 index 00000000..7562b0a1 --- /dev/null +++ b/log/default_test.go @@ -0,0 +1,196 @@ +package log + +import ( + "bytes" + "context" + "log" + "os" + "testing" + + "github.com/gofiber/fiber/v2/utils" +) + +const work = "work" + +func initDefaultLogger() { + logger = &defaultLogger{ + stdlog: log.New(os.Stderr, "", 0), + depth: 4, + } +} + +type byteSliceWriter struct { + b []byte +} + +func (w *byteSliceWriter) Write(p []byte) (int, error) { + w.b = append(w.b, p...) + return len(p), nil +} + +func Test_DefaultLogger(t *testing.T) { + initDefaultLogger() + + var w byteSliceWriter + SetOutput(&w) + + Trace("trace work") + Debug("received work order") + Info("starting work") + Warn("work may fail") + Error("work failed") + Panic("work panic") + utils.AssertEqual(t, "[Trace] trace work\n"+ + "[Debug] received work order\n"+ + "[Info] starting work\n"+ + "[Warn] work may fail\n"+ + "[Error] work failed\n"+ + "[Panic] work panic\n", string(w.b)) +} + +func Test_DefaultFormatLogger(t *testing.T) { + initDefaultLogger() + + var w byteSliceWriter + SetOutput(&w) + + Tracef("trace %s", work) + Debugf("received %s order", work) + Infof("starting %s", work) + Warnf("%s may fail", work) + Errorf("%s failed", work) + Panicf("%s panic", work) + + utils.AssertEqual(t, "[Trace] trace work\n"+ + "[Debug] received work order\n"+ + "[Info] starting work\n"+ + "[Warn] work may fail\n"+ + "[Error] work failed\n"+ + "[Panic] work panic\n", string(w.b)) +} + +func Test_CtxLogger(t *testing.T) { + initDefaultLogger() + + var w byteSliceWriter + SetOutput(&w) + + ctx := context.Background() + + WithContext(ctx).Tracef("trace %s", work) + WithContext(ctx).Debugf("received %s order", work) + WithContext(ctx).Infof("starting %s", work) + WithContext(ctx).Warnf("%s may fail", work) + WithContext(ctx).Errorf("%s failed", work) + WithContext(ctx).Panicf("%s panic", work) + + utils.AssertEqual(t, "[Trace] trace work\n"+ + "[Debug] received work order\n"+ + "[Info] starting work\n"+ + "[Warn] work may fail\n"+ + "[Error] work failed\n"+ + "[Panic] work panic\n", string(w.b)) +} + +func Test_LogfKeyAndValues(t *testing.T) { + tests := []struct { + name string + level Level + format string + fmtArgs []interface{} + keysAndValues []interface{} + wantOutput string + }{ + { + name: "test logf with debug level and key-values", + level: LevelDebug, + format: "", + fmtArgs: nil, + keysAndValues: []interface{}{"name", "Bob", "age", 30}, + wantOutput: "[Debug] name=Bob age=30\n", + }, + { + name: "test logf with info level and key-values", + level: LevelInfo, + format: "", + fmtArgs: nil, + keysAndValues: []interface{}{"status", "ok", "code", 200}, + wantOutput: "[Info] status=ok code=200\n", + }, + { + name: "test logf with warn level and key-values", + level: LevelWarn, + format: "", + fmtArgs: nil, + keysAndValues: []interface{}{"error", "not found", "id", 123}, + wantOutput: "[Warn] error=not found id=123\n", + }, + { + name: "test logf with format and key-values", + level: LevelWarn, + format: "test", + fmtArgs: nil, + keysAndValues: []interface{}{"error", "not found", "id", 123}, + wantOutput: "[Warn] test error=not found id=123\n", + }, + { + name: "test logf with one key", + level: LevelWarn, + format: "", + fmtArgs: nil, + keysAndValues: []interface{}{"error"}, + wantOutput: "[Warn] error=KEYVALS UNPAIRED\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + l := &defaultLogger{ + stdlog: log.New(&buf, "", 0), + level: tt.level, + depth: 4, + } + l.privateLogw(tt.level, tt.format, tt.keysAndValues) + utils.AssertEqual(t, tt.wantOutput, buf.String()) + }) + } +} + +func Test_SetLevel(t *testing.T) { + setLogger := &defaultLogger{ + stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), + depth: 4, + } + + setLogger.SetLevel(LevelTrace) + utils.AssertEqual(t, LevelTrace, setLogger.level) + utils.AssertEqual(t, LevelTrace.toString(), setLogger.level.toString()) + + setLogger.SetLevel(LevelDebug) + utils.AssertEqual(t, LevelDebug, setLogger.level) + utils.AssertEqual(t, LevelDebug.toString(), setLogger.level.toString()) + + setLogger.SetLevel(LevelInfo) + utils.AssertEqual(t, LevelInfo, setLogger.level) + utils.AssertEqual(t, LevelInfo.toString(), setLogger.level.toString()) + + setLogger.SetLevel(LevelWarn) + utils.AssertEqual(t, LevelWarn, setLogger.level) + utils.AssertEqual(t, LevelWarn.toString(), setLogger.level.toString()) + + setLogger.SetLevel(LevelError) + utils.AssertEqual(t, LevelError, setLogger.level) + utils.AssertEqual(t, LevelError.toString(), setLogger.level.toString()) + + setLogger.SetLevel(LevelFatal) + utils.AssertEqual(t, LevelFatal, setLogger.level) + utils.AssertEqual(t, LevelFatal.toString(), setLogger.level.toString()) + + setLogger.SetLevel(LevelPanic) + utils.AssertEqual(t, LevelPanic, setLogger.level) + utils.AssertEqual(t, LevelPanic.toString(), setLogger.level.toString()) + + setLogger.SetLevel(8) + utils.AssertEqual(t, 8, int(setLogger.level)) + utils.AssertEqual(t, "[?8] ", setLogger.level.toString()) +} diff --git a/log/fiberlog.go b/log/fiberlog.go new file mode 100644 index 00000000..90333eef --- /dev/null +++ b/log/fiberlog.go @@ -0,0 +1,141 @@ +package log + +import ( + "context" + "io" +) + +// Fatal calls the default logger's Fatal method and then os.Exit(1). +func Fatal(v ...interface{}) { + logger.Fatal(v...) +} + +// Error calls the default logger's Error method. +func Error(v ...interface{}) { + logger.Error(v...) +} + +// Warn calls the default logger's Warn method. +func Warn(v ...interface{}) { + logger.Warn(v...) +} + +// Info calls the default logger's Info method. +func Info(v ...interface{}) { + logger.Info(v...) +} + +// Debug calls the default logger's Debug method. +func Debug(v ...interface{}) { + logger.Debug(v...) +} + +// Trace calls the default logger's Trace method. +func Trace(v ...interface{}) { + logger.Trace(v...) +} + +// Panic calls the default logger's Panic method. +func Panic(v ...interface{}) { + logger.Panic(v...) +} + +// Fatalf calls the default logger's Fatalf method and then os.Exit(1). +func Fatalf(format string, v ...interface{}) { + logger.Fatalf(format, v...) +} + +// Errorf calls the default logger's Errorf method. +func Errorf(format string, v ...interface{}) { + logger.Errorf(format, v...) +} + +// Warnf calls the default logger's Warnf method. +func Warnf(format string, v ...interface{}) { + logger.Warnf(format, v...) +} + +// Infof calls the default logger's Infof method. +func Infof(format string, v ...interface{}) { + logger.Infof(format, v...) +} + +// Debugf calls the default logger's Debugf method. +func Debugf(format string, v ...interface{}) { + logger.Debugf(format, v...) +} + +// Tracef calls the default logger's Tracef method. +func Tracef(format string, v ...interface{}) { + logger.Tracef(format, v...) +} + +// Panicf calls the default logger's Tracef method. +func Panicf(format string, v ...interface{}) { + logger.Panicf(format, v...) +} + +// Tracew logs a message with some additional context. The variadic key-value +// pairs are treated as they are privateLog With. +func Tracew(msg string, keysAndValues ...interface{}) { + logger.Tracew(msg, keysAndValues...) +} + +// Debugw logs a message with some additional context. The variadic key-value +// pairs are treated as they are privateLog With. +func Debugw(msg string, keysAndValues ...interface{}) { + logger.Debugw(msg, keysAndValues...) +} + +// Infow logs a message with some additional context. The variadic key-value +// pairs are treated as they are privateLog With. +func Infow(msg string, keysAndValues ...interface{}) { + logger.Infow(msg, keysAndValues...) +} + +// Warnw logs a message with some additional context. The variadic key-value +// pairs are treated as they are privateLog With. +func Warnw(msg string, keysAndValues ...interface{}) { + logger.Warnw(msg, keysAndValues...) +} + +// Errorw logs a message with some additional context. The variadic key-value +// pairs are treated as they are privateLog With. +func Errorw(msg string, keysAndValues ...interface{}) { + logger.Errorw(msg, keysAndValues...) +} + +// Fatalw logs a message with some additional context. The variadic key-value +// pairs are treated as they are privateLog With. +func Fatalw(msg string, keysAndValues ...interface{}) { + logger.Fatalw(msg, keysAndValues...) +} + +// Panicw logs a message with some additional context. The variadic key-value +// pairs are treated as they are privateLog With. +func Panicw(msg string, keysAndValues ...interface{}) { + logger.Panicw(msg, keysAndValues...) +} + +func WithContext(ctx context.Context) CommonLogger { + return logger.WithContext(ctx) +} + +// SetLogger sets the default logger and the system logger. +// Note that this method is not concurrent-safe and must not be called +// after the use of DefaultLogger and global functions privateLog this package. +func SetLogger(v AllLogger) { + logger = v +} + +// SetOutput sets the output of default logger and system logger. By default, it is stderr. +func SetOutput(w io.Writer) { + logger.SetOutput(w) +} + +// SetLevel sets the level of logs below which logs will not be output. +// The default logger is LevelTrace. +// Note that this method is not concurrent-safe. +func SetLevel(lv Level) { + logger.SetLevel(lv) +} diff --git a/log/fiberlog_test.go b/log/fiberlog_test.go new file mode 100644 index 00000000..15b1a2cd --- /dev/null +++ b/log/fiberlog_test.go @@ -0,0 +1,24 @@ +package log + +import ( + "log" + "os" + "testing" + + "github.com/gofiber/fiber/v2/utils" +) + +func Test_DefaultSystemLogger(t *testing.T) { + defaultL := DefaultLogger() + utils.AssertEqual(t, logger, defaultL) +} + +func Test_SetLogger(t *testing.T) { + setLog := &defaultLogger{ + stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), + depth: 6, + } + + SetLogger(setLog) + utils.AssertEqual(t, logger, setLog) +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..31b4cc8a --- /dev/null +++ b/log/log.go @@ -0,0 +1,100 @@ +package log + +import ( + "context" + "fmt" + "io" + "log" + "os" +) + +var logger AllLogger = &defaultLogger{ + stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), + depth: 4, +} + +// Logger is a logger interface that provides logging function with levels. +type Logger interface { + Trace(v ...interface{}) + Debug(v ...interface{}) + Info(v ...interface{}) + Warn(v ...interface{}) + Error(v ...interface{}) + Fatal(v ...interface{}) + Panic(v ...interface{}) +} + +// FormatLogger is a logger interface that output logs with a format. +type FormatLogger interface { + Tracef(format string, v ...interface{}) + Debugf(format string, v ...interface{}) + Infof(format string, v ...interface{}) + Warnf(format string, v ...interface{}) + Errorf(format string, v ...interface{}) + Fatalf(format string, v ...interface{}) + Panicf(format string, v ...interface{}) +} + +// WithLogger is a logger interface that output logs with a message and key-value pairs. +type WithLogger interface { + Tracew(msg string, keysAndValues ...interface{}) + Debugw(msg string, keysAndValues ...interface{}) + Infow(msg string, keysAndValues ...interface{}) + Warnw(msg string, keysAndValues ...interface{}) + Errorw(msg string, keysAndValues ...interface{}) + Fatalw(msg string, keysAndValues ...interface{}) + Panicw(msg string, keysAndValues ...interface{}) +} + +type CommonLogger interface { + Logger + FormatLogger + WithLogger +} + +// ControlLogger provides methods to config a logger. +type ControlLogger interface { + SetLevel(Level) + SetOutput(io.Writer) +} + +// AllLogger is the combination of Logger, FormatLogger, CtxLogger and ControlLogger. +// Custom extensions can be made through AllLogger +type AllLogger interface { + CommonLogger + ControlLogger + WithContext(ctx context.Context) CommonLogger +} + +// Level defines the priority of a log message. +// When a logger is configured with a level, any log message with a lower +// log level (smaller by integer comparison) will not be output. +type Level int + +// The levels of logs. +const ( + LevelTrace Level = iota + LevelDebug + LevelInfo + LevelWarn + LevelError + LevelFatal + LevelPanic +) + +var strs = []string{ + "[Trace] ", + "[Debug] ", + "[Info] ", + "[Warn] ", + "[Error] ", + "[Fatal] ", + "[Panic] ", +} + +func (lv Level) toString() string { + if lv >= LevelTrace && lv <= LevelPanic { + return strs[lv] + } + return fmt.Sprintf("[?%d] ", lv) +} diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 734e8535..6fe61627 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -1,10 +1,10 @@ package cache import ( - "log" "time" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/utils" ) @@ -102,11 +102,11 @@ func configDefault(config ...Config) Config { // Set default values if cfg.Store != nil { - log.Printf("[Warning] - [CACHE] Store is deprecated, please use Storage\n") + log.Warn("[CACHE] Store is deprecated, please use Storage") cfg.Storage = cfg.Store } if cfg.Key != nil { - log.Printf("[Warning] - [CACHE] Key is deprecated, please use KeyGenerator\n") + log.Warn("[CACHE] Key is deprecated, please use KeyGenerator") cfg.KeyGenerator = cfg.Key } if cfg.Next == nil { diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index 302b1591..281b2b45 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -1,11 +1,11 @@ package cors import ( - "log" "strconv" "strings" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" ) // Config defines the config for middleware. @@ -98,7 +98,7 @@ func New(config ...Config) fiber.Handler { // Warning logs if both AllowOrigins and AllowOriginsFunc are set if cfg.AllowOrigins != ConfigDefault.AllowOrigins && cfg.AllowOriginsFunc != nil { - log.Printf("[Warning] - [CORS] Both 'AllowOrigins' and 'AllowOriginsFunc' have been defined.\n") + log.Warn("[CORS] Both 'AllowOrigins' and 'AllowOriginsFunc' have been defined.") } // Convert string to slice diff --git a/middleware/csrf/config.go b/middleware/csrf/config.go index 1e2d875a..f17f79a8 100644 --- a/middleware/csrf/config.go +++ b/middleware/csrf/config.go @@ -1,12 +1,12 @@ package csrf import ( - "log" "net/textproto" "strings" "time" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/utils" ) @@ -132,15 +132,15 @@ func configDefault(config ...Config) Config { // Set default values if cfg.TokenLookup != "" { - log.Printf("[Warning] - [CSRF] TokenLookup is deprecated, please use KeyLookup\n") + log.Warn("[CSRF] TokenLookup is deprecated, please use KeyLookup") cfg.KeyLookup = cfg.TokenLookup } if int(cfg.CookieExpires.Seconds()) > 0 { - log.Printf("[Warning] - [CSRF] CookieExpires is deprecated, please use Expiration\n") + log.Warn("[CSRF] CookieExpires is deprecated, please use Expiration") cfg.Expiration = cfg.CookieExpires } if cfg.Cookie != nil { - log.Printf("[Warning] - [CSRF] Cookie is deprecated, please use Cookie* related fields\n") + log.Warn("[CSRF] Cookie is deprecated, please use Cookie* related fields") if cfg.Cookie.Name != "" { cfg.CookieName = cfg.Cookie.Name } diff --git a/middleware/idempotency/idempotency.go b/middleware/idempotency/idempotency.go index f1d3db31..ae4097ae 100644 --- a/middleware/idempotency/idempotency.go +++ b/middleware/idempotency/idempotency.go @@ -2,10 +2,10 @@ package idempotency import ( "fmt" - "log" "strings" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/utils" ) @@ -92,7 +92,7 @@ func New(config ...Config) fiber.Handler { } defer func() { if err := cfg.Lock.Unlock(key); err != nil { - log.Printf("[Error] - [IDEMPOTENCY] failed to unlock key %q: %v", key, err) + log.Errorf("[IDEMPOTENCY] failed to unlock key %q: %v", key, err) } }() diff --git a/middleware/limiter/config.go b/middleware/limiter/config.go index 827debe7..5ec826d4 100644 --- a/middleware/limiter/config.go +++ b/middleware/limiter/config.go @@ -1,10 +1,10 @@ package limiter import ( - "log" "time" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" ) // Config defines the config for middleware. @@ -95,15 +95,15 @@ func configDefault(config ...Config) Config { // Set default values if int(cfg.Duration.Seconds()) > 0 { - log.Printf("[Warning] - [LIMITER] Duration is deprecated, please use Expiration\n") + log.Warn("[LIMITER] Duration is deprecated, please use Expiration") cfg.Expiration = cfg.Duration } if cfg.Key != nil { - log.Printf("[Warning] - [LIMITER] Key is deprecated, please us KeyGenerator\n") + log.Warn("[LIMITER] Key is deprecated, please us KeyGenerator") cfg.KeyGenerator = cfg.Key } if cfg.Store != nil { - log.Printf("[Warning] - [LIMITER] Store is deprecated, please use Storage\n") + log.Warn("[LIMITER] Store is deprecated, please use Storage") cfg.Storage = cfg.Store } if cfg.Next == nil { diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index 9ca5e4bc..eb23c56c 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -3,13 +3,13 @@ package proxy import ( "bytes" "crypto/tls" - "log" "net/url" "strings" "sync" "time" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/utils" "github.com/valyala/fasthttp" @@ -17,7 +17,7 @@ import ( // New is deprecated func New(config Config) fiber.Handler { - log.Printf("[Warning] - [PROXY] proxy.New is deprecated, please use proxy.Balancer instead\n") + log.Warn("[PROXY] proxy.New is deprecated, please use proxy.Balancer instead") return Balancer(config) } diff --git a/middleware/session/config.go b/middleware/session/config.go index aa476eb8..62a80279 100644 --- a/middleware/session/config.go +++ b/middleware/session/config.go @@ -2,11 +2,11 @@ package session import ( "fmt" - "log" "strings" "time" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/utils" ) @@ -97,7 +97,7 @@ func configDefault(config ...Config) Config { cfg.Expiration = ConfigDefault.Expiration } if cfg.CookieName != "" { - log.Printf("[Warning] - [SESSION] CookieName is deprecated, please use KeyLookup\n") + log.Warn("[SESSION] CookieName is deprecated, please use KeyLookup") cfg.KeyLookup = fmt.Sprintf("cookie:%s", cfg.CookieName) } if cfg.KeyLookup == "" { diff --git a/middleware/timeout/timeout.go b/middleware/timeout/timeout.go index e40cc4f8..2bafb3f5 100644 --- a/middleware/timeout/timeout.go +++ b/middleware/timeout/timeout.go @@ -3,10 +3,11 @@ package timeout import ( "context" "errors" - "log" "sync" "time" + "github.com/gofiber/fiber/v2/log" + "github.com/gofiber/fiber/v2" ) @@ -18,7 +19,7 @@ var once sync.Once // Find documentation and sample usage on https://docs.gofiber.io/api/middleware/timeout func New(handler fiber.Handler, timeout time.Duration) fiber.Handler { once.Do(func() { - log.Printf("[Warning] - [TIMEOUT] timeout contains data race issues, not ready for production!") + log.Warn("[TIMEOUT] timeout contains data race issues, not ready for production!") }) if timeout <= 0 { @@ -32,11 +33,11 @@ func New(handler fiber.Handler, timeout time.Duration) fiber.Handler { go func() { defer func() { if err := recover(); err != nil { - log.Printf("[Warning] - [TIMEOUT] recover error %v", err) + log.Errorf("[TIMEOUT] recover error %v", err) } }() if err := handler(ctx); err != nil { - log.Printf("[Warning] - [TIMEOUT] handler error %v", err) + log.Errorf("[TIMEOUT] handler error %v", err) } ch <- struct{}{} }() diff --git a/prefork.go b/prefork.go index 86397469..a26d9314 100644 --- a/prefork.go +++ b/prefork.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "errors" "fmt" - "log" "os" "os/exec" "runtime" @@ -13,6 +12,7 @@ import ( "sync/atomic" "time" + "github.com/gofiber/fiber/v2/log" "github.com/valyala/fasthttp/reuseport" ) @@ -77,7 +77,7 @@ func (app *App) prefork(network, addr string, tlsConfig *tls.Config) error { for _, proc := range childs { if err := proc.Process.Kill(); err != nil { if !errors.Is(err, os.ErrProcessDone) { - log.Printf("prefork: failed to kill child: %v\n", err) + log.Errorf("prefork: failed to kill child: %v", err) } } }