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

🔥 feat: Add Filter option to logger middleware #3333

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8a74eba
🔥 Feature(logger): Add Filter option to logger middleware
JIeJaitt Feb 27, 2025
0a70ee8
📚 Doc(logger): Clarify Filter middleware description
JIeJaitt Feb 27, 2025
efdd738
🚨 Test(logger): Enhance logger filter test with parallel subtests
JIeJaitt Feb 27, 2025
a652e62
🔒 Test(logger): Add mutex to prevent race conditions in logger test
JIeJaitt Feb 27, 2025
c1f2278
🔥 Feature(logger): Add Filter option to logger middleware
JIeJaitt Feb 27, 2025
2794761
📚 Doc(logger): Clarify Filter middleware description
JIeJaitt Feb 27, 2025
2fd940e
🚨 Test(logger): Enhance logger filter test with parallel subtests
JIeJaitt Feb 27, 2025
7dcc904
🔒 Test(logger): Add mutex to prevent race conditions in logger test
JIeJaitt Feb 27, 2025
d6e64a0
🚨 Test(logger): Refactor logger test to improve test isolation
JIeJaitt Feb 27, 2025
4cb5e1c
Merge branch 'jiejaitt-feature/filter-logger' of github.com:JIeJaitt/…
JIeJaitt Feb 27, 2025
93b57a9
Fix issue with unit-tests
gaby Feb 27, 2025
572233e
Merge branch 'main' into jiejaitt-feature/filter-logger
gaby Feb 27, 2025
b600183
Update middleware/logger/logger_test.go
gaby Feb 27, 2025
2154baf
Apply logger filter as soon as possible
gaby Mar 1, 2025
10e6cb9
Merge branch 'main' into jiejaitt-feature/filter-logger
gaby Mar 1, 2025
6528b9f
📚 Doc: Add logger filter configuration example to whats_new.md
JIeJaitt Mar 3, 2025
75976af
Merge branch 'main' into jiejaitt-feature/filter-logger
JIeJaitt Mar 3, 2025
288edb2
📚 Doc: Update logger filter documentation in whats_new.md
JIeJaitt Mar 3, 2025
e2295e5
📚 Doc: Update logger filter documentation and examples
JIeJaitt Mar 4, 2025
0ac13c8
🩹 Fix: improve what_new.md
JIeJaitt Mar 4, 2025
bb05227
Merge branch 'main' into jiejaitt-feature/filter-logger
JIeJaitt Mar 4, 2025
d3030b1
Merge branch 'main' into jiejaitt-feature/filter-logger
JIeJaitt Mar 6, 2025
82e27f3
Merge branch 'main' into jiejaitt-feature/filter-logger
JIeJaitt Mar 6, 2025
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
2 changes: 2 additions & 0 deletions docs/middleware/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Writing to os.File is goroutine-safe, but if you are using a custom Output that
| Property | Type | Description | Default |
|:-----------------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------|
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
| Filter | `func(fiber.Ctx) bool` | Filter is a function that is called before the log string for a request is written to Output. | `nil` |
| Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Output, and pass the log string as parameter. | `nil` |
| CustomTags | `map[string]LogFunc` | tagFunctions defines the custom tag action. | `map[string]LogFunc` |
| Format | `string` | Format defines the logging tags. | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` |
Expand All @@ -157,6 +158,7 @@ Writing to os.File is goroutine-safe, but if you are using a custom Output that
```go
var ConfigDefault = Config{
Next: nil,
Filter nil,
Done: nil,
Format: "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n",
TimeFormat: "15:04:05",
Expand Down
23 changes: 23 additions & 0 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,29 @@ func main() {

</details>

The `Filter` is a function that is called before the log string for a request is written to Output. If it returns true, the log will be written; otherwise, it will be skipped.

<details>
<summary>Example</summary>

```go
app.Use(logger.New(logger.Config{
Filter: func(c fiber.Ctx) bool {
// Skip logging for 404 requests
return c.Response().StatusCode() == fiber.StatusNotFound
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be != ?

If its 404 it returns True, which the defaultLogger negates into False and doesn't filter

Copy link
Member

Choose a reason for hiding this comment

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

well, if he only wants to log every 404, then that should be fine
depends on what he wants to do

it is always logged if the condition is true
-> if of course he wants everything else to be logged except 404, then an unequal should be used

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ReneWerner87 I've added new examples in the documentation, you can take a look at the

app.Use(logger.New(logger.Config{
    Filter: func(c fiber.Ctx) bool {
        // log status code >= 400
        return c.Response().StatusCode() >= fiber.StatusBadRequest
    },
}))

app.Use(logger.New(logger.Config{
    Filter: func(c fiber.Ctx) bool {
        // log status code == 404
        return c.Response().StatusCode() == fiber.StatusNotFound
    },
}))

app.Use(logger.New(logger.Config{
    Filter: func(c fiber.Ctx) bool {
        // log status code != 200
        return c.Response().StatusCode() != fiber.StatusOK
    },
}))

},
}))

app.Use(logger.New(logger.Config{
Filter: func(c fiber.Ctx) bool {
// Only log requests with status code 200
return c.Response().StatusCode() == fiber.StatusOK
},
}))
```

</details>

### Filesystem

We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
Expand Down
10 changes: 10 additions & 0 deletions middleware/logger/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ type Config struct {
// Optional. Default: nil
Next func(c fiber.Ctx) bool

// Filter is a function that is called before writing the log string.
// If it returns true, the log will be written; otherwise, it will be skipped.
//
// Optional. Default: nil
Filter func(c fiber.Ctx) bool

// Done is a function that is called after the log string for a request is written to Output,
// and pass the log string as parameter.
//
Expand Down Expand Up @@ -98,6 +104,7 @@ type LogFunc func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (in
// ConfigDefault is the default config
var ConfigDefault = Config{
Next: nil,
Filter: nil,
Done: nil,
Format: defaultFormat,
TimeFormat: "15:04:05",
Expand Down Expand Up @@ -126,6 +133,9 @@ func configDefault(config ...Config) Config {
if cfg.Next == nil {
cfg.Next = ConfigDefault.Next
}
if cfg.Filter == nil {
cfg.Filter = ConfigDefault.Filter
}
if cfg.Done == nil {
cfg.Done = ConfigDefault.Done
}
Expand Down
5 changes: 5 additions & 0 deletions middleware/logger/default_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import (

// default logger for fiber
func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
// Check if Filter is defined and call it
if cfg.Filter != nil && !cfg.Filter(c) {
return nil // Skip logging if Filter returns false
}

// Alias colors
colors := c.App().Config().ColorScheme

Expand Down
52 changes: 52 additions & 0 deletions middleware/logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,58 @@ func Test_Logger_Done(t *testing.T) {
require.Positive(t, buf.Len(), 0)
}

func Test_Logger_Filter(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused about how this is working.

If the condition is True, it will become False in defaultLogger. But we dont check for that in the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gaby Thanks for the heads up. I've just checked and found a few things that need to be refined, it's almost midnight here, I'll submit a more complete unit test and documentation note tomorrow.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good. Thanks!

t.Parallel()

t.Run("Test Not Found", func(t *testing.T) {
t.Parallel()
app := fiber.New()

logOutput := bytes.Buffer{}

// Create a single logging middleware with both filter and output capture
app.Use(New(Config{
Filter: func(c fiber.Ctx) bool {
return c.Response().StatusCode() == fiber.StatusNotFound
},
Output: &logOutput,
}))

resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/nonexistent", nil))
require.NoError(t, err)
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)

// Verify the log output contains the "404" message
require.Contains(t, logOutput.String(), "404")
})

t.Run("Test OK", func(t *testing.T) {
t.Parallel()
app := fiber.New()

logOutput := bytes.Buffer{}

// Create a single logging middleware with both filter and output capture
app.Use(New(Config{
Filter: func(c fiber.Ctx) bool {
return c.Response().StatusCode() == fiber.StatusNotFound
},
Output: &logOutput,
}))

app.Get("/", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
})

resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode)

// Verify the log output does not contain the "200" message
require.NotContains(t, logOutput.String(), "200")
})
}

// go test -run Test_Logger_ErrorTimeZone
func Test_Logger_ErrorTimeZone(t *testing.T) {
t.Parallel()
Expand Down
Loading