feat(logging): added in db and logging with websocket
This commit is contained in:
111
backend/cmd/services/logging/createLog.go
Normal file
111
backend/cmd/services/logging/createLog.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"lst.net/utils/db"
|
||||
)
|
||||
|
||||
type CustomLogger struct {
|
||||
consoleLogger zerolog.Logger
|
||||
}
|
||||
|
||||
// New creates a configured CustomLogger.
|
||||
func New() *CustomLogger {
|
||||
// Colorized console output
|
||||
consoleWriter := zerolog.ConsoleWriter{
|
||||
Out: os.Stderr,
|
||||
TimeFormat: "2006-01-02 15:04:05",
|
||||
}
|
||||
|
||||
return &CustomLogger{
|
||||
consoleLogger: zerolog.New(consoleWriter).
|
||||
With().
|
||||
Timestamp().
|
||||
Logger(),
|
||||
}
|
||||
}
|
||||
|
||||
func PrettyFormat(level, message string, metadata map[string]interface{}) string {
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
base := fmt.Sprintf("[%s] %s| Message: %s", strings.ToUpper(level), timestamp, message)
|
||||
|
||||
if len(metadata) > 0 {
|
||||
metaJSON, _ := json.Marshal(metadata)
|
||||
return fmt.Sprintf("%s | Metadata: %s", base, string(metaJSON))
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func (l *CustomLogger) logToPostgres(level, message, service string, metadata map[string]interface{}) {
|
||||
err := db.CreateLog(level, message, service, metadata)
|
||||
if err != nil {
|
||||
// Fallback to console if DB fails
|
||||
log.Error().Err(err).Msg("Failed to write log to PostgreSQL")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Level-Specific Methods ---
|
||||
|
||||
func (l *CustomLogger) Info(message, service string, fields map[string]interface{}) {
|
||||
l.consoleLogger.Info().Fields(fields).Msg(message)
|
||||
l.logToPostgres("info", message, service, fields)
|
||||
|
||||
PostLog(PrettyFormat("info", message, fields)) // Broadcast pretty message
|
||||
}
|
||||
|
||||
func (l *CustomLogger) Warn(message, service string, fields map[string]interface{}) {
|
||||
l.consoleLogger.Error().Fields(fields).Msg(message)
|
||||
l.logToPostgres("warn", message, service, fields)
|
||||
|
||||
PostLog(PrettyFormat("warn", message, fields)) // Broadcast pretty message
|
||||
|
||||
// Custom logic for errors (e.g., alerting)
|
||||
if len(fields) > 0 {
|
||||
l.consoleLogger.Warn().Msg("Additional error context captured")
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CustomLogger) Error(message, service string, fields map[string]interface{}) {
|
||||
l.consoleLogger.Error().Fields(fields).Msg(message)
|
||||
l.logToPostgres("error", message, service, fields)
|
||||
|
||||
PostLog(PrettyFormat("error", message, fields)) // Broadcast pretty message
|
||||
|
||||
// Custom logic for errors (e.g., alerting)
|
||||
if len(fields) > 0 {
|
||||
l.consoleLogger.Warn().Msg("Additional error context captured")
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CustomLogger) Panic(message, service string, fields map[string]interface{}) {
|
||||
// Log to console (colored, with fields)
|
||||
l.consoleLogger.Error().
|
||||
Str("service", service).
|
||||
Fields(fields).
|
||||
Msg(message + " (PANIC)") // Explicitly mark as panic
|
||||
|
||||
// Log to PostgreSQL (sync to ensure it's saved before crashing)
|
||||
err := db.CreateLog("panic", message, service, fields) // isCritical=true
|
||||
if err != nil {
|
||||
l.consoleLogger.Error().Err(err).Msg("Failed to save panic log to PostgreSQL")
|
||||
}
|
||||
|
||||
// Additional context (optional)
|
||||
if len(fields) > 0 {
|
||||
l.consoleLogger.Warn().Msg("Additional panic context captured")
|
||||
}
|
||||
|
||||
panic(message)
|
||||
}
|
||||
|
||||
func (l *CustomLogger) Debug(message, service string, fields map[string]interface{}) {
|
||||
l.consoleLogger.Debug().Fields(fields).Msg(message)
|
||||
l.logToPostgres("debug", message, service, fields)
|
||||
}
|
||||
12
backend/cmd/services/logging/logger.go
Normal file
12
backend/cmd/services/logging/logger.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func RegisterLoggerRoutes(l *gin.Engine, baseUrl string) {
|
||||
|
||||
configGroup := l.Group(baseUrl + "/api/logger")
|
||||
configGroup.GET("/logs", GetLogs)
|
||||
|
||||
}
|
||||
132
backend/cmd/services/logging/logs_get_route.go
Normal file
132
backend/cmd/services/logging/logs_get_route.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
logChannel = make(chan string, 1000) // Buffered channel for new logs
|
||||
wsClients = make(map[*websocket.Conn]bool)
|
||||
wsClientsMux sync.Mutex
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
fmt.Println("Origin:", r.Header.Get("Origin"))
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// PostLog sends a new log to all connected SSE clients
|
||||
func PostLog(message string) {
|
||||
// Send to SSE channel
|
||||
select {
|
||||
case logChannel <- message:
|
||||
log.Printf("Published to SSE: %s", message)
|
||||
default:
|
||||
log.Printf("DROPPED SSE message (channel full): %s", message)
|
||||
}
|
||||
|
||||
wsClientsMux.Lock()
|
||||
defer wsClientsMux.Unlock()
|
||||
for client := range wsClients {
|
||||
err := client.WriteMessage(websocket.TextMessage, []byte(message))
|
||||
if err != nil {
|
||||
client.Close()
|
||||
delete(wsClients, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetLogs(c *gin.Context) {
|
||||
// Check if it's a WebSocket request
|
||||
if websocket.IsWebSocketUpgrade(c.Request) {
|
||||
handleWebSocket(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, handle as SSE
|
||||
handleSSE(c)
|
||||
}
|
||||
|
||||
func handleSSE(c *gin.Context) {
|
||||
log := New()
|
||||
log.Info("SSE connection established", "logger", map[string]interface{}{
|
||||
"endpoint": "/api/logger/logs",
|
||||
"client_ip": c.ClientIP(),
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
})
|
||||
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
|
||||
flusher, ok := c.Writer.(http.Flusher)
|
||||
if !ok {
|
||||
log.Info("SSE not supported", "logger", nil)
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
notify := c.Writer.CloseNotify()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-notify:
|
||||
log.Info("SSE client disconnected", "logger", nil)
|
||||
return
|
||||
case message := <-logChannel:
|
||||
fmt.Fprintf(c.Writer, "data: %s\n\n", message)
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleWebSocket(c *gin.Context) {
|
||||
log := New()
|
||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Error("WebSocket upgrade failed", "logger", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Register client
|
||||
wsClientsMux.Lock()
|
||||
wsClients[conn] = true
|
||||
wsClientsMux.Unlock()
|
||||
|
||||
defer func() {
|
||||
wsClientsMux.Lock()
|
||||
delete(wsClients, conn)
|
||||
wsClientsMux.Unlock()
|
||||
conn.Close()
|
||||
|
||||
log.Info("WebSocket client disconnected", "logger", map[string]interface{}{})
|
||||
}()
|
||||
|
||||
// Keep connection alive (or optionally echo, or wait for pings)
|
||||
for {
|
||||
// Can just read to keep the connection alive
|
||||
if _, _, err := conn.NextReader(); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func sendRecentLogs(conn *websocket.Conn) {
|
||||
// // Implement your logic to get recent logs from DB or buffer
|
||||
// recentLogs := getLast20Logs()
|
||||
// for _, log := range recentLogs {
|
||||
// conn.WriteMessage(websocket.TextMessage, []byte(log))
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user