275 lines
6.7 KiB
Go
275 lines
6.7 KiB
Go
package http
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"log/syslog"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/filebrowser/filebrowser/v2/settings"
|
|
"github.com/filebrowser/filebrowser/v2/users"
|
|
"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
|
|
time time.Time
|
|
request_size uint64
|
|
response_size uint64
|
|
path string
|
|
method string
|
|
status int
|
|
elapsed float64
|
|
referer string
|
|
origin string
|
|
}
|
|
|
|
func (r *RequestLog) user_name() string {
|
|
if r.user != nil {
|
|
return r.user.Username
|
|
}
|
|
return "-"
|
|
}
|
|
|
|
func (r *RequestLog) user_id() string {
|
|
if r.user != nil {
|
|
return fmt.Sprintf("%d", r.user.ID)
|
|
}
|
|
return "-"
|
|
}
|
|
|
|
func (r *RequestLog) user_scope() string {
|
|
if r.user != nil {
|
|
if r.user.Scope == "" {
|
|
return "."
|
|
}
|
|
return r.user.Scope
|
|
}
|
|
return "-"
|
|
}
|
|
|
|
func (r *RequestLog) time_string() string {
|
|
return r.time.Format(time.RFC3339)
|
|
}
|
|
|
|
// support placeholders:
|
|
//
|
|
// %{user_name}
|
|
// %{user_id}
|
|
// %{user_scope}
|
|
// %{ip}
|
|
// %{time}
|
|
// %{request_size}
|
|
// %{response_size}: 0 for Transfer-Encoding=chunked
|
|
// %{path}
|
|
// %{method}
|
|
// %{status}
|
|
// %{elapsed}
|
|
// %{referer}
|
|
// %{origin}
|
|
func formatLog(format string, log RequestLog) string {
|
|
format = strings.ReplaceAll(format, "%{user_name}", log.user_name())
|
|
format = strings.ReplaceAll(format, "%{user_id}", log.user_id())
|
|
format = strings.ReplaceAll(format, "%{user_scope}", log.user_scope())
|
|
format = strings.ReplaceAll(format, "%{ip}", log.ip)
|
|
format = strings.ReplaceAll(format, "%{time}", log.time_string())
|
|
format = strings.ReplaceAll(format, "%{request_size}", int2string(log.request_size))
|
|
format = strings.ReplaceAll(format, "%{response_size}", int2string(log.response_size))
|
|
format = strings.ReplaceAll(format, "%{path}", log.path)
|
|
format = strings.ReplaceAll(format, "%{method}", log.method)
|
|
format = strings.ReplaceAll(format, "%{status}", int2string(log.status))
|
|
format = strings.ReplaceAll(format, "%{elapsed}", float2string(log.elapsed))
|
|
format = strings.ReplaceAll(format, "%{referer}", log.referer)
|
|
format = strings.ReplaceAll(format, "%{origin}", log.origin)
|
|
return format
|
|
}
|
|
|
|
func str2uint64(d string) uint64 {
|
|
val, err := strconv.ParseInt(d, 10, 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return uint64(val)
|
|
}
|
|
|
|
func int2string(val any) string {
|
|
return fmt.Sprintf("%d", val)
|
|
}
|
|
|
|
func float2string(val float64) string {
|
|
return fmt.Sprintf("%f", val)
|
|
}
|
|
|
|
func getRequestSize(r *http.Request) uint64 {
|
|
return uint64(r.ContentLength)
|
|
}
|
|
|
|
type myHandler struct {
|
|
f http.HandlerFunc
|
|
}
|
|
|
|
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,
|
|
status: writer.GetStatus(),
|
|
elapsed: writer.GetElapsed(),
|
|
response_size: writer.GetSize(),
|
|
ip: realip.FromRequest(r),
|
|
time: writer.GetTime(),
|
|
request_size: getRequestSize(r),
|
|
origin: r.Header.Get("Origin"),
|
|
referer: r.Header.Get("Referer"),
|
|
path: r.RequestURI,
|
|
method: r.Method,
|
|
}
|
|
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 {
|
|
if !server.EnableRequestLog {
|
|
return handler
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
writer := MakeResponseWriterWrapper(w)
|
|
handler(writer, r)
|
|
_log(writer, r, nil, server)
|
|
})
|
|
}
|
|
|
|
func RequestLogHandler(handler http.Handler, server *settings.Server) http.Handler {
|
|
if !server.EnableRequestLog {
|
|
return handler
|
|
}
|
|
return &myHandler{f: func(w http.ResponseWriter, r *http.Request) {
|
|
writer := MakeResponseWriterWrapper(w)
|
|
handler.ServeHTTP(writer, r)
|
|
_log(writer, r, nil, server)
|
|
}}
|
|
}
|
|
|
|
func RequestLogHandleFunc(handle handleFunc, server *settings.Server) handleFunc {
|
|
if !server.EnableRequestLog {
|
|
return handle
|
|
}
|
|
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
|
writer := MakeResponseWriterWrapper(w)
|
|
status, err := handle(writer, r, d)
|
|
writer.status.set(status)
|
|
_log(writer, r, d.user, server)
|
|
return status, err
|
|
}
|
|
}
|