You've already forked openaccounting-server
mirror of
https://github.com/openaccounting/oa-server.git
synced 2025-12-09 09:00:42 +13:00
237 lines
6.3 KiB
Go
237 lines
6.3 KiB
Go
package rest
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
// TODO Future improvements:
|
|
// * support %{strftime}t ?
|
|
// * support %{<header>}o to print headers
|
|
|
|
// AccessLogFormat defines the format of the access log record.
|
|
// This implementation is a subset of Apache mod_log_config.
|
|
// (See http://httpd.apache.org/docs/2.0/mod/mod_log_config.html)
|
|
//
|
|
// %b content length in bytes, - if 0
|
|
// %B content length in bytes
|
|
// %D response elapsed time in microseconds
|
|
// %h remote address
|
|
// %H server protocol
|
|
// %l identd logname, not supported, -
|
|
// %m http method
|
|
// %P process id
|
|
// %q query string
|
|
// %r first line of the request
|
|
// %s status code
|
|
// %S status code preceeded by a terminal color
|
|
// %t time of the request
|
|
// %T response elapsed time in seconds, 3 decimals
|
|
// %u remote user, - if missing
|
|
// %{User-Agent}i user agent, - if missing
|
|
// %{Referer}i referer, - is missing
|
|
//
|
|
// Some predefined formats are provided as contants.
|
|
type AccessLogFormat string
|
|
|
|
const (
|
|
// CommonLogFormat is the Common Log Format (CLF).
|
|
CommonLogFormat = "%h %l %u %t \"%r\" %s %b"
|
|
|
|
// CombinedLogFormat is the NCSA extended/combined log format.
|
|
CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""
|
|
|
|
// DefaultLogFormat is the default format, colored output and response time, convenient for development.
|
|
DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
|
|
)
|
|
|
|
// AccessLogApacheMiddleware produces the access log following a format inspired by Apache
|
|
// mod_log_config. It depends on TimerMiddleware and RecorderMiddleware that should be in the wrapped
|
|
// middlewares. It also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
|
|
type AccessLogApacheMiddleware struct {
|
|
|
|
// Logger points to the logger object used by this middleware, it defaults to
|
|
// log.New(os.Stderr, "", 0).
|
|
Logger *log.Logger
|
|
|
|
// Format defines the format of the access log record. See AccessLogFormat for the details.
|
|
// It defaults to DefaultLogFormat.
|
|
Format AccessLogFormat
|
|
|
|
textTemplate *template.Template
|
|
}
|
|
|
|
// MiddlewareFunc makes AccessLogApacheMiddleware implement the Middleware interface.
|
|
func (mw *AccessLogApacheMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
|
|
|
// set the default Logger
|
|
if mw.Logger == nil {
|
|
mw.Logger = log.New(os.Stderr, "", 0)
|
|
}
|
|
|
|
// set default format
|
|
if mw.Format == "" {
|
|
mw.Format = DefaultLogFormat
|
|
}
|
|
|
|
mw.convertFormat()
|
|
|
|
return func(w ResponseWriter, r *Request) {
|
|
|
|
// call the handler
|
|
h(w, r)
|
|
|
|
util := &accessLogUtil{w, r}
|
|
|
|
mw.Logger.Print(mw.executeTextTemplate(util))
|
|
}
|
|
}
|
|
|
|
var apacheAdapter = strings.NewReplacer(
|
|
"%b", "{{.BytesWritten | dashIf0}}",
|
|
"%B", "{{.BytesWritten}}",
|
|
"%D", "{{.ResponseTime | microseconds}}",
|
|
"%h", "{{.ApacheRemoteAddr}}",
|
|
"%H", "{{.R.Proto}}",
|
|
"%l", "-",
|
|
"%m", "{{.R.Method}}",
|
|
"%P", "{{.Pid}}",
|
|
"%q", "{{.ApacheQueryString}}",
|
|
"%r", "{{.R.Method}} {{.R.URL.RequestURI}} {{.R.Proto}}",
|
|
"%s", "{{.StatusCode}}",
|
|
"%S", "\033[{{.StatusCode | statusCodeColor}}m{{.StatusCode}}",
|
|
"%t", "{{if .StartTime}}{{.StartTime.Format \"02/Jan/2006:15:04:05 -0700\"}}{{end}}",
|
|
"%T", "{{if .ResponseTime}}{{.ResponseTime.Seconds | printf \"%.3f\"}}{{end}}",
|
|
"%u", "{{.RemoteUser | dashIfEmptyStr}}",
|
|
"%{User-Agent}i", "{{.R.UserAgent | dashIfEmptyStr}}",
|
|
"%{Referer}i", "{{.R.Referer | dashIfEmptyStr}}",
|
|
)
|
|
|
|
// Convert the Apache access log format into a text/template
|
|
func (mw *AccessLogApacheMiddleware) convertFormat() {
|
|
|
|
tmplText := apacheAdapter.Replace(string(mw.Format))
|
|
|
|
funcMap := template.FuncMap{
|
|
"dashIfEmptyStr": func(value string) string {
|
|
if value == "" {
|
|
return "-"
|
|
}
|
|
return value
|
|
},
|
|
"dashIf0": func(value int64) string {
|
|
if value == 0 {
|
|
return "-"
|
|
}
|
|
return fmt.Sprintf("%d", value)
|
|
},
|
|
"microseconds": func(dur *time.Duration) string {
|
|
if dur != nil {
|
|
return fmt.Sprintf("%d", dur.Nanoseconds()/1000)
|
|
}
|
|
return ""
|
|
},
|
|
"statusCodeColor": func(statusCode int) string {
|
|
if statusCode >= 400 && statusCode < 500 {
|
|
return "1;33"
|
|
} else if statusCode >= 500 {
|
|
return "0;31"
|
|
}
|
|
return "0;32"
|
|
},
|
|
}
|
|
|
|
var err error
|
|
mw.textTemplate, err = template.New("accessLog").Funcs(funcMap).Parse(tmplText)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Execute the text template with the data derived from the request, and return a string.
|
|
func (mw *AccessLogApacheMiddleware) executeTextTemplate(util *accessLogUtil) string {
|
|
buf := bytes.NewBufferString("")
|
|
err := mw.textTemplate.Execute(buf, util)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// accessLogUtil provides a collection of utility functions that devrive data from the Request object.
|
|
// This object is used to provide data to the Apache Style template and the the JSON log record.
|
|
type accessLogUtil struct {
|
|
W ResponseWriter
|
|
R *Request
|
|
}
|
|
|
|
// As stored by the auth middlewares.
|
|
func (u *accessLogUtil) RemoteUser() string {
|
|
if u.R.Env["REMOTE_USER"] != nil {
|
|
return u.R.Env["REMOTE_USER"].(string)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// If qs exists then return it with a leadin "?", apache log style.
|
|
func (u *accessLogUtil) ApacheQueryString() string {
|
|
if u.R.URL.RawQuery != "" {
|
|
return "?" + u.R.URL.RawQuery
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// When the request entered the timer middleware.
|
|
func (u *accessLogUtil) StartTime() *time.Time {
|
|
if u.R.Env["START_TIME"] != nil {
|
|
return u.R.Env["START_TIME"].(*time.Time)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// If remoteAddr is set then return is without the port number, apache log style.
|
|
func (u *accessLogUtil) ApacheRemoteAddr() string {
|
|
remoteAddr := u.R.RemoteAddr
|
|
if remoteAddr != "" {
|
|
if ip, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
|
return ip
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// As recorded by the recorder middleware.
|
|
func (u *accessLogUtil) StatusCode() int {
|
|
if u.R.Env["STATUS_CODE"] != nil {
|
|
return u.R.Env["STATUS_CODE"].(int)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// As mesured by the timer middleware.
|
|
func (u *accessLogUtil) ResponseTime() *time.Duration {
|
|
if u.R.Env["ELAPSED_TIME"] != nil {
|
|
return u.R.Env["ELAPSED_TIME"].(*time.Duration)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Process id.
|
|
func (u *accessLogUtil) Pid() int {
|
|
return os.Getpid()
|
|
}
|
|
|
|
// As recorded by the recorder middleware.
|
|
func (u *accessLogUtil) BytesWritten() int64 {
|
|
if u.R.Env["BYTES_WRITTEN"] != nil {
|
|
return u.R.Env["BYTES_WRITTEN"].(int64)
|
|
}
|
|
return 0
|
|
}
|