mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-06 11:02:01 +00:00
🔥 Feature: Add AllLogger to Config (#3153)
* 🔥 Feature: Add SetFlags to Logger Interface 🔥 Feature: Add fiberlog Logger field to config * 🚨 Test: custom-defined Logger and LoggerFunc * 📚 Doc: add LoggerFunc and Logger to middleware logger * 🚨 Test: fine-tune custom Logger and LoggerFunc * 📚 Doc: add Logger documentation 📚 Doc: add custom Logger example * 🩹 fix: add default Logger field to default config * 📚 Doc: remove Logger field in middleware logger 📚 Doc: add example of using fiber logger interface * 🚨 Test: add tests for using fiber logger interface wrapper * 📚 Doc: update custom logger example * Update docs/middleware/logger.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * update * update logger docs * update what's new * replace setflags with getloggerinstance * fix linter * update * Fix markdownlint issues * apply reviews & improve coverage * fix linter * rename controllogger * Update whats_new.md expandable example --------- Co-authored-by: RW <rene@gofiber.io> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Muhammed Efe Cetin <efectn@protonmail.com> Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
This commit is contained in:
parent
f31ec351c7
commit
67021360e1
@ -36,7 +36,7 @@ type CommonLogger interface {
|
||||
|
||||
type AllLogger interface {
|
||||
CommonLogger
|
||||
ControlLogger
|
||||
ConfigurableLogger
|
||||
WithLogger
|
||||
}
|
||||
```
|
||||
@ -186,3 +186,19 @@ commonLogger.Info("info")
|
||||
```
|
||||
|
||||
Binding the logger to a context allows you to include context-specific information in your logs, improving traceability and debugging.
|
||||
|
||||
## Logger
|
||||
|
||||
You can use Logger to retrieve the logger instance. It is useful when you need to access underlying methods of the logger.
|
||||
To retrieve the Logger instance, use the following method:
|
||||
|
||||
```go
|
||||
logger := fiberlog.DefaultLogger() // Call DefaultLogger to get the default logger instance
|
||||
|
||||
stdlogger, ok := logger.Logger().(*log.Logger) // Get the logger instance and assert it to *log.Logger
|
||||
if !ok {
|
||||
panic("logger is not *log.Logger")
|
||||
}
|
||||
|
||||
stdlogger.SetFlags(0) // Hide timestamp by setting flags to 0
|
||||
```
|
||||
|
@ -90,6 +90,44 @@ app.Use(logger.New(logger.Config{
|
||||
}))
|
||||
```
|
||||
|
||||
### Use Logger Middleware with Other Loggers
|
||||
|
||||
In order to use Fiber logger middleware with other loggers such as zerolog, zap, logrus; you can use `LoggerToWriter` helper which converts Fiber logger to a writer, which is compatible with the middleware.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/contrib/fiberzap/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/log"
|
||||
"github.com/gofiber/fiber/v3/middleware/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a new Fiber instance
|
||||
app := fiber.New()
|
||||
|
||||
// Create a new zap logger which is compatible with Fiber AllLogger interface
|
||||
zap := fiberzap.NewLogger(fiberzap.LoggerConfig{
|
||||
ExtraKeys: []string{"request_id"},
|
||||
})
|
||||
|
||||
// Use the logger middleware with zerolog logger
|
||||
app.Use(logger.New(logger.Config{
|
||||
Output: logger.LoggerToWriter(zap, log.LevelDebug),
|
||||
}))
|
||||
|
||||
// Define a route
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
|
||||
// Start server on http://localhost:3000
|
||||
app.Listen(":3000")
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
Writing to os.File is goroutine-safe, but if you are using a custom Output that is not goroutine-safe, make sure to implement locking to properly serialize writes.
|
||||
:::
|
||||
@ -108,6 +146,7 @@ Writing to os.File is goroutine-safe, but if you are using a custom Output that
|
||||
| TimeZone | `string` | TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc | `"Local"` |
|
||||
| TimeInterval | `time.Duration` | TimeInterval is the delay before the timestamp is updated. | `500 * time.Millisecond` |
|
||||
| Output | `io.Writer` | Output is a writer where logs are written. | `os.Stdout` |
|
||||
| LoggerFunc | `func(c fiber.Ctx, data *Data, cfg Config) error` | Custom logger function for integration with logging libraries (Zerolog, Zap, Logrus, etc). Defaults to Fiber's default logger if not defined. | `see default_logger.go defaultLoggerInstance` |
|
||||
| DisableColors | `bool` | DisableColors defines if the logs output should be colorized. | `false` |
|
||||
| enableColors | `bool` | Internal field for enabling colors in the log output. (This is not a user-configurable field) | - |
|
||||
| enableLatency | `bool` | Internal field for enabling latency measurement in logs. (This is not a user-configurable field) | - |
|
||||
@ -125,6 +164,7 @@ var ConfigDefault = Config{
|
||||
TimeInterval: 500 * time.Millisecond,
|
||||
Output: os.Stdout,
|
||||
DisableColors: false,
|
||||
LoggerFunc: defaultLoggerInstance,
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -22,10 +22,12 @@ Here's a quick overview of the changes in Fiber `v3`:
|
||||
- [🔄️ Redirect](#-redirect)
|
||||
- [🌎 Client package](#-client-package)
|
||||
- [🧰 Generic functions](#-generic-functions)
|
||||
- [📃 Log](#-log)
|
||||
- [🧬 Middlewares](#-middlewares)
|
||||
- [CORS](#cors)
|
||||
- [CSRF](#csrf)
|
||||
- [Session](#session)
|
||||
- [Logger](#logger)
|
||||
- [Filesystem](#filesystem)
|
||||
- [Monitor](#monitor)
|
||||
- [Healthcheck](#healthcheck)
|
||||
@ -630,6 +632,12 @@ curl "http://localhost:3000/header"
|
||||
|
||||
</details>
|
||||
|
||||
## 📃 Log
|
||||
|
||||
`fiber.AllLogger` interface now has a new method called `Logger`. This method can be used to get the underlying logger instance from the Fiber logger middleware. This is useful when you want to configure the logger middleware with a custom logger and still want to access the underlying logger instance.
|
||||
|
||||
You can find more details about this feature in [/docs/api/log.md](./api/log.md#logger).
|
||||
|
||||
## 🧬 Middlewares
|
||||
|
||||
### Adaptor
|
||||
@ -705,6 +713,49 @@ The Session middleware has undergone key changes in v3 to improve functionality
|
||||
|
||||
For more details on these changes and migration instructions, check the [Session Middleware Migration Guide](./middleware/session.md#migration-guide).
|
||||
|
||||
### Logger
|
||||
|
||||
New helper function called `LoggerToWriter` has been added to the logger middleware. This function allows you to use 3rd party loggers such as `logrus` or `zap` with the Fiber logger middleware without any extra afford. For example, you can use `zap` with Fiber logger middleware like this:
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/contrib/fiberzap/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/log"
|
||||
"github.com/gofiber/fiber/v3/middleware/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a new Fiber instance
|
||||
app := fiber.New()
|
||||
|
||||
// Create a new zap logger which is compatible with Fiber AllLogger interface
|
||||
zap := fiberzap.NewLogger(fiberzap.LoggerConfig{
|
||||
ExtraKeys: []string{"request_id"},
|
||||
})
|
||||
|
||||
// Use the logger middleware with zerolog logger
|
||||
app.Use(logger.New(logger.Config{
|
||||
Output: logger.LoggerToWriter(zap, log.LevelDebug),
|
||||
}))
|
||||
|
||||
// Define a route
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
|
||||
// Start server on http://localhost:3000
|
||||
app.Listen(":3000")
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Filesystem
|
||||
|
||||
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
||||
|
@ -210,6 +210,11 @@ func (l *defaultLogger) SetOutput(writer io.Writer) {
|
||||
l.stdlog.SetOutput(writer)
|
||||
}
|
||||
|
||||
// Logger returns the logger instance. It can be used to adjust the logger configurations in case of need.
|
||||
func (l *defaultLogger) Logger() any {
|
||||
return l.stdlog
|
||||
}
|
||||
|
||||
// DefaultLogger returns the default logger.
|
||||
func DefaultLogger() AllLogger {
|
||||
return logger
|
||||
|
@ -221,6 +221,22 @@ func Test_SetLevel(t *testing.T) {
|
||||
require.Equal(t, "[?8] ", setLogger.level.toString())
|
||||
}
|
||||
|
||||
func Test_Logger(t *testing.T) {
|
||||
underlyingLogger := log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds)
|
||||
setLogger := &defaultLogger{
|
||||
stdlog: underlyingLogger,
|
||||
depth: 4,
|
||||
}
|
||||
|
||||
require.Equal(t, underlyingLogger, setLogger.Logger())
|
||||
|
||||
logger, ok := setLogger.Logger().(*log.Logger)
|
||||
require.True(t, ok)
|
||||
|
||||
logger.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
|
||||
require.Equal(t, log.LstdFlags|log.Lshortfile|log.Lmicroseconds, setLogger.stdlog.Flags())
|
||||
}
|
||||
|
||||
func Test_Debugw(t *testing.T) {
|
||||
initDefaultLogger()
|
||||
|
||||
|
18
log/log.go
18
log/log.go
@ -52,17 +52,27 @@ type CommonLogger interface {
|
||||
WithLogger
|
||||
}
|
||||
|
||||
// ControlLogger provides methods to config a logger.
|
||||
type ControlLogger interface {
|
||||
// ConfigurableLogger provides methods to config a logger.
|
||||
type ConfigurableLogger interface {
|
||||
// SetLevel sets logging level.
|
||||
//
|
||||
// Available levels: Trace, Debug, Info, Warn, Error, Fatal, Panic.
|
||||
SetLevel(level Level)
|
||||
|
||||
// SetOutput sets the logger output.
|
||||
SetOutput(w io.Writer)
|
||||
|
||||
// Logger returns the logger instance. It can be used to adjust the logger configurations in case of need.
|
||||
Logger() any
|
||||
}
|
||||
|
||||
// AllLogger is the combination of Logger, FormatLogger, CtxLogger and ControlLogger.
|
||||
// AllLogger is the combination of Logger, FormatLogger, CtxLogger and ConfigurableLogger.
|
||||
// Custom extensions can be made through AllLogger
|
||||
type AllLogger interface {
|
||||
CommonLogger
|
||||
ControlLogger
|
||||
ConfigurableLogger
|
||||
|
||||
// WithContext returns a new logger with the given context.
|
||||
WithContext(ctx context.Context) CommonLogger
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
//nolint:depguard // Because we test logging :D
|
||||
package logger
|
||||
|
||||
import (
|
||||
@ -6,15 +7,18 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
fiberlog "github.com/gofiber/fiber/v3/log"
|
||||
"github.com/gofiber/fiber/v3/middleware/requestid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/valyala/bytebufferpool"
|
||||
@ -181,6 +185,83 @@ func Test_Logger_ErrorTimeZone(t *testing.T) {
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_Fiber_Logger
|
||||
func Test_Logger_LoggerToWriter(t *testing.T) {
|
||||
app := fiber.New()
|
||||
|
||||
buf := bytebufferpool.Get()
|
||||
t.Cleanup(func() {
|
||||
bytebufferpool.Put(buf)
|
||||
})
|
||||
|
||||
logger := fiberlog.DefaultLogger()
|
||||
stdlogger, ok := logger.Logger().(*log.Logger)
|
||||
require.True(t, ok)
|
||||
|
||||
stdlogger.SetFlags(0)
|
||||
logger.SetOutput(buf)
|
||||
|
||||
testCases := []struct {
|
||||
levelStr string
|
||||
level fiberlog.Level
|
||||
}{
|
||||
{
|
||||
level: fiberlog.LevelTrace,
|
||||
levelStr: "Trace",
|
||||
},
|
||||
{
|
||||
level: fiberlog.LevelDebug,
|
||||
levelStr: "Debug",
|
||||
},
|
||||
{
|
||||
level: fiberlog.LevelInfo,
|
||||
levelStr: "Info",
|
||||
},
|
||||
{
|
||||
level: fiberlog.LevelWarn,
|
||||
levelStr: "Warn",
|
||||
},
|
||||
{
|
||||
level: fiberlog.LevelError,
|
||||
levelStr: "Error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
level := strconv.Itoa(int(tc.level))
|
||||
t.Run(level, func(t *testing.T) {
|
||||
buf.Reset()
|
||||
|
||||
app.Use("/"+level, New(Config{
|
||||
Format: "${error}",
|
||||
Output: LoggerToWriter(logger, tc.
|
||||
level),
|
||||
}))
|
||||
|
||||
app.Get("/"+level, func(_ fiber.Ctx) error {
|
||||
return errors.New("some random error")
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/"+level, nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
|
||||
require.Equal(t, "["+tc.levelStr+"] some random error\n", buf.String())
|
||||
})
|
||||
|
||||
require.Panics(t, func() {
|
||||
LoggerToWriter(logger, fiberlog.LevelPanic)
|
||||
})
|
||||
|
||||
require.Panics(t, func() {
|
||||
LoggerToWriter(logger, fiberlog.LevelFatal)
|
||||
})
|
||||
|
||||
require.Panics(t, func() {
|
||||
LoggerToWriter(nil, fiberlog.LevelFatal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeErrorOutput int
|
||||
|
||||
func (o *fakeErrorOutput) Write([]byte) (int, error) {
|
||||
@ -733,6 +814,19 @@ func Benchmark_Logger(b *testing.B) {
|
||||
benchmarkSetup(bb, app, "/")
|
||||
})
|
||||
|
||||
b.Run("DefaultFormatWithFiberLog", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
logger := fiberlog.DefaultLogger()
|
||||
logger.SetOutput(io.Discard)
|
||||
app.Use(New(Config{
|
||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
benchmarkSetup(bb, app, "/")
|
||||
})
|
||||
|
||||
b.Run("WithTagParameter", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
@ -876,6 +970,19 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||
benchmarkSetupParallel(bb, app, "/")
|
||||
})
|
||||
|
||||
b.Run("DefaultFormatWithFiberLog", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
logger := fiberlog.DefaultLogger()
|
||||
logger.SetOutput(io.Discard)
|
||||
app.Use(New(Config{
|
||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
benchmarkSetupParallel(bb, app, "/")
|
||||
})
|
||||
|
||||
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
|
@ -1,7 +1,11 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
fiberlog "github.com/gofiber/fiber/v3/log"
|
||||
"github.com/gofiber/utils/v2"
|
||||
)
|
||||
|
||||
func methodColor(method string, colors fiber.Colors) string {
|
||||
@ -37,3 +41,48 @@ func statusColor(code int, colors fiber.Colors) string {
|
||||
return colors.Red
|
||||
}
|
||||
}
|
||||
|
||||
type customLoggerWriter struct {
|
||||
loggerInstance fiberlog.AllLogger
|
||||
level fiberlog.Level
|
||||
}
|
||||
|
||||
func (cl *customLoggerWriter) Write(p []byte) (int, error) {
|
||||
switch cl.level {
|
||||
case fiberlog.LevelTrace:
|
||||
cl.loggerInstance.Trace(utils.UnsafeString(p))
|
||||
case fiberlog.LevelDebug:
|
||||
cl.loggerInstance.Debug(utils.UnsafeString(p))
|
||||
case fiberlog.LevelInfo:
|
||||
cl.loggerInstance.Info(utils.UnsafeString(p))
|
||||
case fiberlog.LevelWarn:
|
||||
cl.loggerInstance.Warn(utils.UnsafeString(p))
|
||||
case fiberlog.LevelError:
|
||||
cl.loggerInstance.Error(utils.UnsafeString(p))
|
||||
default:
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// LoggerToWriter is a helper function that returns an io.Writer that writes to a custom logger.
|
||||
// You can integrate 3rd party loggers such as zerolog, logrus, etc. to logger middleware using this function.
|
||||
//
|
||||
// Valid levels: fiberlog.LevelInfo, fiberlog.LevelTrace, fiberlog.LevelWarn, fiberlog.LevelDebug, fiberlog.LevelError
|
||||
func LoggerToWriter(logger fiberlog.AllLogger, level fiberlog.Level) io.Writer {
|
||||
// Check if customLogger is nil
|
||||
if logger == nil {
|
||||
fiberlog.Panic("LoggerToWriter: customLogger must not be nil")
|
||||
}
|
||||
|
||||
// Check if level is valid
|
||||
if level == fiberlog.LevelFatal || level == fiberlog.LevelPanic {
|
||||
fiberlog.Panic("LoggerToWriter: invalid level")
|
||||
}
|
||||
|
||||
return &customLoggerWriter{
|
||||
level: level,
|
||||
loggerInstance: logger,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user