491 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			491 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
package logger
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
	"runtime"
 | 
						|
	"sync"
 | 
						|
	"text/template"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// AuthStatus defines the different types of auth logging that occur
 | 
						|
type AuthStatus string
 | 
						|
 | 
						|
const (
 | 
						|
	// DefaultStandardLoggingFormat defines the default standard log format
 | 
						|
	DefaultStandardLoggingFormat = "[{{.Timestamp}}] [{{.File}}] {{.Message}}"
 | 
						|
	// DefaultAuthLoggingFormat defines the default auth log format
 | 
						|
	DefaultAuthLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
 | 
						|
	// DefaultRequestLoggingFormat defines the default request log format
 | 
						|
	DefaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
 | 
						|
 | 
						|
	// AuthSuccess indicates that an auth attempt has succeeded explicitly
 | 
						|
	AuthSuccess AuthStatus = "AuthSuccess"
 | 
						|
	// AuthFailure indicates that an auth attempt has failed explicitly
 | 
						|
	AuthFailure AuthStatus = "AuthFailure"
 | 
						|
	// AuthError indicates that an auth attempt has failed due to an error
 | 
						|
	AuthError AuthStatus = "AuthError"
 | 
						|
 | 
						|
	// Llongfile flag to log full file name and line number: /a/b/c/d.go:23
 | 
						|
	Llongfile = 1 << iota
 | 
						|
	// Lshortfile flag to log final file name element and line number: d.go:23. overrides Llongfile
 | 
						|
	Lshortfile
 | 
						|
	// LUTC flag to log UTC datetime rather than the local time zone
 | 
						|
	LUTC
 | 
						|
	// LstdFlags flag for initial values for the logger
 | 
						|
	LstdFlags = Lshortfile
 | 
						|
)
 | 
						|
 | 
						|
// These are the containers for all values that are available as variables in the logging formats.
 | 
						|
// All values are pre-formatted strings so it is easy to use them in the format string.
 | 
						|
type stdLogMessageData struct {
 | 
						|
	Timestamp,
 | 
						|
	File,
 | 
						|
	Message string
 | 
						|
}
 | 
						|
 | 
						|
type authLogMessageData struct {
 | 
						|
	Client,
 | 
						|
	Host,
 | 
						|
	Protocol,
 | 
						|
	RequestMethod,
 | 
						|
	Timestamp,
 | 
						|
	UserAgent,
 | 
						|
	Username,
 | 
						|
	Status,
 | 
						|
	Message string
 | 
						|
}
 | 
						|
 | 
						|
type reqLogMessageData struct {
 | 
						|
	Client,
 | 
						|
	Host,
 | 
						|
	Protocol,
 | 
						|
	RequestDuration,
 | 
						|
	RequestMethod,
 | 
						|
	RequestURI,
 | 
						|
	ResponseSize,
 | 
						|
	StatusCode,
 | 
						|
	Timestamp,
 | 
						|
	Upstream,
 | 
						|
	UserAgent,
 | 
						|
	Username string
 | 
						|
}
 | 
						|
 | 
						|
// A Logger represents an active logging object that generates lines of
 | 
						|
// output to an io.Writer passed through a formatter. Each logging
 | 
						|
// operation makes a single call to the Writer's Write method. A Logger
 | 
						|
// can be used simultaneously from multiple goroutines; it guarantees to
 | 
						|
// serialize access to the Writer.
 | 
						|
type Logger struct {
 | 
						|
	mu             sync.Mutex
 | 
						|
	flag           int
 | 
						|
	writer         io.Writer
 | 
						|
	stdEnabled     bool
 | 
						|
	authEnabled    bool
 | 
						|
	reqEnabled     bool
 | 
						|
	reverseProxy   bool
 | 
						|
	excludePaths   map[string]struct{}
 | 
						|
	stdLogTemplate *template.Template
 | 
						|
	authTemplate   *template.Template
 | 
						|
	reqTemplate    *template.Template
 | 
						|
}
 | 
						|
 | 
						|
// New creates a new Standarderr Logger.
 | 
						|
func New(flag int) *Logger {
 | 
						|
	return &Logger{
 | 
						|
		writer:         os.Stderr,
 | 
						|
		flag:           flag,
 | 
						|
		stdEnabled:     true,
 | 
						|
		authEnabled:    true,
 | 
						|
		reqEnabled:     true,
 | 
						|
		reverseProxy:   false,
 | 
						|
		excludePaths:   nil,
 | 
						|
		stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)),
 | 
						|
		authTemplate:   template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)),
 | 
						|
		reqTemplate:    template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var std = New(LstdFlags)
 | 
						|
 | 
						|
// Output a standard log template with a simple message.
 | 
						|
// Write a final newline at the end of every message.
 | 
						|
func (l *Logger) Output(calldepth int, message string) {
 | 
						|
	if !l.stdEnabled {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	now := time.Now()
 | 
						|
	file := "???:0"
 | 
						|
 | 
						|
	if l.flag&(Lshortfile|Llongfile) != 0 {
 | 
						|
		file = l.GetFileLineString(calldepth + 1)
 | 
						|
	}
 | 
						|
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
 | 
						|
	l.stdLogTemplate.Execute(l.writer, stdLogMessageData{
 | 
						|
		Timestamp: FormatTimestamp(now),
 | 
						|
		File:      file,
 | 
						|
		Message:   message,
 | 
						|
	})
 | 
						|
 | 
						|
	l.writer.Write([]byte("\n"))
 | 
						|
}
 | 
						|
 | 
						|
// PrintAuthf writes auth info to the logger. Requires an http.Request to
 | 
						|
// log request details. Remaining arguments are handled in the manner of
 | 
						|
// fmt.Sprintf. Writes a final newline to the end of every message.
 | 
						|
func (l *Logger) PrintAuthf(username string, req *http.Request, status AuthStatus, format string, a ...interface{}) {
 | 
						|
	if !l.authEnabled {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	now := time.Now()
 | 
						|
 | 
						|
	if username == "" {
 | 
						|
		username = "-"
 | 
						|
	}
 | 
						|
 | 
						|
	client := GetClient(req, l.reverseProxy)
 | 
						|
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
 | 
						|
	l.authTemplate.Execute(l.writer, authLogMessageData{
 | 
						|
		Client:        client,
 | 
						|
		Host:          req.Host,
 | 
						|
		Protocol:      req.Proto,
 | 
						|
		RequestMethod: req.Method,
 | 
						|
		Timestamp:     FormatTimestamp(now),
 | 
						|
		UserAgent:     fmt.Sprintf("%q", req.UserAgent()),
 | 
						|
		Username:      username,
 | 
						|
		Status:        string(status),
 | 
						|
		Message:       fmt.Sprintf(format, a...),
 | 
						|
	})
 | 
						|
 | 
						|
	l.writer.Write([]byte("\n"))
 | 
						|
}
 | 
						|
 | 
						|
// PrintReq writes request details to the Logger using the http.Request,
 | 
						|
// url, and timestamp of the request.  Writes a final newline to the end
 | 
						|
// of every message.
 | 
						|
func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
 | 
						|
	if !l.reqEnabled {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if _, ok := l.excludePaths[url.Path]; ok {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	duration := float64(time.Since(ts)) / float64(time.Second)
 | 
						|
 | 
						|
	if username == "" {
 | 
						|
		username = "-"
 | 
						|
	}
 | 
						|
 | 
						|
	if upstream == "" {
 | 
						|
		upstream = "-"
 | 
						|
	}
 | 
						|
 | 
						|
	if url.User != nil && username == "-" {
 | 
						|
		if name := url.User.Username(); name != "" {
 | 
						|
			username = name
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	client := GetClient(req, l.reverseProxy)
 | 
						|
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
 | 
						|
	l.reqTemplate.Execute(l.writer, reqLogMessageData{
 | 
						|
		Client:          client,
 | 
						|
		Host:            req.Host,
 | 
						|
		Protocol:        req.Proto,
 | 
						|
		RequestDuration: fmt.Sprintf("%0.3f", duration),
 | 
						|
		RequestMethod:   req.Method,
 | 
						|
		RequestURI:      fmt.Sprintf("%q", url.RequestURI()),
 | 
						|
		ResponseSize:    fmt.Sprintf("%d", size),
 | 
						|
		StatusCode:      fmt.Sprintf("%d", status),
 | 
						|
		Timestamp:       FormatTimestamp(ts),
 | 
						|
		Upstream:        upstream,
 | 
						|
		UserAgent:       fmt.Sprintf("%q", req.UserAgent()),
 | 
						|
		Username:        username,
 | 
						|
	})
 | 
						|
 | 
						|
	l.writer.Write([]byte("\n"))
 | 
						|
}
 | 
						|
 | 
						|
// GetFileLineString will find the caller file and line number
 | 
						|
// taking in to account the calldepth to iterate up the stack
 | 
						|
// to find the non-logging call location.
 | 
						|
func (l *Logger) GetFileLineString(calldepth int) string {
 | 
						|
	var file string
 | 
						|
	var line int
 | 
						|
	var ok bool
 | 
						|
 | 
						|
	_, file, line, ok = runtime.Caller(calldepth)
 | 
						|
	if !ok {
 | 
						|
		file = "???"
 | 
						|
		line = 0
 | 
						|
	}
 | 
						|
 | 
						|
	if l.flag&Lshortfile != 0 {
 | 
						|
		short := file
 | 
						|
		for i := len(file) - 1; i > 0; i-- {
 | 
						|
			if file[i] == '/' {
 | 
						|
				short = file[i+1:]
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		file = short
 | 
						|
	}
 | 
						|
 | 
						|
	return fmt.Sprintf("%s:%d", file, line)
 | 
						|
}
 | 
						|
 | 
						|
// GetClient parses an HTTP request for the client/remote IP address.
 | 
						|
func GetClient(req *http.Request, reverseProxy bool) string {
 | 
						|
	client := req.RemoteAddr
 | 
						|
	if reverseProxy {
 | 
						|
		if ip := req.Header.Get("X-Real-IP"); ip != "" {
 | 
						|
			client = ip
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if c, _, err := net.SplitHostPort(client); err == nil {
 | 
						|
		client = c
 | 
						|
	}
 | 
						|
 | 
						|
	return client
 | 
						|
}
 | 
						|
 | 
						|
// FormatTimestamp returns a formatted timestamp.
 | 
						|
func (l *Logger) FormatTimestamp(ts time.Time) string {
 | 
						|
	if l.flag&LUTC != 0 {
 | 
						|
		ts = ts.UTC()
 | 
						|
	}
 | 
						|
 | 
						|
	return ts.Format("2006/01/02 15:04:05")
 | 
						|
}
 | 
						|
 | 
						|
// Flags returns the output flags for the logger.
 | 
						|
func (l *Logger) Flags() int {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	return l.flag
 | 
						|
}
 | 
						|
 | 
						|
// SetFlags sets the output flags for the logger.
 | 
						|
func (l *Logger) SetFlags(flag int) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.flag = flag
 | 
						|
}
 | 
						|
 | 
						|
// SetStandardEnabled enables or disables standard logging.
 | 
						|
func (l *Logger) SetStandardEnabled(e bool) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.stdEnabled = e
 | 
						|
}
 | 
						|
 | 
						|
// SetAuthEnabled enables or disables auth logging.
 | 
						|
func (l *Logger) SetAuthEnabled(e bool) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.authEnabled = e
 | 
						|
}
 | 
						|
 | 
						|
// SetReqEnabled enabled or disables request logging.
 | 
						|
func (l *Logger) SetReqEnabled(e bool) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.reqEnabled = e
 | 
						|
}
 | 
						|
 | 
						|
// SetReverseProxy controls whether logging will trust headers that can be set by a reverse proxy.
 | 
						|
func (l *Logger) SetReverseProxy(e bool) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.reverseProxy = e
 | 
						|
}
 | 
						|
 | 
						|
// SetExcludePaths sets the paths to exclude from logging.
 | 
						|
func (l *Logger) SetExcludePaths(s []string) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.excludePaths = make(map[string]struct{})
 | 
						|
	for _, p := range s {
 | 
						|
		l.excludePaths[p] = struct{}{}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SetStandardTemplate sets the template for standard logging.
 | 
						|
func (l *Logger) SetStandardTemplate(t string) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.stdLogTemplate = template.Must(template.New("std-log").Parse(t))
 | 
						|
}
 | 
						|
 | 
						|
// SetAuthTemplate sets the template for auth logging.
 | 
						|
func (l *Logger) SetAuthTemplate(t string) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.authTemplate = template.Must(template.New("auth-log").Parse(t))
 | 
						|
}
 | 
						|
 | 
						|
// SetReqTemplate sets the template for request logging.
 | 
						|
func (l *Logger) SetReqTemplate(t string) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.reqTemplate = template.Must(template.New("req-log").Parse(t))
 | 
						|
}
 | 
						|
 | 
						|
// These functions utilize the standard logger.
 | 
						|
 | 
						|
// FormatTimestamp returns a formatted timestamp for the standard logger.
 | 
						|
func FormatTimestamp(ts time.Time) string {
 | 
						|
	return std.FormatTimestamp(ts)
 | 
						|
}
 | 
						|
 | 
						|
// Flags returns the output flags for the standard logger.
 | 
						|
func Flags() int {
 | 
						|
	return std.Flags()
 | 
						|
}
 | 
						|
 | 
						|
// SetFlags sets the output flags for the standard logger.
 | 
						|
func SetFlags(flag int) {
 | 
						|
	std.SetFlags(flag)
 | 
						|
}
 | 
						|
 | 
						|
// SetOutput sets the output destination for the standard logger.
 | 
						|
func SetOutput(w io.Writer) {
 | 
						|
	std.mu.Lock()
 | 
						|
	defer std.mu.Unlock()
 | 
						|
	std.writer = w
 | 
						|
}
 | 
						|
 | 
						|
// SetStandardEnabled enables or disables standard logging for the
 | 
						|
// standard logger.
 | 
						|
func SetStandardEnabled(e bool) {
 | 
						|
	std.SetStandardEnabled(e)
 | 
						|
}
 | 
						|
 | 
						|
// SetAuthEnabled enables or disables auth logging for the standard
 | 
						|
// logger.
 | 
						|
func SetAuthEnabled(e bool) {
 | 
						|
	std.SetAuthEnabled(e)
 | 
						|
}
 | 
						|
 | 
						|
// SetReqEnabled enables or disables request logging for the
 | 
						|
// standard logger.
 | 
						|
func SetReqEnabled(e bool) {
 | 
						|
	std.SetReqEnabled(e)
 | 
						|
}
 | 
						|
 | 
						|
// SetReverseProxy controls whether logging will trust headers that can be set
 | 
						|
// by a reverse proxy for the standard logger.
 | 
						|
func SetReverseProxy(e bool) {
 | 
						|
	std.SetReverseProxy(e)
 | 
						|
}
 | 
						|
 | 
						|
// SetExcludePaths sets the path to exclude from logging, eg: health checks
 | 
						|
func SetExcludePaths(s []string) {
 | 
						|
	std.SetExcludePaths(s)
 | 
						|
}
 | 
						|
 | 
						|
// SetStandardTemplate sets the template for standard logging for
 | 
						|
// the standard logger.
 | 
						|
func SetStandardTemplate(t string) {
 | 
						|
	std.SetStandardTemplate(t)
 | 
						|
}
 | 
						|
 | 
						|
// SetAuthTemplate sets the template for auth logging for the
 | 
						|
// standard logger.
 | 
						|
func SetAuthTemplate(t string) {
 | 
						|
	std.SetAuthTemplate(t)
 | 
						|
}
 | 
						|
 | 
						|
// SetReqTemplate sets the template for request logging for the
 | 
						|
// standard logger.
 | 
						|
func SetReqTemplate(t string) {
 | 
						|
	std.SetReqTemplate(t)
 | 
						|
}
 | 
						|
 | 
						|
// Print calls Output to print to the standard logger.
 | 
						|
// Arguments are handled in the manner of fmt.Print.
 | 
						|
func Print(v ...interface{}) {
 | 
						|
	std.Output(2, fmt.Sprint(v...))
 | 
						|
}
 | 
						|
 | 
						|
// Printf calls Output to print to the standard logger.
 | 
						|
// Arguments are handled in the manner of fmt.Printf.
 | 
						|
func Printf(format string, v ...interface{}) {
 | 
						|
	std.Output(2, fmt.Sprintf(format, v...))
 | 
						|
}
 | 
						|
 | 
						|
// Println calls Output to print to the standard logger.
 | 
						|
// Arguments are handled in the manner of fmt.Println.
 | 
						|
func Println(v ...interface{}) {
 | 
						|
	std.Output(2, fmt.Sprintln(v...))
 | 
						|
}
 | 
						|
 | 
						|
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
 | 
						|
func Fatal(v ...interface{}) {
 | 
						|
	std.Output(2, fmt.Sprint(v...))
 | 
						|
	os.Exit(1)
 | 
						|
}
 | 
						|
 | 
						|
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
 | 
						|
func Fatalf(format string, v ...interface{}) {
 | 
						|
	std.Output(2, fmt.Sprintf(format, v...))
 | 
						|
	os.Exit(1)
 | 
						|
}
 | 
						|
 | 
						|
// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
 | 
						|
func Fatalln(v ...interface{}) {
 | 
						|
	std.Output(2, fmt.Sprintln(v...))
 | 
						|
	os.Exit(1)
 | 
						|
}
 | 
						|
 | 
						|
// Panic is equivalent to Print() followed by a call to panic().
 | 
						|
func Panic(v ...interface{}) {
 | 
						|
	s := fmt.Sprint(v...)
 | 
						|
	std.Output(2, s)
 | 
						|
	panic(s)
 | 
						|
}
 | 
						|
 | 
						|
// Panicf is equivalent to Printf() followed by a call to panic().
 | 
						|
func Panicf(format string, v ...interface{}) {
 | 
						|
	s := fmt.Sprintf(format, v...)
 | 
						|
	std.Output(2, s)
 | 
						|
	panic(s)
 | 
						|
}
 | 
						|
 | 
						|
// Panicln is equivalent to Println() followed by a call to panic().
 | 
						|
func Panicln(v ...interface{}) {
 | 
						|
	s := fmt.Sprintln(v...)
 | 
						|
	std.Output(2, s)
 | 
						|
	panic(s)
 | 
						|
}
 | 
						|
 | 
						|
// PrintAuthf writes authentication details to the standard logger.
 | 
						|
// Arguments are handled in the manner of fmt.Printf.
 | 
						|
func PrintAuthf(username string, req *http.Request, status AuthStatus, format string, a ...interface{}) {
 | 
						|
	std.PrintAuthf(username, req, status, format, a...)
 | 
						|
}
 | 
						|
 | 
						|
// PrintReq writes request details to the standard logger.
 | 
						|
func PrintReq(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
 | 
						|
	std.PrintReq(username, upstream, req, url, ts, status, size)
 | 
						|
}
 |