Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔥 Feature: Add AllLogger to Config #3153

Merged
merged 26 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3689da6
:fire: Feature: Add SetFlags to Logger Interface
haochunchang Oct 5, 2024
a32b80a
:rotating_light: Test: custom-defined Logger and LoggerFunc
haochunchang Oct 5, 2024
e609bbd
📚 Doc: add LoggerFunc and Logger to middleware logger
haochunchang Oct 5, 2024
fc80e6e
:rotating_light: Test: fine-tune custom Logger and LoggerFunc
haochunchang Oct 6, 2024
3afdcac
📚 Doc: add Logger documentation
haochunchang Oct 6, 2024
4fe7adf
:adhesive_bandage: fix: add default Logger field to default config
haochunchang Oct 10, 2024
ae18274
📚 Doc: remove Logger field in middleware logger
haochunchang Oct 14, 2024
ddb55f6
:rotating_light: Test: add tests for using fiber logger interface wra…
haochunchang Oct 14, 2024
eb093d5
📚 Doc: update custom logger example
haochunchang Nov 24, 2024
a88e612
Update docs/middleware/logger.md
ReneWerner87 Nov 29, 2024
8454f0c
update
efectn Nov 29, 2024
86c837d
update logger docs
efectn Nov 29, 2024
c872918
Merge branch 'main' into feature/logger-interface
efectn Nov 29, 2024
ed708f5
update what's new
efectn Nov 29, 2024
1a09cb6
replace setflags with getloggerinstance
efectn Nov 29, 2024
ef57ea3
fix linter
efectn Nov 29, 2024
2b2b5fe
update
efectn Nov 29, 2024
25f2952
Fix markdownlint issues
gaby Nov 30, 2024
6bbb4b9
apply reviews & improve coverage
efectn Dec 1, 2024
175ebf6
Merge branch 'feature/logger-interface' of https://github.com/haochun…
efectn Dec 1, 2024
c3a5a1e
fix linter
efectn Dec 1, 2024
353a2ff
Merge branch 'main' into feature/logger-interface
efectn Dec 1, 2024
d89b1cc
rename controllogger
efectn Dec 1, 2024
c99ee37
Merge branch 'feature/logger-interface' of https://github.com/haochun…
efectn Dec 1, 2024
3c27970
Update whats_new.md
ReneWerner87 Dec 1, 2024
8efa1b1
Merge branch 'main' into feature/logger-interface
ReneWerner87 Dec 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/api/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,20 @@
```

Binding the logger to a context allows you to include context-specific information in your logs, improving traceability and debugging.

## GetLoggerInstance

You can use GetLoggerInstance 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.GetLoggerInstance().(*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
```
gaby marked this conversation as resolved.
Show resolved Hide resolved

40 changes: 40 additions & 0 deletions docs/middleware/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,44 @@
}))
```

### 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"

Check failure on line 101 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:101:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md
"github.com/gofiber/fiber/v3"

Check failure on line 102 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:102:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md
"github.com/gofiber/fiber/v3/log"

Check failure on line 103 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:103:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md
"github.com/gofiber/fiber/v3/middleware/logger"

Check failure on line 104 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:104:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md
)

func main() {
// Create a new Fiber instance

Check failure on line 108 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:108:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md
app := fiber.New()

Check failure on line 109 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:109:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md

// Create a new zap logger which is compatible with Fiber AllLogger interface
zap := fiberzap.NewLogger(fiberzap.LoggerConfig{

Check failure on line 112 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:112:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md
ExtraKeys: []string{"request_id"},

Check failure on line 113 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:113:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md
})

Check failure on line 114 in docs/middleware/logger.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/middleware/logger.md:114:1 MD010/no-hard-tabs Hard tabs [Column: 1] https://github.com/DavidAnson/markdownlint/blob/v0.36.1/doc/md010.md

// 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.
:::
Expand All @@ -108,6 +146,7 @@
| 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` |
efectn marked this conversation as resolved.
Show resolved Hide resolved
| 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) | - |
Expand All @@ -125,6 +164,7 @@
TimeInterval: 500 * time.Millisecond,
Output: os.Stdout,
DisableColors: false,
LoggerFunc: defaultLoggerInstance,
}
```

Expand Down
47 changes: 47 additions & 0 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -629,6 +631,12 @@ curl "http://localhost:3000/header"

</details>

## 📃 Log

`fiber.AllLogger` interface now has a new method called `GetLoggerInstance`. 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#getloggerinstance).

## 🧬 Middlewares

### Adaptor
Expand Down Expand Up @@ -704,6 +712,45 @@ 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:

```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")
}
```

### Filesystem

We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
Expand Down
5 changes: 5 additions & 0 deletions log/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@
l.stdlog.SetOutput(writer)
}

// GetLoggerInstance returns the logger instance. It can be used to adjust the logger configurations in case of need.
func (l *defaultLogger) GetLoggerInstance() any {
efectn marked this conversation as resolved.
Show resolved Hide resolved
return l.stdlog

Check warning on line 215 in log/default.go

View check run for this annotation

Codecov / codecov/patch

log/default.go#L214-L215

Added lines #L214 - L215 were not covered by tests
}

// DefaultLogger returns the default logger.
func DefaultLogger() AllLogger {
return logger
Expand Down
10 changes: 10 additions & 0 deletions log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,25 @@ type CommonLogger interface {

// ControlLogger provides methods to config a logger.
type ControlLogger interface {
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved
// 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)

// GetLoggerInstance returns the logger instance. It can be used to adjust the logger configurations in case of need.
GetLoggerInstance() any
efectn marked this conversation as resolved.
Show resolved Hide resolved
}

// AllLogger is the combination of Logger, FormatLogger, CtxLogger and ControlLogger.
// Custom extensions can be made through AllLogger
type AllLogger interface {
CommonLogger
ControlLogger

// WithContext returns a new logger with the given context.
WithContext(ctx context.Context) CommonLogger
}

Expand Down
71 changes: 71 additions & 0 deletions middleware/logger/logger_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:depguard // Because we test logging :D
package logger

import (
Expand All @@ -6,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"os"
Expand All @@ -15,6 +17,7 @@ import (
"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"
Expand Down Expand Up @@ -181,6 +184,48 @@ func Test_Logger_ErrorTimeZone(t *testing.T) {
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
}

// go test -run Test_Logger_Fiber_Logger
func Test_Logger_Fiber_Logger(t *testing.T) {
t.Parallel()
app := fiber.New()

buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)

logger := fiberlog.DefaultLogger()
stdlogger, ok := logger.GetLoggerInstance().(*log.Logger)
require.True(t, ok)

stdlogger.SetFlags(0)
logger.SetOutput(buf)

app.Use(New(Config{
Format: "${error}",
Output: LoggerToWriter(logger, fiberlog.LevelError),
}))

app.Get("/", func(_ fiber.Ctx) error {
return errors.New("some random error")
})

resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
require.NoError(t, err)
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
require.Equal(t, "[Error] some random error\n", buf.String())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a testcase for colorized log output using LoggerToWriter?


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) {
Expand Down Expand Up @@ -733,6 +778,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, "/")
})
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved

b.Run("WithTagParameter", func(bb *testing.B) {
app := fiber.New()
app.Use(New(Config{
Expand Down Expand Up @@ -876,6 +934,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{
Expand Down
49 changes: 49 additions & 0 deletions middleware/logger/utils.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -37,3 +41,48 @@
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.LevelInfo:
efectn marked this conversation as resolved.
Show resolved Hide resolved
cl.loggerInstance.Info(utils.UnsafeString(p))
case fiberlog.LevelTrace:
cl.loggerInstance.Trace(utils.UnsafeString(p))
case fiberlog.LevelWarn:
cl.loggerInstance.Warn(utils.UnsafeString(p))
case fiberlog.LevelDebug:
cl.loggerInstance.Debug(utils.UnsafeString(p))

Check warning on line 59 in middleware/logger/utils.go

View check run for this annotation

Codecov / codecov/patch

middleware/logger/utils.go#L52-L59

Added lines #L52 - L59 were not covered by tests
case fiberlog.LevelError:
cl.loggerInstance.Error(utils.UnsafeString(p))
default:
return 0, nil

Check warning on line 63 in middleware/logger/utils.go

View check run for this annotation

Codecov / codecov/patch

middleware/logger/utils.go#L62-L63

Added lines #L62 - L63 were not covered by tests
}

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(customLogger fiberlog.AllLogger, level fiberlog.Level) io.Writer {
efectn marked this conversation as resolved.
Show resolved Hide resolved
// Check if customLogger is nil
if customLogger == 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: customLogger,
}
}
Loading