- Add new `app` package to manage application initialization and lifecycle - Refactor `main.go` to use new application management approach - Implement graceful shutdown with context timeout and signal handling - Add dependency injection container initialization - Enhance logging with configurable log levels and structured logging - Update configuration loading and server initialization process - Modify Jitsi configuration in `.env` for custom deployment - Improve error handling and logging throughout application startup - Centralize application startup and shutdown logic in single package Introduces a more robust and flexible application management system with improved initialization, logging, and shutdown capabilities.
347 lines
9.1 KiB
Go
347 lines
9.1 KiB
Go
package logger
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
// LogLevel represents the severity level of a log entry
|
|
type LogLevel string
|
|
|
|
const (
|
|
DEBUG LogLevel = "DEBUG"
|
|
INFO LogLevel = "INFO"
|
|
WARN LogLevel = "WARN"
|
|
ERROR LogLevel = "ERROR"
|
|
FATAL LogLevel = "FATAL"
|
|
)
|
|
|
|
// LogEntry represents a structured log entry
|
|
type LogEntry struct {
|
|
Timestamp string `json:"timestamp"`
|
|
Level LogLevel `json:"level"`
|
|
Message string `json:"message"`
|
|
Service string `json:"service"`
|
|
TraceID string `json:"trace_id,omitempty"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
Fields map[string]interface{} `json:"fields,omitempty"`
|
|
Error *ErrorDetails `json:"error,omitempty"`
|
|
Source *SourceLocation `json:"source,omitempty"`
|
|
}
|
|
|
|
// ErrorDetails contains error-specific information
|
|
type ErrorDetails struct {
|
|
Type string `json:"type"`
|
|
Message string `json:"message"`
|
|
StackTrace string `json:"stack_trace,omitempty"`
|
|
}
|
|
|
|
// SourceLocation contains source code location information
|
|
type SourceLocation struct {
|
|
File string `json:"file"`
|
|
Line int `json:"line"`
|
|
Function string `json:"function"`
|
|
}
|
|
|
|
// Logger provides structured logging capabilities
|
|
type Logger struct {
|
|
service string
|
|
level LogLevel
|
|
}
|
|
|
|
// New creates a new logger instance
|
|
func New(service string) *Logger {
|
|
return &Logger{
|
|
service: service,
|
|
level: INFO, // Default level
|
|
}
|
|
}
|
|
|
|
// SetLevel sets the minimum log level
|
|
func (l *Logger) SetLevel(level LogLevel) {
|
|
l.level = level
|
|
}
|
|
|
|
// GetLevel returns the current log level
|
|
func (l *Logger) GetLevel() LogLevel {
|
|
return l.level
|
|
}
|
|
|
|
// IsLevelEnabled checks if a log level is enabled
|
|
func (l *Logger) IsLevelEnabled(level LogLevel) bool {
|
|
return l.shouldLog(level)
|
|
}
|
|
|
|
// Debug logs a debug message
|
|
func (l *Logger) Debug(message string, fields ...map[string]interface{}) {
|
|
if l.shouldLog(DEBUG) {
|
|
l.log(DEBUG, message, nil, fields...)
|
|
}
|
|
}
|
|
|
|
// Info logs an info message
|
|
func (l *Logger) Info(message string, fields ...map[string]interface{}) {
|
|
if l.shouldLog(INFO) {
|
|
l.log(INFO, message, nil, fields...)
|
|
}
|
|
}
|
|
|
|
// Warn logs a warning message
|
|
func (l *Logger) Warn(message string, fields ...map[string]interface{}) {
|
|
if l.shouldLog(WARN) {
|
|
l.log(WARN, message, nil, fields...)
|
|
}
|
|
}
|
|
|
|
// Error logs an error message
|
|
func (l *Logger) Error(message string, err error, fields ...map[string]interface{}) {
|
|
if l.shouldLog(ERROR) {
|
|
l.log(ERROR, message, err, fields...)
|
|
}
|
|
}
|
|
|
|
// Fatal logs a fatal message and exits
|
|
func (l *Logger) Fatal(message string, err error, fields ...map[string]interface{}) {
|
|
l.log(FATAL, message, err, fields...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// WithContext creates a logger with context information
|
|
func (l *Logger) WithContext(ctx context.Context) *ContextLogger {
|
|
return &ContextLogger{
|
|
logger: l,
|
|
ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// WithFields creates a logger with predefined fields
|
|
func (l *Logger) WithFields(fields map[string]interface{}) *FieldLogger {
|
|
return &FieldLogger{
|
|
logger: l,
|
|
fields: fields,
|
|
}
|
|
}
|
|
|
|
// log performs the actual logging
|
|
func (l *Logger) log(level LogLevel, message string, err error, fields ...map[string]interface{}) {
|
|
entry := LogEntry{
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
Level: level,
|
|
Message: message,
|
|
Service: l.service,
|
|
}
|
|
|
|
// Add fields if provided
|
|
if len(fields) > 0 && fields[0] != nil {
|
|
entry.Fields = fields[0]
|
|
}
|
|
|
|
// Add error details if provided
|
|
if err != nil {
|
|
entry.Error = &ErrorDetails{
|
|
Type: fmt.Sprintf("%T", err),
|
|
Message: err.Error(),
|
|
}
|
|
|
|
// Add stack trace for errors and fatal logs
|
|
if level == ERROR || level == FATAL {
|
|
entry.Error.StackTrace = getStackTrace()
|
|
}
|
|
}
|
|
|
|
// Add source location for errors and fatal logs
|
|
if level == ERROR || level == FATAL {
|
|
entry.Source = getSourceLocation(3) // Skip 3 frames: log, Error/Fatal, caller
|
|
}
|
|
|
|
// Output the log entry
|
|
l.output(entry)
|
|
}
|
|
|
|
// shouldLog checks if the message should be logged based on level
|
|
func (l *Logger) shouldLog(level LogLevel) bool {
|
|
levels := map[LogLevel]int{
|
|
DEBUG: 0,
|
|
INFO: 1,
|
|
WARN: 2,
|
|
ERROR: 3,
|
|
FATAL: 4,
|
|
}
|
|
|
|
return levels[level] >= levels[l.level]
|
|
}
|
|
|
|
// output writes the log entry to the output
|
|
func (l *Logger) output(entry LogEntry) {
|
|
jsonBytes, err := json.Marshal(entry)
|
|
if err != nil {
|
|
// Fallback to standard logging if JSON marshaling fails
|
|
log.Printf("LOGGER_ERROR: Failed to marshal log entry: %v", err)
|
|
log.Printf("%s [%s] %s: %s", entry.Timestamp, entry.Level, entry.Service, entry.Message)
|
|
return
|
|
}
|
|
|
|
fmt.Println(string(jsonBytes))
|
|
}
|
|
|
|
// getStackTrace returns the current stack trace
|
|
func getStackTrace() string {
|
|
buf := make([]byte, 4096)
|
|
n := runtime.Stack(buf, false)
|
|
return string(buf[:n])
|
|
}
|
|
|
|
// getSourceLocation returns the source location information
|
|
func getSourceLocation(skip int) *SourceLocation {
|
|
pc, file, line, ok := runtime.Caller(skip)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
fn := runtime.FuncForPC(pc)
|
|
if fn == nil {
|
|
return nil
|
|
}
|
|
|
|
return &SourceLocation{
|
|
File: file,
|
|
Line: line,
|
|
Function: fn.Name(),
|
|
}
|
|
}
|
|
|
|
// ContextLogger wraps a logger with context information
|
|
type ContextLogger struct {
|
|
logger *Logger
|
|
ctx context.Context
|
|
}
|
|
|
|
// Debug logs a debug message with context
|
|
func (cl *ContextLogger) Debug(message string, fields ...map[string]interface{}) {
|
|
cl.logWithContext(DEBUG, message, nil, fields...)
|
|
}
|
|
|
|
// Info logs an info message with context
|
|
func (cl *ContextLogger) Info(message string, fields ...map[string]interface{}) {
|
|
cl.logWithContext(INFO, message, nil, fields...)
|
|
}
|
|
|
|
// Warn logs a warning message with context
|
|
func (cl *ContextLogger) Warn(message string, fields ...map[string]interface{}) {
|
|
cl.logWithContext(WARN, message, nil, fields...)
|
|
}
|
|
|
|
// Error logs an error message with context
|
|
func (cl *ContextLogger) Error(message string, err error, fields ...map[string]interface{}) {
|
|
cl.logWithContext(ERROR, message, err, fields...)
|
|
}
|
|
|
|
// logWithContext logs with context information
|
|
func (cl *ContextLogger) logWithContext(level LogLevel, message string, err error, fields ...map[string]interface{}) {
|
|
// Extract context information
|
|
contextFields := make(map[string]interface{})
|
|
|
|
// Add trace ID if available
|
|
if traceID := cl.ctx.Value("trace_id"); traceID != nil {
|
|
contextFields["trace_id"] = traceID
|
|
}
|
|
|
|
// Add user ID if available
|
|
if userID := cl.ctx.Value("user_id"); userID != nil {
|
|
contextFields["user_id"] = userID
|
|
}
|
|
|
|
// Merge with provided fields
|
|
if len(fields) > 0 && fields[0] != nil {
|
|
for k, v := range fields[0] {
|
|
contextFields[k] = v
|
|
}
|
|
}
|
|
|
|
cl.logger.log(level, message, err, contextFields)
|
|
}
|
|
|
|
// FieldLogger wraps a logger with predefined fields
|
|
type FieldLogger struct {
|
|
logger *Logger
|
|
fields map[string]interface{}
|
|
}
|
|
|
|
// Debug logs a debug message with predefined fields
|
|
func (fl *FieldLogger) Debug(message string, additionalFields ...map[string]interface{}) {
|
|
fl.logWithFields(DEBUG, message, nil, additionalFields...)
|
|
}
|
|
|
|
// Info logs an info message with predefined fields
|
|
func (fl *FieldLogger) Info(message string, additionalFields ...map[string]interface{}) {
|
|
fl.logWithFields(INFO, message, nil, additionalFields...)
|
|
}
|
|
|
|
// Warn logs a warning message with predefined fields
|
|
func (fl *FieldLogger) Warn(message string, additionalFields ...map[string]interface{}) {
|
|
fl.logWithFields(WARN, message, nil, additionalFields...)
|
|
}
|
|
|
|
// Error logs an error message with predefined fields
|
|
func (fl *FieldLogger) Error(message string, err error, additionalFields ...map[string]interface{}) {
|
|
fl.logWithFields(ERROR, message, err, additionalFields...)
|
|
}
|
|
|
|
// logWithFields logs with predefined fields
|
|
func (fl *FieldLogger) logWithFields(level LogLevel, message string, err error, additionalFields ...map[string]interface{}) {
|
|
// Merge predefined fields with additional fields
|
|
mergedFields := make(map[string]interface{})
|
|
|
|
// Add predefined fields
|
|
for k, v := range fl.fields {
|
|
mergedFields[k] = v
|
|
}
|
|
|
|
// Add additional fields
|
|
if len(additionalFields) > 0 && additionalFields[0] != nil {
|
|
for k, v := range additionalFields[0] {
|
|
mergedFields[k] = v
|
|
}
|
|
}
|
|
|
|
fl.logger.log(level, message, err, mergedFields)
|
|
}
|
|
|
|
// Global logger instance
|
|
var globalLogger = New("app")
|
|
|
|
// SetGlobalLevel sets the global logger level
|
|
func SetGlobalLevel(level LogLevel) {
|
|
globalLogger.SetLevel(level)
|
|
}
|
|
|
|
// Debug logs a debug message using the global logger
|
|
func Debug(message string, fields ...map[string]interface{}) {
|
|
globalLogger.Debug(message, fields...)
|
|
}
|
|
|
|
// Info logs an info message using the global logger
|
|
func Info(message string, fields ...map[string]interface{}) {
|
|
globalLogger.Info(message, fields...)
|
|
}
|
|
|
|
// Warn logs a warning message using the global logger
|
|
func Warn(message string, fields ...map[string]interface{}) {
|
|
globalLogger.Warn(message, fields...)
|
|
}
|
|
|
|
// Error logs an error message using the global logger
|
|
func Error(message string, err error, fields ...map[string]interface{}) {
|
|
globalLogger.Error(message, err, fields...)
|
|
}
|
|
|
|
// Fatal logs a fatal message using the global logger and exits
|
|
func Fatal(message string, err error, fields ...map[string]interface{}) {
|
|
globalLogger.Fatal(message, err, fields...)
|
|
}
|