Initial commit: logcorrelator with unified packaging (DEB + RPM using fpm)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Jacquin Antoine
2026-02-27 15:31:46 +01:00
commit 8fc14c1e94
35 changed files with 4829 additions and 0 deletions

View File

@ -0,0 +1,85 @@
package observability
import (
"log"
"os"
"sync"
)
// Logger provides structured logging.
type Logger struct {
mu sync.Mutex
logger *log.Logger
prefix string
fields map[string]any
}
// NewLogger creates a new logger.
func NewLogger(prefix string) *Logger {
return &Logger{
logger: log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds),
prefix: prefix,
fields: make(map[string]any),
}
}
// WithFields returns a new logger with additional fields.
func (l *Logger) WithFields(fields map[string]any) *Logger {
newLogger := &Logger{
logger: l.logger,
prefix: l.prefix,
fields: make(map[string]any),
}
for k, v := range l.fields {
newLogger.fields[k] = v
}
for k, v := range fields {
newLogger.fields[k] = v
}
return newLogger
}
// Info logs an info message.
func (l *Logger) Info(msg string) {
l.mu.Lock()
defer l.mu.Unlock()
l.log("INFO", msg)
}
// Error logs an error message.
func (l *Logger) Error(msg string, err error) {
l.mu.Lock()
defer l.mu.Unlock()
if err != nil {
l.log("ERROR", msg+" "+err.Error())
} else {
l.log("ERROR", msg)
}
}
// Debug logs a debug message.
func (l *Logger) Debug(msg string) {
l.mu.Lock()
defer l.mu.Unlock()
l.log("DEBUG", msg)
}
func (l *Logger) log(level, msg string) {
prefix := l.prefix
if prefix != "" {
prefix = "[" + prefix + "] "
}
l.logger.SetPrefix(prefix + level + " ")
var args []any
for k, v := range l.fields {
args = append(args, k, v)
}
if len(args) > 0 {
l.logger.Printf(msg+" %+v", args...)
} else {
l.logger.Print(msg)
}
}

View File

@ -0,0 +1,111 @@
package observability
import (
"bytes"
"io"
"os"
"strings"
"testing"
)
func TestNewLogger(t *testing.T) {
logger := NewLogger("test")
if logger == nil {
t.Fatal("expected non-nil logger")
}
if logger.prefix != "test" {
t.Errorf("expected prefix 'test', got %s", logger.prefix)
}
}
func TestLogger_Info(t *testing.T) {
// Capture stderr
oldStderr := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w
logger := NewLogger("test")
logger.Info("test message")
w.Close()
os.Stderr = oldStderr
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
if !strings.Contains(output, "INFO") {
t.Error("expected INFO in output")
}
if !strings.Contains(output, "test message") {
t.Error("expected 'test message' in output")
}
}
func TestLogger_Error(t *testing.T) {
oldStderr := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w
logger := NewLogger("test")
logger.Error("error message", nil)
w.Close()
os.Stderr = oldStderr
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
if !strings.Contains(output, "ERROR") {
t.Error("expected ERROR in output")
}
if !strings.Contains(output, "error message") {
t.Error("expected 'error message' in output")
}
}
func TestLogger_Debug(t *testing.T) {
oldStderr := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w
logger := NewLogger("test")
logger.Debug("debug message")
w.Close()
os.Stderr = oldStderr
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
if !strings.Contains(output, "DEBUG") {
t.Error("expected DEBUG in output")
}
if !strings.Contains(output, "debug message") {
t.Error("expected 'debug message' in output")
}
}
func TestLogger_WithFields(t *testing.T) {
logger := NewLogger("test")
fieldsLogger := logger.WithFields(map[string]any{
"key1": "value1",
"key2": 42,
})
if fieldsLogger == logger {
t.Error("expected different logger instance")
}
if len(fieldsLogger.fields) != 2 {
t.Errorf("expected 2 fields, got %d", len(fieldsLogger.fields))
}
}
func TestLogger_Name(t *testing.T) {
logger := NewLogger("myservice")
if logger.prefix != "myservice" {
t.Errorf("expected prefix 'myservice', got %s", logger.prefix)
}
}