From 6ea4d31109002f114bd25a954be88b154c3239b0 Mon Sep 17 00:00:00 2001 From: Stefan Etringer Date: Tue, 29 Apr 2025 15:03:49 +0000 Subject: [PATCH] added weblogger to display logs, timestamps and levels on the webpage --- .gitignore | 1 + agents/agent.go | 3 + go.mod | 1 + go.sum | 2 + main.go | 48 ++++++++- src/logger/logger.go | 127 ++++++++++++++++++++++++ src/server/api/agentApi.go | 1 + src/server/websocket/websocketServer.go | 40 +++++--- templates/index.html | 37 +------ templates/logs.html | 24 +++++ 10 files changed, 233 insertions(+), 51 deletions(-) create mode 100644 src/logger/logger.go create mode 100644 templates/logs.html diff --git a/.gitignore b/.gitignore index 5f9b08d..1373fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build/ build/* pkg/* agents/agents +logs.db diff --git a/agents/agent.go b/agents/agent.go index 5d6b6c4..8d7f819 100644 --- a/agents/agent.go +++ b/agents/agent.go @@ -11,6 +11,7 @@ import ( "math/rand" "math" "strconv" + // "gontrol/src/logger" "github.com/gorilla/websocket" ) @@ -57,6 +58,7 @@ func registerAgent(agentName, agentId, agentIp, agentType string) error { } log.Printf("Agent %s successfully registered.", agentName) + logger.LogEntries = append(logger.LogEntries, fmt.Sprintf("%s Agent successfully registered.", time.Now().Format(time.RFC3339))) return nil } @@ -67,6 +69,7 @@ func connectToWebSocket(agentName, agentId, agentIp, agentType string) error { conn, _, err = websocket.DefaultDialer.Dial(wsURL, nil) if err == nil { log.Println("WeSocket connection established") + // logger.LogEntries = append(logger.LogEntries, fmt.Sprintf("%s websocket established", time.Now().Format(time.RFC3339))) return nil } diff --git a/go.mod b/go.mod index 0b9f0c9..7eecfcd 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-sql-driver/mysql v1.8.1 github.com/gorilla/websocket v1.5.3 github.com/kelseyhightower/envconfig v1.4.0 + github.com/mattn/go-sqlite3 v1.14.28 ) require ( diff --git a/go.sum b/go.sum index f6be390..a87bc7f 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/main.go b/main.go index 04de929..765df01 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "gontrol/src/randomname" "gontrol/src/server/database" + "gontrol/src/logger" api "gontrol/src/server/api" websocketserver "gontrol/src/server/websocket" @@ -25,8 +26,10 @@ import ( "github.com/kelseyhightower/envconfig" ) -var tmpl *template.Template -var db *sql.DB +var ( + tmpl *template.Template + db *sql.DB +) type Config struct { Database struct { @@ -135,6 +138,24 @@ func getAgentIds(w http.ResponseWriter, r *http.Request) { return } +func logsHandler(w http.ResponseWriter, r *http.Request) { + + logs, err := logger.FetchLogs(10) + if err != nil { + http.Error(w, "Error fetching logs", http.StatusInternalServerError) + return + } + + fmt.Fprintf(w, "
") + // for _, logEntry := range logsToSend { + for _, logEntry := range logs { + fmt.Fprintf(w, "

[%s] [%s] %s

", logEntry.Timestamp, logEntry.Level, logEntry.Message) + } + fmt.Fprintf(w, "
") + +} + + func main() { var cfg Config @@ -153,8 +174,16 @@ func main() { webMux.HandleFunc("/agentNames", getAgentNames) webMux.HandleFunc("/agentIds", getAgentIds) webMux.HandleFunc("/agents/{agentId}", agentsHandler) + webMux.HandleFunc("/logs", logsHandler) + // Sqlite3 + err := logger.InitDB("/tmp/gontrol_logs.db") + if err != nil { + log.Fatal(err) + } + defer logger.CloseDB() // initDB (cfg.Database.Username, cfg.Database.Password, cfg.Database.Host, cfg.Database.Port, cfg.Database.Name) + // mysql db = database.InitDB (cfg.Database.Username, cfg.Database.Password, cfg.Database.Host, cfg.Database.Port, cfg.Database.Name) defer db.Close() name := randomname.GenerateRandomName() @@ -168,20 +197,31 @@ func main() { wg.Add(1) go func() { defer wg.Done() - log.Println("Websocket server is running on port 5555") + logLine := "Websocket server is running on port 5555" + log.Println(logLine) if err := websocketServer.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("Websocket server failed: %s", err) } + err = logger.InsertLog(logger.Info, logLine) + if err != nil { + log.Println("Error inserting log:", err) + } }() wg.Add(1) go func() { defer wg.Done() - log.Println("Web server is running on port 3333") + logLine := "Web server is running on port 3333" + log.Println(logLine) if err := webServer.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("Web server failed: %s", err) } + + err = logger.InsertLog(logger.Info, logLine) + if err != nil { + log.Println("Error inserting log:", err) + } }() shutdownCh := make(chan os.Signal, 1) diff --git a/src/logger/logger.go b/src/logger/logger.go new file mode 100644 index 0000000..c4158c3 --- /dev/null +++ b/src/logger/logger.go @@ -0,0 +1,127 @@ +package logger + +import ( + "database/sql" + "fmt" + "log" + // "net/http" + "sync" + "time" + + _ "github.com/mattn/go-sqlite3" +) + + +var ( + Lite_db *sql.DB + lite_dbMutex sync.Mutex + + logMutex sync.Mutex + logLimit = 10 + // LogEntries []string +) + +const ( + Debug LogLevel = "DEBUG" + Info LogLevel = "INFO" + Warning LogLevel = "WARNING" + Error LogLevel = "ERROR" + Fatal LogLevel = "FATAL" +) + +type LogLevel string + +type LogEntry struct { + Message string + Timestamp string + Level LogLevel +} + +func ToLog(logLine string) string { + log := fmt.Sprintf("%s",time.Now().Format(time.RFC3339) + " " + logLine) + return log +} + +func InitDB(dbPath string) error { + var err error + Lite_db, err = sql.Open("sqlite3", dbPath) + if err != nil { + return fmt.Errorf("Error opening DB: %w", err) + } + + CreateTableQuery := `CREATE TABLE IF NOT EXISTS logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + message TEXT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + level TEXT + );` + + _, err = Lite_db.Exec(CreateTableQuery) + if err != nil { + return fmt.Errorf("Error creating table: %w", err) + } + + return nil +} + +func InsertLog(level LogLevel, message string) error { + lite_dbMutex.Lock() + defer lite_dbMutex.Unlock() + + // Future use may fulfill multiple transactions, a transaction is used + tx, err := Lite_db.Begin() + if err != nil { + return fmt.Errorf("Error starting transaction: %w", err) + } + + // insertQuery := `INSERT INTO logs (message) VALUES (?)` + insertQuery := `INSERT INTO logs (message, level) VALUES (?, ?)` + // _, err := Lite_db.Exec(insertQuery, message, level) + _, err = tx.Exec(insertQuery, message, level) + + if err != nil { + tx.Rollback() + return fmt.Errorf("Error inserting log: %v", err) + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("Error committing transaction: %w", err) + } + + return nil +} + +func FetchLogs(limit int) ([]LogEntry, error) { + lite_dbMutex.Lock() + defer lite_dbMutex.Unlock() + + query := `SELECT timestamp, level, message FROM logs ORDER BY timestamp DESC LIMIT ?` + rows, err := Lite_db.Query(query, limit) + if err != nil { + return nil, fmt.Errorf("Error fetching logs: %w", err) + } + defer rows.Close() + + // var logs []string + var logs []LogEntry + for rows.Next() { + // var message string + var logEntry LogEntry + if err := rows.Scan( &logEntry.Timestamp, &logEntry.Level, &logEntry.Message); err != nil { + return nil, fmt.Errorf("Error scanning row: %w", err) + } + logs = append(logs, logEntry) + } + + return logs, nil +} + +func CloseDB() { + if Lite_db != nil { + err := Lite_db.Close() + if err != nil { + log.Printf("Error closing database: %v", err) + } + } +} diff --git a/src/server/api/agentApi.go b/src/server/api/agentApi.go index 2f12685..a43bb41 100644 --- a/src/server/api/agentApi.go +++ b/src/server/api/agentApi.go @@ -6,6 +6,7 @@ import ( "encoding/json" "strconv" "log" + // "gontrol/src/logger" _ "github.com/go-sql-driver/mysql" ) diff --git a/src/server/websocket/websocketServer.go b/src/server/websocket/websocketServer.go index db08b9d..56a9479 100644 --- a/src/server/websocket/websocketServer.go +++ b/src/server/websocket/websocketServer.go @@ -3,6 +3,8 @@ package websocketserver import ( "database/sql" "encoding/json" + "fmt" + "gontrol/src/logger" "gontrol/src/randomname" "gontrol/src/server/api" "io" @@ -63,7 +65,10 @@ func registerAgent(agentName, agentId, agentIp, agentType string) error { defer resp.Body.Close() if resp.StatusCode == http.StatusCreated { - log.Printf("Agent %s successfully registered.", agentName) + logLine := fmt.Sprintf("Agent %s successfully registered.", agentName) + log.Printf(logLine) + // logLine = logger.ToLog(logLine) + logger.InsertLog(logger.Info, logLine) return nil } else if resp.StatusCode == http.StatusOK { log.Printf("Agent %s already registered.", agentName) @@ -81,14 +86,14 @@ func getAgentDetails(agentId string) (*api.Agent, error) { // var ids []string resp, err := http.Get(agentURL) if err != nil { - log.Printf("Failed to make GET request: %w", err) + log.Printf("Failed to make GET request: %s", err) return nil, err } defer resp.Body.Close() doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { - log.Printf("Failed to parse HTML: %w", err) + log.Printf("Failed to parse HTML: %s", err) return nil, err } @@ -98,7 +103,7 @@ func getAgentDetails(agentId string) (*api.Agent, error) { if strings.HasPrefix(text, "ID:") { agent.AgentID, err = strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(text, "ID:"))) if err != nil { - log.Printf("Converting string to integer failed in getAgentDetails(): %w", err) + log.Printf("Converting string to integer failed in getAgentDetails(): %s", err) } } else if strings.HasPrefix(text, "Name:") { agent.AgentName = strings.TrimSpace(strings.TrimPrefix(text, "Name:")) @@ -124,7 +129,7 @@ func getAgentIds() ([]string, error) { // var ids []string resp, err := http.Get(idURL) if err != nil { - log.Printf("Failed to make GET request: %w", err) + log.Printf("Failed to make GET request: %s", err) return nil, err } defer resp.Body.Close() @@ -136,13 +141,13 @@ func getAgentIds() ([]string, error) { body, err := io.ReadAll(resp.Body) if err != nil { - log.Printf("Failed to read response body: %w", err) + log.Printf("Failed to read response body: %s", err) return nil, err } var agentIds []string if err := json.Unmarshal(body, &agentIds); err != nil { - log.Printf("Failed to parse JSON response: %w", err) + log.Printf("Failed to parse JSON response: %s", err) return nil, err } @@ -182,7 +187,9 @@ func (wsh webSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){ return } - log.Printf("Agent %s connected: %s (%s)", agentId, agentName, agentIP) + logLine := fmt.Sprintf("Agent %s connected: %s (%s)", agentId, agentName, agentIP) + log.Printf(logLine) + logger.InsertLog(logger.Info, logLine) agentSocketsMutex.Lock() agentSockets[agentName] = c @@ -193,17 +200,22 @@ func (wsh webSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){ delete(agentSockets, agentName) agentSocketsMutex.Unlock() c.Close() - log.Printf("Agent disconnected: %s (%s)", agentName, agentIP) + logLine := fmt.Sprintf("Agent disconnected: %s (%s)", agentName, agentIP) + log.Printf(logLine) + logger.InsertLog(logger.Info, logLine) }() for { _, message, err := c.ReadMessage() if err != nil { - log.Printf("Error reading from agent %s: %v", agentName, err) + logLine := fmt.Sprintf("Error reading from agent %s: %v", agentName, err) + log.Printf(logLine) + logger.InsertLog(logger.Error, logLine) break } - log.Printf("Message from agent %s: %s", agentName, message) - + logLine := fmt.Sprintf("Message from agent %s: %s", agentName, message) + log.Printf(logLine) + logger.InsertLog(logger.Debug, logLine) if ch, ok := responseChannels.Load(agentName); ok { responseChan := ch.(chan string) @@ -274,8 +286,6 @@ var executeCommand http.HandlerFunc = func(w http.ResponseWriter, r *http.Reques } func Server() (*http.Server) { - - webSocketHandler := webSocketHandler { upgrader: websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { @@ -286,7 +296,7 @@ func Server() (*http.Server) { corsMiddleware := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") // Allow the WebUI origin + w.Header().Set("Access-Control-Allow-Origin", "*") // Allow the WebUI origin, this needs to be changed before prod w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, HX-Current-URL, HX-Request, HX-Target, HX-Trigger, HX-Trigger-Name") if r.Method == "OPTIONS" { diff --git a/templates/index.html b/templates/index.html index 4e12b85..ba16f96 100644 --- a/templates/index.html +++ b/templates/index.html @@ -10,6 +10,7 @@ g2: gommand & gontrol diff --git a/templates/logs.html b/templates/logs.html new file mode 100644 index 0000000..bafd305 --- /dev/null +++ b/templates/logs.html @@ -0,0 +1,24 @@ + + + + + + Web Server Logs + + + +

Web Server Logs

+ +
+ +
+ + + + + +