mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2024-11-24 08:52:25 +02:00
dd05e7ff0b
* add new linters and fix issues * fix deprecated warnings * simplify return * update CHANGELOG * fix staticcheck issues * remove a deprecated linter, minor fixes of variable initialization
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)
|
|
}
|