From cc430b1cd068ded4ea88a6a3c010ca5c031868b8 Mon Sep 17 00:00:00 2001 From: wwt Date: Fri, 22 Dec 2023 19:15:53 +0800 Subject: [PATCH] feat: file/syslog output for request logging --- cmd/root.go | 4 ++ http/request_log.go | 108 ++++++++++++++++++++++++++++++++++++++++++- settings/settings.go | 1 + 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 77c3f6b4..bbbb57a0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -72,6 +72,7 @@ func addServerFlags(flags *pflag.FlagSet) { flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers") flags.Bool("enable-request-log", false, "enable logging all the request") flags.String("request-log-format", "%{user_name} %{ip} %{method} %{path} %{response_size}", "logging format for the request") + flags.String("request-log-output", "system", "destination for request logs, available options: system (golang logging), file://path/to/log.txt (local file), udp://host:port (syslog server by UDP), tcp://host:port (syslog server by TCP). By default: system.") } var rootCmd = &cobra.Command{ @@ -270,6 +271,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { requestLogFormat := getParam(flags, "request-log-format") server.RequestLogFormat = requestLogFormat + requestLogOutput := getParam(flags, "request-log-output") + server.RequestLogOutput = requestLogOutput + if val, set := getParamB(flags, "token-expiration-time"); set { server.TokenExpirationTime = val } diff --git a/http/request_log.go b/http/request_log.go index 52f87b11..885a8292 100644 --- a/http/request_log.go +++ b/http/request_log.go @@ -3,7 +3,10 @@ package http import ( "fmt" "log" + "log/syslog" "net/http" + "os" + "path/filepath" "strconv" "strings" "time" @@ -13,6 +16,78 @@ import ( "github.com/tomasen/realip" ) +type LogWriter struct { + Type string + Uri string + syslogWriter *syslog.Writer + fileWriter *os.File +} + +var globalLogWriter *LogWriter = nil + +func _syslogConnect(type_ string, host string) *syslog.Writer { + writer, err := syslog.Dial(type_, host, syslog.LOG_EMERG|syslog.LOG_SYSLOG, "filebrowser") + if err != nil { + log.Printf("ERROR: fail to connect to the syslog-%s server: %s", type_, host) + log.Println(err) + return nil + } else { + return writer + } +} + +func (w *LogWriter) Connect() bool { + if w.Type == "system" { + return true + } + if w.Type == "file" && w.fileWriter != nil { + return true + } + if (w.Type == "syslog-tcp" || w.Type == "syslog-udp") && w.syslogWriter != nil { + return true + } + if w.Type == "file" { + w.fileWriter = _openFile(w.Uri) + } else if w.Type == "syslog-udp" { + w.syslogWriter = _syslogConnect("udp", w.Uri) + } else if w.Type == "syslog-tcp" { + w.syslogWriter = _syslogConnect("tcp", w.Uri) + } + return w.syslogWriter != nil || w.fileWriter != nil +} + +func _openFile(path string) *os.File { + d := filepath.Dir(path) + err := os.MkdirAll(d, 0644) + if err != nil { + log.Println("ERROR: fail to create dir " + d) + return nil + } + file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + log.Println("ERROR: fail to open file " + path) + log.Println(err) + return nil + } + return file +} + +func (w *LogWriter) Write(msg string) { + if w.Type == "system" { + log.Println(msg) + } else if w.Type == "file" { + if w.Connect() { + w.fileWriter.Write([]byte(msg + "\n")) + } + } else if w.Type == "syslog-udp" || w.Type == "syslog-tcp" { + if w.Connect() { + w.syslogWriter.Emerg(msg) + } + } else { + log.Println("Unsupported request log output type " + w.Type) + } +} + type RequestLog struct { user *users.User ip string @@ -115,6 +190,34 @@ func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.f(w, r) } +func _logToOutput(msg string, writer *LogWriter) { + if writer != nil { + writer.Write(msg) + } +} + +func _getLogWriter(output string) *LogWriter { + if output == "system" { + return &LogWriter{Type: "system"} + } else if strings.HasPrefix(output, "udp://") { + return &LogWriter{Type: "syslog-udp", Uri: output[6:]} + } else if strings.HasPrefix(output, "tcp://") { + return &LogWriter{Type: "syslog-tcp", Uri: output[6:]} + } else if strings.HasPrefix(output, "file://") { + return &LogWriter{Type: "file", Uri: output[7:]} + } + log.Fatal("Unsupported log output: " + output) + return &LogWriter{} +} + +func _globalLogWriter(output string) *LogWriter { + if globalLogWriter == nil { + globalLogWriter = _getLogWriter(output) + } + return globalLogWriter + +} + func _log(writer *ResponseWriterWrapper, r *http.Request, user *users.User, server *settings.Server) { log_ := RequestLog{ user: user, @@ -129,7 +232,10 @@ func _log(writer *ResponseWriterWrapper, r *http.Request, user *users.User, serv path: r.RequestURI, method: r.Method, } - log.Println(formatLog(server.RequestLogFormat, log_)) + if log_.status == 0 { + log_.status = 200 + } + _globalLogWriter(server.RequestLogOutput).Write(formatLog(server.RequestLogFormat, log_)) } func RequestLogHandlerFunc(handler http.HandlerFunc, server *settings.Server) http.HandlerFunc { diff --git a/settings/settings.go b/settings/settings.go index aa088751..f63ec835 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -52,6 +52,7 @@ type Server struct { TokenExpirationTime string `json:"tokenExpirationTime"` EnableRequestLog bool `json:"enableRequestLog"` RequestLogFormat string `json:"requestLogFormat"` + RequestLogOutput string `json:"requestLogOutput"` } // Clean cleans any variables that might need cleaning.