diff --git a/internal/api/api.go b/internal/api/api.go index 9f00a6bc..d08912df 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -52,6 +52,7 @@ func Init() { HandleFunc("api/config", configHandler) HandleFunc("api/exit", exitHandler) HandleFunc("api/restart", restartHandler) + HandleFunc("api/log", logHandler) Handler = http.DefaultServeMux // 4th @@ -246,6 +247,48 @@ func restartHandler(w http.ResponseWriter, r *http.Request) { go shell.Restart() } +// logHandler handles HTTP requests for log file operations. +// It supports two HTTP methods: +// - GET: Retrieves the content of the log file and sends it back to the client as plain text. +// - DELETE: Deletes the log file from the server. +// +// The function expects a valid http.ResponseWriter and an http.Request as parameters. +// For a GET request, it reads the log file specified by app.GetLogFilepath() and writes +// the content to the response writer with a "text/plain" content type. If the log file +// cannot be read, it responds with an HTTP 404 (Not Found) status. +// +// For a DELETE request, it attempts to delete the log file. If the deletion fails, +// it responds with an HTTP 503 (Service Unavailable) status. +// +// For any other HTTP method, it responds with an HTTP 400 (Bad Request) status. +// +// Parameters: +// - w http.ResponseWriter: The response writer to write the HTTP response to. +// - r *http.Request: The HTTP request object containing the request details. +// +// No return values are provided since the function writes directly to the response writer. +func logHandler(w http.ResponseWriter, r *http.Request) { + + if r.Method == "GET" { + data, err := os.ReadFile(app.GetLogFilepath()) + if err != nil { + http.Error(w, "", http.StatusNotFound) + return + } + Response(w, data, "text/plain") + } else if r.Method == "DELETE" { + err := os.Truncate(app.GetLogFilepath(), 0) + if err != nil { + http.Error(w, "", http.StatusServiceUnavailable) + return + } + } else { + http.Error(w, "", http.StatusBadRequest) + return + } + +} + type Source struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` diff --git a/internal/app/app.go b/internal/app/app.go index 6a432cf1..7b8b46f0 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + "io/ioutil" "os" "path/filepath" "runtime" @@ -21,6 +22,7 @@ var Version = "1.8.4" var UserAgent = "go2rtc/" + Version var ConfigPath string +var LogFilePath string var Info = map[string]any{ "version": Version, } @@ -77,16 +79,66 @@ func Init() { LoadConfig(&cfg) - log.Logger = NewLogger(cfg.Mod["format"], cfg.Mod["level"]) + log.Logger = NewLogger(cfg.Mod["format"], cfg.Mod["level"], GetLogFilepath()) modules = cfg.Mod log.Info().Msgf("go2rtc version %s %s/%s", Version, runtime.GOOS, runtime.GOARCH) + log.Debug().Msgf("[log] file: %s", GetLogFilepath()) migrateStore() } -func NewLogger(format string, level string) zerolog.Logger { +// GetLogFilepath retrieves the file path for the log file from the application's configuration. +// The configuration is expected to be in YAML format and contain a "log" section with a "file" key. +// It uses the LoadConfig function to populate the cfg structure with the configuration data. +// +// Returns: +// +// string: The file path of the log file as specified in the configuration. +// +// Note: +// +// The function assumes that the LoadConfig function is defined elsewhere and is responsible +// for loading and parsing the configuration into the provided struct. +// The cfg struct is an anonymous struct with a Mod field, which is a map with string keys and values. +// The "log" key within the Mod map is expected to contain a sub-map with the "file" key that holds the log file path. +// +// Example of expected YAML configuration: +// +// log: +// file: "/path/to/logfile.log" +// +// If the "file" key is not found within the "log" section of the configuration, the function will return an empty string. +func GetLogFilepath() string { + var cfg struct { + Mod map[string]string `yaml:"log"` + } + + if LogFilePath != "" { + return LogFilePath + } + + LoadConfig(&cfg) + + if cfg.Mod["file"] == "" { + // Generate temporary log file + tmpFile, err := ioutil.TempFile("", "go2rtc*.log") + if err != nil { + return "" + } + defer tmpFile.Close() + + LogFilePath = tmpFile.Name() + + } else { + LogFilePath = cfg.Mod["file"] + } + + return LogFilePath +} + +func NewLogger(format string, level string, file string) zerolog.Logger { var writer io.Writer = os.Stdout if format != "json" { @@ -96,6 +148,19 @@ func NewLogger(format string, level string) zerolog.Logger { } } + if file != "" { + fileHandler, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + fileLogger := zerolog.ConsoleWriter{ + Out: fileHandler, TimeFormat: "15:04:05.000", + NoColor: true, + } + + if err == nil { + writer = zerolog.MultiLevelWriter(writer, fileLogger) + } + + } + zerolog.TimeFieldFormat = time.RFC3339Nano lvl, err := zerolog.ParseLevel(level) diff --git a/www/log.html b/www/log.html new file mode 100644 index 00000000..4d86a7c3 --- /dev/null +++ b/www/log.html @@ -0,0 +1,100 @@ + + +
+