mirror of
https://github.com/gofiber/fiber.git
synced 2025-05-02 22:00:16 +00:00
* feat(logger): Add predefined log formats This commit introduces predefined log formats for the logger middleware, enhancing its flexibility and ease of use. Users can now specify formats like "common", "combined", and "json" in addition to the default format. Changes: - Added a `format.go` file to store predefined log format constants. - Updated `config.go` to include documentation for the `Format` configuration option, explaining the available placeholders and predefined formats. - Modified `logger.go` to utilize the predefined formats based on the `Format` configuration. - Added a new test case `Test_Logger_CLF` in `logger_test.go` to verify the "common" log format. * feat(logger): Use predefined formats and fix default format This commit updates the logger middleware to utilize the predefined log formats introduced in a previous commit. It also fixes the default format to use the `FormatDefault` constant. Changes: - Updated `config.go` to use `FormatDefault` constant for the default format. - Updated `default_logger.go` to use `FormatDefault` constant for the default format. - Added new test cases in `logger_test.go` to verify the "common", "combined" and "json" log formats. - Updated `format.go` to add newline character to the end of the default format. * feat(logger): Document and exemplify predefined formats * fix(logger): Improve test assertions based on golangci-lint * docs(logger): Improve documentation and formatting logger.md based on markdownlint-cli2 * docs(logger): Improve documentation based on markdownlint-cli2 * fix(logger): Improve combined and JSON format tests * feat(logger): Add ECS log format * feat(logger): Add CustomFormat option This commit introduces a `CustomFormat` option to the `Config` struct, allowing users to specify a predefined format (like "common", "combined", "json", or "ecs") * feat(logger): Add ECS log format to examples and config * docs(logger): Update examples in whats_new.md * feat(logger): Remove CustomFormat option and renamed Format consts - Removed `CustomFormat` field from `Config`. - Removed `LoggerConfig` map. - Rename predefined formats constants. * docs(logger): Update documentation and examples after format refactor --------- Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
173 lines
4.5 KiB
Go
173 lines
4.5 KiB
Go
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/gofiber/utils/v2"
|
|
"github.com/mattn/go-colorable"
|
|
"github.com/mattn/go-isatty"
|
|
"github.com/valyala/bytebufferpool"
|
|
)
|
|
|
|
// default logger for fiber
|
|
func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|
// Check if Skip is defined and call it.
|
|
// Now, if Skip(c) == true, we SKIP logging:
|
|
if cfg.Skip != nil && cfg.Skip(c) {
|
|
return nil // Skip logging if Skip returns true
|
|
}
|
|
|
|
// Alias colors
|
|
colors := c.App().Config().ColorScheme
|
|
|
|
// Get new buffer
|
|
buf := bytebufferpool.Get()
|
|
|
|
// Default output when no custom Format or io.Writer is given
|
|
if cfg.Format == DefaultFormat {
|
|
// Format error if exist
|
|
formatErr := ""
|
|
if cfg.enableColors {
|
|
if data.ChainErr != nil {
|
|
formatErr = colors.Red + " | " + data.ChainErr.Error() + colors.Reset
|
|
}
|
|
buf.WriteString(
|
|
fmt.Sprintf("%s |%s %3d %s| %13v | %15s |%s %-7s %s| %-"+data.ErrPaddingStr+"s %s\n",
|
|
data.Timestamp.Load().(string), //nolint:forcetypeassert,errcheck // Timestamp is always a string
|
|
statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset,
|
|
data.Stop.Sub(data.Start),
|
|
c.IP(),
|
|
methodColor(c.Method(), colors), c.Method(), colors.Reset,
|
|
c.Path(),
|
|
formatErr,
|
|
),
|
|
)
|
|
} else {
|
|
if data.ChainErr != nil {
|
|
formatErr = " | " + data.ChainErr.Error()
|
|
}
|
|
|
|
// Helper function to append fixed-width string with padding
|
|
fixedWidth := func(s string, width int, rightAlign bool) {
|
|
if rightAlign {
|
|
for i := len(s); i < width; i++ {
|
|
buf.WriteByte(' ')
|
|
}
|
|
buf.WriteString(s)
|
|
} else {
|
|
buf.WriteString(s)
|
|
for i := len(s); i < width; i++ {
|
|
buf.WriteByte(' ')
|
|
}
|
|
}
|
|
}
|
|
|
|
// Timestamp
|
|
buf.WriteString(data.Timestamp.Load().(string)) //nolint:forcetypeassert,errcheck // Timestamp is always a string
|
|
buf.WriteString(" | ")
|
|
|
|
// Status Code with 3 fixed width, right aligned
|
|
fixedWidth(strconv.Itoa(c.Response().StatusCode()), 3, true)
|
|
buf.WriteString(" | ")
|
|
|
|
// Duration with 13 fixed width, right aligned
|
|
fixedWidth(data.Stop.Sub(data.Start).String(), 13, true)
|
|
buf.WriteString(" | ")
|
|
|
|
// Client IP with 15 fixed width, right aligned
|
|
fixedWidth(c.IP(), 15, true)
|
|
buf.WriteString(" | ")
|
|
|
|
// HTTP Method with 7 fixed width, left aligned
|
|
fixedWidth(c.Method(), 7, false)
|
|
buf.WriteString(" | ")
|
|
|
|
// Path with dynamic padding for error message, left aligned
|
|
errPadding, _ := strconv.Atoi(data.ErrPaddingStr) //nolint:errcheck // It is fine to ignore the error
|
|
fixedWidth(c.Path(), errPadding, false)
|
|
|
|
// Error message
|
|
buf.WriteString(" ")
|
|
buf.WriteString(formatErr)
|
|
buf.WriteString("\n")
|
|
}
|
|
|
|
// Write buffer to output
|
|
writeLog(cfg.Stream, buf.Bytes())
|
|
|
|
if cfg.Done != nil {
|
|
cfg.Done(c, buf.Bytes())
|
|
}
|
|
|
|
// Put buffer back to pool
|
|
bytebufferpool.Put(buf)
|
|
|
|
// End chain
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
// Loop over template parts execute dynamic parts and add fixed parts to the buffer
|
|
for i, logFunc := range data.LogFuncChain {
|
|
switch {
|
|
case logFunc == nil:
|
|
buf.Write(data.TemplateChain[i])
|
|
case data.TemplateChain[i] == nil:
|
|
_, err = logFunc(buf, c, data, "")
|
|
default:
|
|
_, err = logFunc(buf, c, data, utils.UnsafeString(data.TemplateChain[i]))
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Also write errors to the buffer
|
|
if err != nil {
|
|
buf.WriteString(err.Error())
|
|
}
|
|
|
|
writeLog(cfg.Stream, buf.Bytes())
|
|
|
|
if cfg.Done != nil {
|
|
cfg.Done(c, buf.Bytes())
|
|
}
|
|
|
|
// Put buffer back to pool
|
|
bytebufferpool.Put(buf)
|
|
|
|
return nil
|
|
}
|
|
|
|
// run something before returning the handler
|
|
func beforeHandlerFunc(cfg Config) {
|
|
// If colors are enabled, check terminal compatibility
|
|
if cfg.enableColors {
|
|
cfg.Stream = colorable.NewColorableStdout()
|
|
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
|
|
cfg.Stream = colorable.NewNonColorable(os.Stdout)
|
|
}
|
|
}
|
|
}
|
|
|
|
func appendInt(output Buffer, v int) (int, error) {
|
|
old := output.Len()
|
|
output.Set(strconv.AppendInt(output.Bytes(), int64(v), 10))
|
|
return output.Len() - old, nil
|
|
}
|
|
|
|
// writeLog writes a msg to w, printing a warning to stderr if the log fails.
|
|
func writeLog(w io.Writer, msg []byte) {
|
|
if _, err := w.Write(msg); err != nil {
|
|
// Write error to output
|
|
if _, err := w.Write([]byte(err.Error())); err != nil {
|
|
// There is something wrong with the given io.Writer
|
|
_, _ = fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
}
|
|
}
|
|
}
|