github.com/gofiber/fiber/v2@v2.47.0/middleware/logger/logger.go (about)

     1  package logger
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/gofiber/fiber/v2"
    13  	"github.com/gofiber/fiber/v2/utils"
    14  
    15  	"github.com/mattn/go-colorable"
    16  	"github.com/mattn/go-isatty"
    17  	"github.com/valyala/bytebufferpool"
    18  	"github.com/valyala/fasthttp"
    19  )
    20  
    21  // New creates a new middleware handler
    22  func New(config ...Config) fiber.Handler {
    23  	// Set default config
    24  	cfg := configDefault(config...)
    25  
    26  	// Get timezone location
    27  	tz, err := time.LoadLocation(cfg.TimeZone)
    28  	if err != nil || tz == nil {
    29  		cfg.timeZoneLocation = time.Local
    30  	} else {
    31  		cfg.timeZoneLocation = tz
    32  	}
    33  
    34  	// Check if format contains latency
    35  	cfg.enableLatency = strings.Contains(cfg.Format, "${"+TagLatency+"}")
    36  
    37  	var timestamp atomic.Value
    38  	// Create correct timeformat
    39  	timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat))
    40  
    41  	// Update date/time every 500 milliseconds in a separate go routine
    42  	if strings.Contains(cfg.Format, "${"+TagTime+"}") {
    43  		go func() {
    44  			for {
    45  				time.Sleep(cfg.TimeInterval)
    46  				timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat))
    47  			}
    48  		}()
    49  	}
    50  
    51  	// Set PID once
    52  	pid := strconv.Itoa(os.Getpid())
    53  
    54  	// Set variables
    55  	var (
    56  		once       sync.Once
    57  		mu         sync.Mutex
    58  		errHandler fiber.ErrorHandler
    59  
    60  		dataPool = sync.Pool{New: func() interface{} { return new(Data) }}
    61  	)
    62  
    63  	// If colors are enabled, check terminal compatibility
    64  	if cfg.enableColors {
    65  		cfg.Output = colorable.NewColorableStdout()
    66  		if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
    67  			cfg.Output = colorable.NewNonColorable(os.Stdout)
    68  		}
    69  	}
    70  
    71  	errPadding := 15
    72  	errPaddingStr := strconv.Itoa(errPadding)
    73  
    74  	// instead of analyzing the template inside(handler) each time, this is done once before
    75  	// and we create several slices of the same length with the functions to be executed and fixed parts.
    76  	templateChain, logFunChain, err := buildLogFuncChain(&cfg, createTagMap(&cfg))
    77  	if err != nil {
    78  		panic(err)
    79  	}
    80  
    81  	// Return new handler
    82  	return func(c *fiber.Ctx) error {
    83  		// Don't execute middleware if Next returns true
    84  		if cfg.Next != nil && cfg.Next(c) {
    85  			return c.Next()
    86  		}
    87  
    88  		// Alias colors
    89  		colors := c.App().Config().ColorScheme
    90  
    91  		// Set error handler once
    92  		once.Do(func() {
    93  			// get longested possible path
    94  			stack := c.App().Stack()
    95  			for m := range stack {
    96  				for r := range stack[m] {
    97  					if len(stack[m][r].Path) > errPadding {
    98  						errPadding = len(stack[m][r].Path)
    99  						errPaddingStr = strconv.Itoa(errPadding)
   100  					}
   101  				}
   102  			}
   103  			// override error handler
   104  			errHandler = c.App().ErrorHandler
   105  		})
   106  
   107  		// Logger data
   108  		data := dataPool.Get().(*Data) //nolint:forcetypeassert,errcheck // We store nothing else in the pool
   109  		// no need for a reset, as long as we always override everything
   110  		data.Pid = pid
   111  		data.ErrPaddingStr = errPaddingStr
   112  		data.Timestamp = timestamp
   113  		// put data back in the pool
   114  		defer dataPool.Put(data)
   115  
   116  		// Set latency start time
   117  		if cfg.enableLatency {
   118  			data.Start = time.Now()
   119  		}
   120  
   121  		// Handle request, store err for logging
   122  		chainErr := c.Next()
   123  
   124  		data.ChainErr = chainErr
   125  		// Manually call error handler
   126  		if chainErr != nil {
   127  			if err := errHandler(c, chainErr); err != nil {
   128  				_ = c.SendStatus(fiber.StatusInternalServerError) //nolint:errcheck // TODO: Explain why we ignore the error here
   129  			}
   130  		}
   131  
   132  		// Set latency stop time
   133  		if cfg.enableLatency {
   134  			data.Stop = time.Now()
   135  		}
   136  
   137  		// Get new buffer
   138  		buf := bytebufferpool.Get()
   139  
   140  		// Default output when no custom Format or io.Writer is given
   141  		if cfg.Format == ConfigDefault.Format {
   142  			// Format error if exist
   143  			formatErr := ""
   144  			if cfg.enableColors {
   145  				if chainErr != nil {
   146  					formatErr = colors.Red + " | " + chainErr.Error() + colors.Reset
   147  				}
   148  				_, _ = buf.WriteString( //nolint:errcheck // This will never fail
   149  					fmt.Sprintf("%s |%s %3d %s| %7v | %15s |%s %-7s %s| %-"+errPaddingStr+"s %s\n",
   150  						timestamp.Load().(string),
   151  						statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset,
   152  						data.Stop.Sub(data.Start).Round(time.Millisecond),
   153  						c.IP(),
   154  						methodColor(c.Method(), colors), c.Method(), colors.Reset,
   155  						c.Path(),
   156  						formatErr,
   157  					),
   158  				)
   159  			} else {
   160  				if chainErr != nil {
   161  					formatErr = " | " + chainErr.Error()
   162  				}
   163  				_, _ = buf.WriteString( //nolint:errcheck // This will never fail
   164  					fmt.Sprintf("%s | %3d | %7v | %15s | %-7s | %-"+errPaddingStr+"s %s\n",
   165  						timestamp.Load().(string),
   166  						c.Response().StatusCode(),
   167  						data.Stop.Sub(data.Start).Round(time.Millisecond),
   168  						c.IP(),
   169  						c.Method(),
   170  						c.Path(),
   171  						formatErr,
   172  					),
   173  				)
   174  			}
   175  
   176  			// Write buffer to output
   177  			_, _ = cfg.Output.Write(buf.Bytes()) //nolint:errcheck // This will never fail
   178  
   179  			if cfg.Done != nil {
   180  				cfg.Done(c, buf.Bytes())
   181  			}
   182  
   183  			// Put buffer back to pool
   184  			bytebufferpool.Put(buf)
   185  
   186  			// End chain
   187  			return nil
   188  		}
   189  
   190  		var err error
   191  		// Loop over template parts execute dynamic parts and add fixed parts to the buffer
   192  		for i, logFunc := range logFunChain {
   193  			if logFunc == nil {
   194  				_, _ = buf.Write(templateChain[i]) //nolint:errcheck // This will never fail
   195  			} else if templateChain[i] == nil {
   196  				_, err = logFunc(buf, c, data, "")
   197  			} else {
   198  				_, err = logFunc(buf, c, data, utils.UnsafeString(templateChain[i]))
   199  			}
   200  			if err != nil {
   201  				break
   202  			}
   203  		}
   204  
   205  		// Also write errors to the buffer
   206  		if err != nil {
   207  			_, _ = buf.WriteString(err.Error()) //nolint:errcheck // This will never fail
   208  		}
   209  		mu.Lock()
   210  		// Write buffer to output
   211  		if _, err := cfg.Output.Write(buf.Bytes()); err != nil {
   212  			// Write error to output
   213  			if _, err := cfg.Output.Write([]byte(err.Error())); err != nil {
   214  				// There is something wrong with the given io.Writer
   215  				_, _ = fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
   216  			}
   217  		}
   218  		mu.Unlock()
   219  
   220  		if cfg.Done != nil {
   221  			cfg.Done(c, buf.Bytes())
   222  		}
   223  
   224  		// Put buffer back to pool
   225  		bytebufferpool.Put(buf)
   226  
   227  		return nil
   228  	}
   229  }
   230  
   231  func appendInt(output Buffer, v int) (int, error) {
   232  	old := output.Len()
   233  	output.Set(fasthttp.AppendUint(output.Bytes(), v))
   234  	return output.Len() - old, nil
   235  }