code.gitea.io/gitea@v1.22.3/services/context/access_log.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package context
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"net"
    10  	"net/http"
    11  	"strings"
    12  	"text/template"
    13  	"time"
    14  
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/setting"
    18  	"code.gitea.io/gitea/modules/web/middleware"
    19  )
    20  
    21  type routerLoggerOptions struct {
    22  	req            *http.Request
    23  	Identity       *string
    24  	Start          *time.Time
    25  	ResponseWriter http.ResponseWriter
    26  	Ctx            map[string]any
    27  	RequestID      *string
    28  }
    29  
    30  const keyOfRequestIDInTemplate = ".RequestID"
    31  
    32  // According to:
    33  // TraceId: A valid trace identifier is a 16-byte array with at least one non-zero byte
    34  // MD5 output is 16 or 32 bytes: md5-bytes is 16, md5-hex is 32
    35  // SHA1: similar, SHA1-bytes is 20, SHA1-hex is 40.
    36  // UUID is 128-bit, 32 hex chars, 36 ASCII chars with 4 dashes
    37  // So, we accept a Request ID with a maximum character length of 40
    38  const maxRequestIDByteLength = 40
    39  
    40  func parseRequestIDFromRequestHeader(req *http.Request) string {
    41  	requestID := "-"
    42  	for _, key := range setting.Log.RequestIDHeaders {
    43  		if req.Header.Get(key) != "" {
    44  			requestID = req.Header.Get(key)
    45  			break
    46  		}
    47  	}
    48  	if len(requestID) > maxRequestIDByteLength {
    49  		requestID = fmt.Sprintf("%s...", requestID[:maxRequestIDByteLength])
    50  	}
    51  	return requestID
    52  }
    53  
    54  // AccessLogger returns a middleware to log access logger
    55  func AccessLogger() func(http.Handler) http.Handler {
    56  	logger := log.GetLogger("access")
    57  	needRequestID := len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate)
    58  	logTemplate, _ := template.New("log").Parse(setting.Log.AccessLogTemplate)
    59  	return func(next http.Handler) http.Handler {
    60  		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    61  			start := time.Now()
    62  
    63  			var requestID string
    64  			if needRequestID {
    65  				requestID = parseRequestIDFromRequestHeader(req)
    66  			}
    67  
    68  			reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
    69  			if err != nil {
    70  				reqHost = req.RemoteAddr
    71  			}
    72  
    73  			next.ServeHTTP(w, req)
    74  			rw := w.(ResponseWriter)
    75  
    76  			identity := "-"
    77  			data := middleware.GetContextData(req.Context())
    78  			if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
    79  				identity = signedUser.Name
    80  			}
    81  			buf := bytes.NewBuffer([]byte{})
    82  			err = logTemplate.Execute(buf, routerLoggerOptions{
    83  				req:            req,
    84  				Identity:       &identity,
    85  				Start:          &start,
    86  				ResponseWriter: rw,
    87  				Ctx: map[string]any{
    88  					"RemoteAddr": req.RemoteAddr,
    89  					"RemoteHost": reqHost,
    90  					"Req":        req,
    91  				},
    92  				RequestID: &requestID,
    93  			})
    94  			if err != nil {
    95  				log.Error("Could not execute access logger template: %v", err.Error())
    96  			}
    97  
    98  			logger.Info("%s", buf.String())
    99  		})
   100  	}
   101  }