github.com/thanos-io/thanos@v0.32.5/pkg/logging/http.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package logging
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"sort"
    10  	"strings"
    11  
    12  	"net/http"
    13  	"time"
    14  
    15  	"github.com/go-kit/log"
    16  	"github.com/go-kit/log/level"
    17  
    18  	httputil "github.com/thanos-io/thanos/pkg/server/http"
    19  )
    20  
    21  type HTTPServerMiddleware struct {
    22  	opts   *options
    23  	logger log.Logger
    24  }
    25  
    26  func (m *HTTPServerMiddleware) preCall(name string, start time.Time, r *http.Request) {
    27  	logger := m.opts.filterLog(m.logger)
    28  	level.Debug(logger).Log("http.start_time", start.String(), "http.method", fmt.Sprintf("%s %s", r.Method, r.URL), "http.request_id", r.Header.Get("X-Request-ID"), "thanos.method_name", name, "msg", "started call")
    29  }
    30  
    31  func (m *HTTPServerMiddleware) postCall(name string, start time.Time, wrapped *httputil.ResponseWriterWithStatus, r *http.Request) {
    32  	status := wrapped.Status()
    33  	logger := log.With(m.logger, "http.method", fmt.Sprintf("%s %s", r.Method, r.URL), "http.request_id", r.Header.Get("X-Request-ID"), "http.status_code", fmt.Sprintf("%d", status),
    34  		"http.time_ms", fmt.Sprintf("%v", durationToMilliseconds(time.Since(start))), "http.remote_addr", r.RemoteAddr, "thanos.method_name", name)
    35  
    36  	logger = m.opts.filterLog(logger)
    37  	m.opts.levelFunc(logger, status).Log("msg", "finished call")
    38  }
    39  
    40  func (m *HTTPServerMiddleware) HTTPMiddleware(name string, next http.Handler) http.HandlerFunc {
    41  	return func(w http.ResponseWriter, r *http.Request) {
    42  		wrapped := httputil.WrapResponseWriterWithStatus(w)
    43  		start := time.Now()
    44  		hostPort := r.Host
    45  		if hostPort == "" {
    46  			hostPort = r.URL.Host
    47  		}
    48  
    49  		var port string
    50  		var err error
    51  		// Try to extract port if there is ':' as part of 'hostPort'.
    52  		if strings.Contains(hostPort, ":") {
    53  			_, port, err = net.SplitHostPort(hostPort)
    54  			if err != nil {
    55  				level.Error(m.logger).Log("msg", "failed to parse host port for http log decision", "err", err)
    56  				next.ServeHTTP(w, r)
    57  				return
    58  			}
    59  		}
    60  
    61  		deciderURL := r.URL.String()
    62  		if len(port) > 0 {
    63  			deciderURL = net.JoinHostPort(deciderURL, port)
    64  		}
    65  		decision := m.opts.shouldLog(deciderURL, nil)
    66  
    67  		switch decision {
    68  		case NoLogCall:
    69  			next.ServeHTTP(w, r)
    70  
    71  		case LogStartAndFinishCall:
    72  			m.preCall(name, start, r)
    73  			next.ServeHTTP(wrapped, r)
    74  			m.postCall(name, start, wrapped, r)
    75  
    76  		case LogFinishCall:
    77  			next.ServeHTTP(wrapped, r)
    78  			m.postCall(name, start, wrapped, r)
    79  		}
    80  	}
    81  }
    82  
    83  // NewHTTPServerMiddleware returns an http middleware.
    84  func NewHTTPServerMiddleware(logger log.Logger, opts ...Option) *HTTPServerMiddleware {
    85  	o := evaluateOpt(opts)
    86  	return &HTTPServerMiddleware{
    87  		logger: log.With(logger, "protocol", "http", "http.component", "server"),
    88  		opts:   o,
    89  	}
    90  }
    91  
    92  // getHTTPLoggingOption returns the logging ENUM based on logStart and logEnd values.
    93  func getHTTPLoggingOption(logStart, logEnd bool) (Decision, error) {
    94  	if !logStart && !logEnd {
    95  		return NoLogCall, nil
    96  	}
    97  	if !logStart && logEnd {
    98  		return LogFinishCall, nil
    99  	}
   100  	if logStart && logEnd {
   101  		return LogStartAndFinishCall, nil
   102  	}
   103  	return -1, fmt.Errorf("log start call is not supported")
   104  }
   105  
   106  // getLevel returns the level based logger.
   107  func getLevel(lvl string) level.Option {
   108  	switch lvl {
   109  	case "INFO":
   110  		return level.AllowInfo()
   111  	case "DEBUG":
   112  		return level.AllowDebug()
   113  	case "WARN":
   114  		return level.AllowWarn()
   115  	case "ERROR":
   116  		return level.AllowError()
   117  	default:
   118  		return level.AllowAll()
   119  	}
   120  }
   121  
   122  // NewHTTPOption returns a http config option.
   123  func NewHTTPOption(configYAML []byte) ([]Option, error) {
   124  	// Define a black config option.
   125  	logOpts := []Option{
   126  		WithDecider(func(_ string, err error) Decision {
   127  			return NoLogCall
   128  		}),
   129  	}
   130  
   131  	// If req logging is disabled.
   132  	if len(configYAML) == 0 {
   133  		return logOpts, nil
   134  	}
   135  
   136  	reqLogConfig, err := NewRequestConfig(configYAML)
   137  	// If unmarshalling is an issue.
   138  	if err != nil {
   139  		return logOpts, err
   140  	}
   141  
   142  	globalLevel, globalStart, globalEnd, err := fillGlobalOptionConfig(reqLogConfig, false)
   143  
   144  	// If global options have invalid entries.
   145  	if err != nil {
   146  		return logOpts, err
   147  	}
   148  	// If the level entry does not matches our entries.
   149  	if err := validateLevel(globalLevel); err != nil {
   150  		// fmt.Printf("HTTP")
   151  		return logOpts, err
   152  	}
   153  
   154  	// If the combination is valid, use them, otherwise return error.
   155  	reqLogDecision, err := getHTTPLoggingOption(globalStart, globalEnd)
   156  	if err != nil {
   157  		return logOpts, err
   158  	}
   159  
   160  	logOpts = []Option{
   161  		WithFilter(func(logger log.Logger) log.Logger {
   162  			return level.NewFilter(logger, getLevel(globalLevel))
   163  		}),
   164  		WithLevels(DefaultCodeToLevel),
   165  	}
   166  
   167  	if len(reqLogConfig.HTTP.Config) == 0 {
   168  		logOpts = append(logOpts, []Option{WithDecider(func(_ string, err error) Decision {
   169  			return reqLogDecision
   170  		}),
   171  		}...)
   172  		return logOpts, nil
   173  	}
   174  
   175  	methodNameSlice := []string{}
   176  
   177  	for _, eachConfig := range reqLogConfig.HTTP.Config {
   178  		eachConfigName := fmt.Sprintf("%v:%v", eachConfig.Path, eachConfig.Port)
   179  		methodNameSlice = append(methodNameSlice, eachConfigName)
   180  	}
   181  
   182  	sort.Strings(methodNameSlice)
   183  
   184  	logOpts = append(logOpts, []Option{
   185  		WithDecider(func(runtimeMethodName string, err error) Decision {
   186  			idx := sort.SearchStrings(methodNameSlice, runtimeMethodName)
   187  			if idx < len(methodNameSlice) && methodNameSlice[idx] == runtimeMethodName {
   188  				return reqLogDecision
   189  			}
   190  			return NoLogCall
   191  		}),
   192  	}...)
   193  	return logOpts, nil
   194  
   195  }