Skip to content

Commit 186000e

Browse files
authored
Merge branch 'main' into fix-x-real-ip-header-handling-in-balancerforward
2 parents 2af51d2 + 40c0a52 commit 186000e

38 files changed

Lines changed: 2542 additions & 167 deletions

app_integration_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"io"
7+
"mime/multipart"
78
"net"
89
"net/http"
910
"net/http/httptest"
@@ -578,6 +579,45 @@ func Test_Integration_App_ServerErrorHandler_PreservesHelmetHeadersOnBodyLimit(t
578579
require.Equal(t, "require-corp", string(resp.Header.Peek("Cross-Origin-Embedder-Policy")))
579580
}
580581

582+
func Test_Integration_App_ServerErrorHandler_CustomMultipartBodyLimit(t *testing.T) {
583+
t.Parallel()
584+
585+
app := fiber.New(fiber.Config{
586+
BodyLimit: 128,
587+
ErrorHandler: func(c fiber.Ctx, err error) error {
588+
code := fiber.StatusInternalServerError
589+
var fiberErr *fiber.Error
590+
if errors.As(err, &fiberErr) {
591+
code = fiberErr.Code
592+
}
593+
return c.Status(code).JSON(fiber.Map{
594+
"code": code,
595+
"message": err.Error(),
596+
})
597+
},
598+
})
599+
app.Post("/", func(c fiber.Ctx) error {
600+
return c.SendString("ok")
601+
})
602+
603+
body := &bytes.Buffer{}
604+
writer := multipart.NewWriter(body)
605+
fileWriter, err := writer.CreateFormFile("file", "large.txt")
606+
require.NoError(t, err)
607+
_, err = fileWriter.Write(bytes.Repeat([]byte{'a'}, 256))
608+
require.NoError(t, err)
609+
require.NoError(t, writer.Close())
610+
611+
resp := performOversizedRequest(t, app, func(req *fasthttp.Request) {
612+
req.Header.Set(fiber.HeaderContentType, writer.FormDataContentType())
613+
req.SetBody(body.Bytes())
614+
})
615+
616+
require.Equal(t, fiber.StatusRequestEntityTooLarge, resp.StatusCode())
617+
require.Contains(t, string(resp.Header.ContentType()), fiber.MIMEApplicationJSON)
618+
require.JSONEq(t, `{"code":413,"message":"Request Entity Too Large"}`, string(resp.Body()))
619+
}
620+
581621
func Test_Integration_App_ServerErrorHandler_PreservesRequestID(t *testing.T) {
582622
const expectedRequestID = "integration-request-id"
583623

docs/api/log.md

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ type ConfigurableLogger[T any] interface {
4848
type AllLogger[T any] interface {
4949
CommonLogger
5050
ConfigurableLogger[T]
51-
WithLogger
51+
52+
// WithContext returns a new logger with the given context.
53+
WithContext(ctx any) CommonLogger
5254
}
5355
```
5456

@@ -153,11 +155,16 @@ Setting the log level allows you to control the verbosity of the logs, filtering
153155

154156
### Writing Logs to Stderr
155157

158+
`log.SetOutput(os.Stderr)` redirects the default logger to standard error.
159+
156160
```go
157-
var logger fiberlog.AllLogger[*log.Logger] = &defaultLogger{
158-
stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),
159-
depth: 4,
160-
}
161+
import (
162+
"os"
163+
164+
fiberlog "github.com/gofiber/fiber/v3/log"
165+
)
166+
167+
fiberlog.SetOutput(os.Stderr)
161168
```
162169

163170
This lets you route logs to a file, service, or any destination.
@@ -198,7 +205,125 @@ commonLogger := log.WithContext(ctx)
198205
commonLogger.Info("info")
199206
```
200207

201-
Context binding adds request-specific data for easier tracing.
208+
Context binding can render request-specific data for easier tracing. The default context format is `log.DefaultFormat` (the empty string), so `log.WithContext(ctx)` adds no fields until you configure a format with `log.SetContextTemplate` (or its `MustSetContextTemplate` panic-on-error variant).
209+
210+
`SetContextTemplate` configures Fiber's built-in default logger. Custom loggers registered with `SetLogger` keep full control over their own `WithContext` behavior and should implement equivalent enrichment themselves when needed.
211+
212+
```go
213+
import (
214+
"github.com/gofiber/fiber/v3"
215+
"github.com/gofiber/fiber/v3/log"
216+
"github.com/gofiber/fiber/v3/middleware/requestid"
217+
)
218+
219+
app.Use(requestid.New())
220+
221+
log.MustSetContextTemplate(log.ContextConfig{Format: log.RequestIDFormat})
222+
223+
app.Get("/", func(c fiber.Ctx) error {
224+
log.WithContext(c).Info("start")
225+
return c.SendString("Hello, World!")
226+
})
227+
```
228+
229+
Middleware that stores request values registers its log context tags automatically. For example, importing `requestid` makes `${requestid}` and `${request-id}` available; the actual value is filled in once `requestid.New()` runs in your handler stack. Until then the tag renders as an empty string, so a format that references `${requestid}` still compiles without `requestid.New()` in the chain.
230+
231+
Use `log.WithContext(c)` inside handlers when you want tags to read values stored by Fiber middleware. Passing `c.Context()` only exposes values propagated into the standard request context.
232+
233+
:::tip
234+
Cross-reference: the access-log integration uses the same tag names — see [middleware/logger](../middleware/logger.md#config) for the request-time format.
235+
:::
236+
237+
### Glossary
238+
239+
- **Format** — the string with `${...}` placeholders, e.g. `"[${requestid}] "`.
240+
- **Template** — the compiled, reusable form of a format produced by `SetContextTemplate`.
241+
- **Tag** — a single `${name}` (bare) or `${name:param}` (parametric) placeholder inside a format.
242+
243+
### Signatures
244+
245+
```go
246+
// Configure the active context template (or pass log.ContextConfig{} to disable).
247+
func SetContextTemplate(config ContextConfig) error
248+
func MustSetContextTemplate(config ContextConfig)
249+
250+
// Register a tag globally; the new renderer becomes available to subsequent
251+
// SetContextTemplate calls. The reserved TagContextValue ("value:") name
252+
// cannot be registered.
253+
func RegisterContextTag(tag string, fn ContextTagFunc) error
254+
func MustRegisterContextTag(tag string, fn ContextTagFunc)
255+
256+
// Bind a context to the default logger. ctx accepts fiber.Ctx,
257+
// *fasthttp.RequestCtx, context.Context, or any value exposing
258+
// Value(key any)/UserValue(key any).
259+
func WithContext(ctx any) CommonLogger
260+
261+
// Public types referenced by the API above.
262+
type ContextConfig struct {
263+
CustomTags map[string]ContextTagFunc
264+
Format string
265+
}
266+
type ContextData struct{}
267+
type ContextTagFunc = logtemplate.Func[any, ContextData]
268+
type Buffer = logtemplate.Buffer
269+
270+
// Format constants.
271+
const (
272+
DefaultFormat = ""
273+
RequestIDFormat = "[${requestid}] "
274+
KeyValueFormat = "request-id=${request-id} username=${username} api-key=${api-key} csrf-token=${csrf-token} session-id=${session-id} "
275+
TagContextValue = "value:"
276+
)
277+
```
278+
279+
### Context Formats
280+
281+
| Format Constant | Format String | Description |
282+
| :-- | :-- | :-- |
283+
| `DefaultFormat` | `""` | Disables contextual fields. |
284+
| `RequestIDFormat` | `"[${requestid}] "` | Prepends the request ID when the requestid middleware is used. |
285+
| `KeyValueFormat` | `"request-id=${request-id} username=${username} api-key=${api-key} csrf-token=${csrf-token} session-id=${session-id} "` | Prepends common middleware context values as key/value fields. Sensitive values are redacted by the registering middleware. |
286+
287+
### Context Tags
288+
289+
| Tag | Source |
290+
| :-- | :-- |
291+
| `${requestid}` / `${request-id}` | `requestid` middleware |
292+
| `${username}` | `basicauth` middleware — written **in clear text** for audit-log use cases. Avoid this tag if your usernames are PII. |
293+
| `${api-key}` | `keyauth` middleware, redacted to a 4-byte prefix |
294+
| `${csrf-token}` | `csrf` middleware, redacted to a 4-byte prefix |
295+
| `${session-id}` | `session` middleware, redacted to a 4-byte prefix |
296+
| `${value:key}` | Any bound value with `Value(key)` or `UserValue(key)` lookup methods |
297+
298+
:::caution
299+
`${value:KEY}` looks up arbitrary context values. CR, LF, NUL, and other ASCII control bytes (except tab) are replaced with spaces before they reach the log line, so attacker-controlled values cannot forge log lines via header smuggling. The lookup still writes the value verbatim apart from that scrub — strip or hash sensitive fields before storing them on the context if you do not want them in operator logs.
300+
:::
301+
302+
### Custom Context Tags
303+
304+
Register custom tags with `log.RegisterContextTag`, then reference them from a format passed to `SetContextTemplate`. The built-in `${value:key}` tag is reserved for context-value lookups and cannot be overridden.
305+
306+
```go
307+
type tenantContextKey struct{}
308+
309+
var tenantKey tenantContextKey
310+
311+
log.MustRegisterContextTag("tenant", func(output log.Buffer, ctx any, _ *log.ContextData, _ string) (int, error) {
312+
tenant, _ := fiber.ValueFromContext[string](ctx, tenantKey)
313+
return output.WriteString(tenant)
314+
})
315+
316+
log.MustSetContextTemplate(log.ContextConfig{Format: "[${tenant}] "})
317+
318+
app.Use(func(c fiber.Ctx) error {
319+
fiber.StoreInContext(c, tenantKey, "acme")
320+
return c.Next()
321+
})
322+
```
323+
324+
:::note
325+
Register tags **before** referencing them in `SetContextTemplate`. Compiling a format that references an unregistered name returns `*logtemplate.UnknownTagError` (or panics from `MustSetContextTemplate`).
326+
:::
202327

203328
## Logger
204329

docs/middleware/logger.md

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Logger middleware for [Fiber](https://github.com/gofiber/fiber) that logs HTTP r
1010

1111
```go
1212
func New(config ...Config) fiber.Handler
13+
func RegisterTag(tag string, fn LogFunc) error
14+
func MustRegisterTag(tag string, fn LogFunc)
1315
```
1416

1517
## Examples
@@ -20,6 +22,7 @@ Import the package:
2022
import (
2123
"github.com/gofiber/fiber/v3"
2224
"github.com/gofiber/fiber/v3/middleware/logger"
25+
"github.com/gofiber/fiber/v3/middleware/requestid"
2326
)
2427
```
2528

@@ -42,13 +45,7 @@ app.Use(logger.New(logger.Config{
4245
// Logging Request ID
4346
app.Use(requestid.New()) // Ensure requestid middleware is used before the logger
4447
app.Use(logger.New(logger.Config{
45-
CustomTags: map[string]logger.LogFunc{
46-
"requestid": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
47-
return output.WriteString(requestid.FromContext(c))
48-
},
49-
},
50-
// For more options, see the Config section
51-
// Use the custom tag ${requestid} as defined above.
48+
// requestid.New() registers ${requestid} automatically.
5249
Format: "${pid} ${requestid} ${status} - ${method} ${path}\n",
5350
}))
5451

@@ -117,6 +114,94 @@ app.Use(logger.New(logger.Config{
117114
}))
118115
```
119116

117+
### Auto-Registered Tags
118+
119+
Some Fiber middleware registers logger middleware tags automatically. Register the producing middleware before `logger.New()` and then use the tag in `Format`.
120+
121+
```go
122+
app.Use(requestid.New())
123+
app.Use(logger.New(logger.Config{
124+
Format: "${requestid} ${status} ${method} ${path}\n",
125+
}))
126+
```
127+
128+
The logger middleware resolves tags in this order:
129+
130+
1. Built-in logger tags, such as `${method}`, `${path}`, and `${status}`.
131+
2. Globally registered tags from Fiber middleware or `logger.RegisterTag`.
132+
3. `Config.CustomTags`, which override tags with the same name for that logger instance.
133+
134+
The following tags are registered by Fiber middleware when the middleware is initialized:
135+
136+
| Tag | Registered by | Value |
137+
| :-- | :------------ | :---- |
138+
| `${requestid}` / `${request-id}` | `requestid.New()` | Request ID stored by the requestid middleware. |
139+
| `${username}` | `basicauth.New()` | Authenticated username stored by the basicauth middleware. |
140+
| `${api-key}` | `keyauth.New()` | Redacted API key stored by the keyauth middleware. |
141+
| `${csrf-token}` | `csrf.New()` | Redacted marker when the csrf middleware stores a token. |
142+
| `${session-id}` | `session.New()` or `session.NewWithStore()` | Redacted session ID stored by the session middleware. |
143+
144+
:::note
145+
Auto-registered tags are access-log tags for `middleware/logger`. The same names are also registered for application logs in the `log` package via `logger.RegisterContextTag` — see [api/log#context-tags](../api/log.md#context-tags) for details on `log.WithContext` enrichment.
146+
:::
147+
148+
### Register Tags from Custom Middleware
149+
150+
Third-party middleware can expose logger tags with `logger.RegisterTag` or `logger.MustRegisterTag`. Use `sync.Once` so the tag is registered once even when the middleware is initialized multiple times.
151+
152+
```go
153+
package tenantmw
154+
155+
import (
156+
"sync"
157+
158+
"github.com/gofiber/fiber/v3"
159+
"github.com/gofiber/fiber/v3/middleware/logger"
160+
)
161+
162+
type tenantContextKey struct{}
163+
164+
var tenantKey tenantContextKey
165+
166+
var registerLoggerTagsOnce sync.Once
167+
168+
func New() fiber.Handler {
169+
registerLoggerTagsOnce.Do(func() {
170+
logger.MustRegisterTag("tenant", func(output logger.Buffer, c fiber.Ctx, _ *logger.Data, _ string) (int, error) {
171+
tenant, _ := fiber.ValueFromContext[string](c, tenantKey)
172+
return output.WriteString(tenant)
173+
})
174+
})
175+
176+
return func(c fiber.Ctx) error {
177+
fiber.StoreInContext(c, tenantKey, "acme")
178+
return c.Next()
179+
}
180+
}
181+
```
182+
183+
Use the registered tag in the logger format after installing the middleware:
184+
185+
```go
186+
app.Use(tenantmw.New())
187+
app.Use(logger.New(logger.Config{
188+
Format: "${tenant} ${status} ${method} ${path}\n",
189+
}))
190+
```
191+
192+
Use `Config.CustomTags` when one logger instance needs a local override without changing the global tag registration:
193+
194+
```go
195+
app.Use(logger.New(logger.Config{
196+
Format: "${tenant} ${status} ${method} ${path}\n",
197+
CustomTags: map[string]logger.LogFunc{
198+
"tenant": func(output logger.Buffer, c fiber.Ctx, _ *logger.Data, _ string) (int, error) {
199+
return output.WriteString("override")
200+
},
201+
},
202+
}))
203+
```
204+
120205
### Use Logger Middleware with Other Loggers
121206

122207
To combine the logger middleware with loggers like Zerolog, Zap, or Logrus, use the `LoggerToWriter` helper to adapt them to an `io.Writer`.
@@ -166,7 +251,7 @@ Writing to `os.File` is goroutine-safe, but custom streams may require locking t
166251
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` |
167252
| Skip | `func(fiber.Ctx) bool` | Skip is a function to determine if logging is skipped or written to Stream. | `nil` |
168253
| Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Stream, and pass the log string as parameter. | `nil` |
169-
| CustomTags | `map[string]LogFunc` | tagFunctions defines the custom tag action. | `map[string]LogFunc` |
254+
| CustomTags | `map[string]LogFunc` | Defines custom tag actions for this logger instance. These tags override built-in and globally registered tags with the same name. | `nil` |
170255
| `Format` | `string` | Defines the logging tags. See more in [Predefined Formats](#predefined-formats), or create your own using [Tags](#constants). | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` (same as `DefaultFormat`) |
171256
| TimeFormat | `string` | TimeFormat defines the time format for log timestamps. | `15:04:05` |
172257
| TimeZone | `string` | TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc | `"Local"` |
@@ -203,7 +288,7 @@ Logger provides predefined formats that you can use by name or directly by speci
203288
| `DefaultFormat` | `"[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"` | Fiber's default logger format. |
204289
| `CommonFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent}\n"` | Common Log Format (CLF) used in web server logs. |
205290
| `CombinedFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent} "${referer}" "${ua}"\n"` | CLF format plus the `referer` and `user agent` fields. |
206-
| `JSONFormat` | `"{time: ${time}, ip: ${ip}, method: ${method}, url: ${url}, status: ${status}, bytesSent: ${bytesSent}}\n"` | JSON format for structured logging. |
291+
| `JSONFormat` | `"{\"time\":\"${time}\",\"ip\":\"${ip}\",\"method\":\"${method}\",\"url\":\"${url}\",\"status\":${status},\"bytesSent\":${bytesSent}}\n"` | JSON format for structured logging. |
207292
| `ECSFormat` | `"{\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}\n"` | Elastic Common Schema (ECS) format for structured logging. |
208293

209294
:::tip

0 commit comments

Comments
 (0)