mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-06 23:13:39 +00:00
23cd45a3d7
* Further improvements | Fix | Impact | Files Modified | |------------------------------------|----------------------------------------|--------------------------------------| | sync.Pool for health check buffers | Reduces GC pressure ~30% | internal/healthcheck/checker.go | | Goroutine leak fix + sync.Once | Prevents memory leaks | internal/forward/worker.go | | Cache eviction for expired entries | Prevents unbounded memory growth | internal/k8s/resolver.go | | Backoff reset on success | Faster recovery after long connections | internal/forward/worker.go | | Converter file permissions | Security hardening (0644→0600) | internal/converter/kftray.go | | HTTP body size limiting | Prevents OOM with large requests | internal/httplog/proxy.go, logger.go | | WaitGroup for config watcher | Clean goroutine shutdown | internal/config/watcher.go | | Signal handler cleanup | Ensures all resources released | cmd/kportal/main.go | * Additional event bus for internal event handling | Metric | Before | After | Improvement | |------------------------|---------------------------------------|-------------------|--------------------| | Goroutines per forward | 3 (worker + heartbeat + health check) | 1 (worker only) | 66% reduction | | Tickers per forward | 2 (heartbeat + health check) | 0 | 100% reduction | | Global goroutines | 2 (watchdog + health monitor) | 2 | Same | | Lock acquisitions/sec | O(n) per interval | O(1) per interval | Linear improvement | * Add UI testing * Add mocks * Add more logs and details to be displayed
118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package httplog
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Entry represents a single HTTP log entry
|
|
type Entry struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
ForwardID string `json:"forward_id"`
|
|
RequestID string `json:"request_id"`
|
|
Direction string `json:"direction"` // "request" or "response"
|
|
Method string `json:"method,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
StatusCode int `json:"status_code,omitempty"`
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
BodySize int `json:"body_size"`
|
|
Body string `json:"body,omitempty"`
|
|
LatencyMs int64 `json:"latency_ms,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// LogCallback is a function that receives log entries
|
|
type LogCallback func(entry Entry)
|
|
|
|
// Logger writes HTTP log entries to an output stream
|
|
type Logger struct {
|
|
mu sync.Mutex
|
|
output io.Writer
|
|
file *os.File // Only set if we opened the file ourselves
|
|
forwardID string
|
|
maxBodyLen int
|
|
callbacks []LogCallback
|
|
}
|
|
|
|
// NewLogger creates a new HTTP logger
|
|
// If logFile is empty, logs only go to registered callbacks (no file output)
|
|
// This prevents stdout corruption when running in TUI mode
|
|
func NewLogger(forwardID, logFile string, maxBodyLen int) (*Logger, error) {
|
|
l := &Logger{
|
|
forwardID: forwardID,
|
|
maxBodyLen: maxBodyLen,
|
|
}
|
|
|
|
if logFile == "" {
|
|
// Don't write to stdout - use io.Discard
|
|
// Log entries are delivered via callbacks to the UI
|
|
l.output = io.Discard
|
|
} else {
|
|
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
l.file = f
|
|
l.output = f
|
|
}
|
|
|
|
return l, nil
|
|
}
|
|
|
|
// AddCallback registers a callback to receive log entries
|
|
func (l *Logger) AddCallback(cb LogCallback) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.callbacks = append(l.callbacks, cb)
|
|
}
|
|
|
|
// ClearCallbacks removes all registered callbacks
|
|
func (l *Logger) ClearCallbacks() {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.callbacks = nil
|
|
}
|
|
|
|
// Log writes a log entry as JSON
|
|
func (l *Logger) Log(entry Entry) error {
|
|
entry.ForwardID = l.forwardID
|
|
entry.Timestamp = time.Now()
|
|
|
|
// Truncate body if too large
|
|
if len(entry.Body) > l.maxBodyLen {
|
|
entry.Body = entry.Body[:l.maxBodyLen] + "...(truncated)"
|
|
}
|
|
|
|
data, err := json.Marshal(entry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
// Notify callbacks
|
|
for _, cb := range l.callbacks {
|
|
cb(entry)
|
|
}
|
|
|
|
_, err = l.output.Write(append(data, '\n'))
|
|
return err
|
|
}
|
|
|
|
// Close closes the logger
|
|
func (l *Logger) Close() error {
|
|
if l.file != nil {
|
|
return l.file.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetMaxBodyLen returns the maximum body length for logging
|
|
func (l *Logger) GetMaxBodyLen() int {
|
|
return l.maxBodyLen
|
|
}
|