Add log file handling and viewing capabilities

This commit introduces the ability to handle log files through the API and
provides a new log viewing page. The API now supports GET and DELETE methods
for log file operations, allowing retrieval and deletion of log contents.
A new log.html page has been added for viewing logs in the browser, with
automatic refresh every 5 seconds and styling based on log levels.

The app.go file has been updated to include a GetLogFilepath function that
retrieves or generates the log file path. The NewLogger function now accepts
a file parameter to enable file logging. The main.js file has been updated
to include a link to the new log.html page.

This enhancement improves the observability and management of the application
by providing real-time access to logs and the ability to clear them directly
from the web interface.
This commit is contained in:
Sergey Krashevich
2023-11-26 23:21:57 +03:00
parent 051c5ff913
commit 8d382afa0f
4 changed files with 211 additions and 2 deletions
+43
View File
@@ -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"`
+67 -2
View File
@@ -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)