Files
honeydany/app/logger.go
T

124 lines
2.9 KiB
Go
Raw Normal View History

2025-09-28 06:48:03 +01:00
package app
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
_ "github.com/mattn/go-sqlite3"
)
// Record represents a captured event
type Record struct {
Timestamp time.Time `json:"timestamp"`
RemoteAddr string `json:"remote_addr"`
RemotePort string `json:"remote_port"`
Service string `json:"service"`
Details map[string]string `json:"details,omitempty"`
RawPayload string `json:"raw_payload,omitempty"`
}
// Logger handles output to file / stdout / sqlite
type Logger struct {
mode string
mu sync.Mutex
f *os.File
db *sql.DB
}
// NewLogger creates and initializes a Logger
func NewLogger(cfg Config) (*Logger, error) {
l := &Logger{mode: cfg.LogMode}
switch cfg.LogMode {
case "stdout":
// nothing to open
case "sqlite":
if err := os.MkdirAll(filepath.Dir(cfg.LogPath), 0755); err != nil {
return nil, fmt.Errorf("create db dir: %w", err)
}
db, err := sql.Open("sqlite3", cfg.LogPath)
if err != nil {
return nil, fmt.Errorf("open sqlite: %w", err)
}
l.db = db
if err := l.ensureSQLiteSchema(); err != nil {
return nil, err
}
default: // file
if err := os.MkdirAll(filepath.Dir(cfg.LogPath), 0700); err != nil {
return nil, fmt.Errorf("create log dir: %w", err)
}
f, err := os.OpenFile(cfg.LogPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return nil, fmt.Errorf("open log file: %w", err)
}
l.f = f
}
return l, nil
}
func (l *Logger) ensureSQLiteSchema() error {
if l.db == nil {
return nil
}
q := `CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
remote_addr TEXT,
remote_port TEXT,
service TEXT,
details TEXT,
raw_payload TEXT
)`
_, err := l.db.Exec(q)
return err
}
// Close closes any underlying resources
func (l *Logger) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if l.f != nil {
_ = l.f.Sync()
if err := l.f.Close(); err != nil {
return err
}
}
if l.db != nil {
return l.db.Close()
}
return nil
}
// Log writes a Record according to the configured backend
func (l *Logger) Log(r Record) error {
l.mu.Lock()
defer l.mu.Unlock()
switch l.mode {
case "stdout":
b, _ := json.Marshal(r)
fmt.Println(string(b))
return nil
case "sqlite":
if l.db == nil {
return fmt.Errorf("sqlite DB not open")
}
detailsB, _ := json.Marshal(r.Details)
_, err := l.db.Exec(`INSERT INTO logs (timestamp, remote_addr, remote_port, service, details, raw_payload) VALUES (?, ?, ?, ?, ?, ?)`, r.Timestamp.UTC().Format(time.RFC3339Nano), r.RemoteAddr, r.RemotePort, r.Service, string(detailsB), r.RawPayload)
return err
default: // file
if l.f == nil {
return fmt.Errorf("file logger not initialized")
}
enc := json.NewEncoder(l.f)
if err := enc.Encode(r); err != nil {
return err
}
return l.f.Sync()
}
}