1
0
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:
Hao Chun Chang 2024-12-01 20:32:52 +08:00 committed by GitHub
parent f31ec351c7
commit 67021360e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 299 additions and 5 deletions

View File

@ -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
```

View File

@ -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,
}
```

View File

@ -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.

View File

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

View File

@ -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()

View File

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

View File

@ -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{

View File

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