package middleware import ( "fmt" "net" "net/http" "net/url" "time" ) // Logger is an interface that allows users to implement a custom HTTP request // logging algorithm. Logging, in the context of Internet web servers, is the // act of keeping a history of page requests. // // Although there is a standard format for web server log files, usually known // as Common Log Format, other proprietary formats exist. Information about the // request, including client IP address, request date/time, page requested, the // HTTP status code, bytes served, user agent, and referrer are typically added. // This data can be combined into a single document, or separated into distinct // logs, such as an access log, error log, or referrer log. // // If you want to extend the basic logger instead of override it, you can // embed it into your own using a private struct field, then use that field // to call the corresponding methods of the parent struct. // // type CustomLogger struct { // parent middleware.Logger // } // func NewCustomLogger() middleware.Logger { // return &CustomLogger{ // parent: middleware.NewBasicLogger(), // } // } // func (l CustomLogger) Log(data middleware.AccessLog) { // l.parent.Log(data) // } // // Example, request tracing using Prometheus: // // var srv = middleware.New() // var counter = prometheus.NewCounterVec(...) // func init() { // srv.Logger = NewCustomLogger() // prometheus.MustRegister(counter) // srv.Handle(http.MethodGet, "/metrics", promhttp.Handler()) // } // type CustomLogger struct { // parent middleware.Logger // } // func NewCustomLogger() middleware.Logger { // return &CustomLogger{ // parent: middleware.NewBasicLogger(), // } // } // func (l CustomLogger) Log(data middleware.AccessLog) { // counter.With(prometheus.Labels{"host": data.Host}).Inc() // l.parent.Log(data) // } type Logger interface { // ListeningOn is called once, just before the execution of ListenAndServe. ListeningOn(net.Addr) // Shutdown is called once, immediately after the graceful server shutdown. Shutdown(error) // Log is called every time the web server handles a request. Log(AccessLog) } // AccessLog represents the most relevant information associated to each HTTP // request. The struct type was designed to be as flexible as the Common Log // Format, also known as the NCSA Common log format or simply NCSA_HTTPd, which // is a standardized text file format used by various web servers like Apache // and Nginx when generating server log files. // // Each line in a file stored in Common Log Format has the following syntax: // // host ident authuser date request status bytes // // Example: // // 127.0.0.1 - cixtor [10/Dec/2019:13:55:36 -0700] "GET /server-status HTTP/1.1" 200 2326 // // The format is extended by the Combined Log Format with the HTTP referrer and // user-agent fields. The Logger interface gives you the flexibility to follow // any standard or to design your own. // // Example: // // 127.0.0.1 - cixtor [10/Dec/2019:13:55:36 -0700] "GET /server-status HTTP/1.1" 200 2326 "http://localhost/" "Mozilla/5.0 (KHTML, like Gecko) Version/78.0.3904.108" // // The "hyphen" in the output indicates that the requested piece of information // is not available. In the example, the hyphen is the RFC 1413 identity of the // client determined by "identd" on the client's machine. This information is // highly unreliable and should almost never be used except on tightly controlled // internal networks. Other web servers, like Apache httpd, will not even attempt // to determine this information unless IdentityCheck is set to "On". // // Source: https://en.wikipedia.org/wiki/Common_Log_Format type AccessLog struct { StartTime time.Time Host string RemoteAddr string RemoteUser string Method string Path string Query url.Values Protocol string StatusCode int BytesReceived int64 BytesSent int Header http.Header Duration time.Duration } // Request concatenates the request method, path, parameters and protocol. func (a AccessLog) Request() string { return fmt.Sprintf("%q", a.Method+"\x20"+a.FullURL()+"\x20"+a.Protocol) } // FullURL concatenates the request path and its query parameters. func (a AccessLog) FullURL() string { fullURL := a.Path if params := a.Query.Encode(); params != "" { fullURL += "?" + params } return fullURL } // Referer returns the request HTTP referer or a hyphen. func (a AccessLog) Referer() string { referer := a.Header.Get("Referer") if referer == "" { referer = "-" } return referer } // UserAgent returns the request HTTP user-agent or a hyphen. func (a AccessLog) UserAgent() string { userAgent := a.Header.Get("User-Agent") if userAgent == "" { userAgent = "-" } return userAgent } // String returns the request metadata in Combined Log format. func (a AccessLog) String() string { return fmt.Sprintf( "%s %s %s %d %d %q %v", a.Host, a.RemoteAddr, a.Request(), a.StatusCode, a.BytesSent, a.Header.Get("User-Agent"), a.Duration, ) } // CommonLog returns the request metadata in Common Log format. func (a AccessLog) CommonLog() string { return fmt.Sprintf( "%s - - [%s] %s %d %d", a.RemoteAddr, a.StartTime.Format(`02/01/2006:15:04:05 -07:00`), a.Request(), a.StatusCode, a.BytesSent, ) } // CombinedLog returns the request metadata in Combined Log format. func (a AccessLog) CombinedLog() string { return fmt.Sprintf( "%s %q %q", a.CommonLog(), a.Referer(), a.UserAgent(), ) }