Perbaikan Middelware dan tool generete logger
This commit is contained in:
378
pkg/logger/logger.go
Normal file
378
pkg/logger/logger.go
Normal file
@@ -0,0 +1,378 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel represents the severity level of a log message
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
DEBUG LogLevel = iota
|
||||
INFO
|
||||
WARN
|
||||
ERROR
|
||||
FATAL
|
||||
)
|
||||
|
||||
var (
|
||||
levelStrings = map[LogLevel]string{
|
||||
DEBUG: "DEBUG",
|
||||
INFO: "INFO",
|
||||
WARN: "WARN",
|
||||
ERROR: "ERROR",
|
||||
FATAL: "FATAL",
|
||||
}
|
||||
|
||||
stringLevels = map[string]LogLevel{
|
||||
"DEBUG": DEBUG,
|
||||
"INFO": INFO,
|
||||
"WARN": WARN,
|
||||
"ERROR": ERROR,
|
||||
"FATAL": FATAL,
|
||||
}
|
||||
)
|
||||
|
||||
// Logger represents a structured logger instance
|
||||
type Logger struct {
|
||||
serviceName string
|
||||
level LogLevel
|
||||
output *log.Logger
|
||||
mu sync.Mutex
|
||||
jsonFormat bool
|
||||
}
|
||||
|
||||
// LogEntry represents a structured log entry
|
||||
type LogEntry struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
Level string `json:"level"`
|
||||
Service string `json:"service"`
|
||||
Message string `json:"message"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
CorrelationID string `json:"correlation_id,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Fields map[string]interface{} `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// New creates a new logger instance
|
||||
func New(serviceName string, level LogLevel, jsonFormat bool) *Logger {
|
||||
return &Logger{
|
||||
serviceName: serviceName,
|
||||
level: level,
|
||||
output: log.New(os.Stdout, "", 0),
|
||||
jsonFormat: jsonFormat,
|
||||
}
|
||||
}
|
||||
|
||||
// NewFromConfig creates a new logger from configuration
|
||||
func NewFromConfig(cfg Config) *Logger {
|
||||
level := INFO
|
||||
if l, exists := stringLevels[strings.ToUpper(cfg.Level)]; exists {
|
||||
level = l
|
||||
}
|
||||
|
||||
return New(cfg.Service, level, cfg.JSONFormat)
|
||||
}
|
||||
|
||||
// Default creates a default logger instance
|
||||
func Default() *Logger {
|
||||
return New("api-service", INFO, false)
|
||||
}
|
||||
|
||||
// WithService returns a new logger with the specified service name
|
||||
func (l *Logger) WithService(serviceName string) *Logger {
|
||||
return &Logger{
|
||||
serviceName: serviceName,
|
||||
level: l.level,
|
||||
output: l.output,
|
||||
jsonFormat: l.jsonFormat,
|
||||
}
|
||||
}
|
||||
|
||||
// SetLevel sets the log level for the logger
|
||||
func (l *Logger) SetLevel(level LogLevel) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.level = level
|
||||
}
|
||||
|
||||
// SetJSONFormat sets whether to output logs in JSON format
|
||||
func (l *Logger) SetJSONFormat(jsonFormat bool) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.jsonFormat = jsonFormat
|
||||
}
|
||||
|
||||
// Debug logs a debug message
|
||||
func (l *Logger) Debug(msg string, fields ...map[string]interface{}) {
|
||||
l.log(DEBUG, msg, nil, fields...)
|
||||
}
|
||||
|
||||
// Debugf logs a formatted debug message
|
||||
func (l *Logger) Debugf(format string, args ...interface{}) {
|
||||
l.log(DEBUG, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Info logs an info message
|
||||
func (l *Logger) Info(msg string, fields ...map[string]interface{}) {
|
||||
l.log(INFO, msg, nil, fields...)
|
||||
}
|
||||
|
||||
// Infof logs a formatted info message
|
||||
func (l *Logger) Infof(format string, args ...interface{}) {
|
||||
l.log(INFO, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Warn logs a warning message
|
||||
func (l *Logger) Warn(msg string, fields ...map[string]interface{}) {
|
||||
l.log(WARN, msg, nil, fields...)
|
||||
}
|
||||
|
||||
// Warnf logs a formatted warning message
|
||||
func (l *Logger) Warnf(format string, args ...interface{}) {
|
||||
l.log(WARN, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Error logs an error message
|
||||
func (l *Logger) Error(msg string, fields ...map[string]interface{}) {
|
||||
l.log(ERROR, msg, nil, fields...)
|
||||
}
|
||||
|
||||
// Errorf logs a formatted error message
|
||||
func (l *Logger) Errorf(format string, args ...interface{}) {
|
||||
l.log(ERROR, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Fatal logs a fatal message and exits the program
|
||||
func (l *Logger) Fatal(msg string, fields ...map[string]interface{}) {
|
||||
l.log(FATAL, msg, nil, fields...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf logs a formatted fatal message and exits the program
|
||||
func (l *Logger) Fatalf(format string, args ...interface{}) {
|
||||
l.log(FATAL, fmt.Sprintf(format, args...), nil)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// WithRequestID returns a new logger with the specified request ID
|
||||
func (l *Logger) WithRequestID(requestID string) *Logger {
|
||||
return l.withField("request_id", requestID)
|
||||
}
|
||||
|
||||
// WithCorrelationID returns a new logger with the specified correlation ID
|
||||
func (l *Logger) WithCorrelationID(correlationID string) *Logger {
|
||||
return l.withField("correlation_id", correlationID)
|
||||
}
|
||||
|
||||
// WithField returns a new logger with an additional field
|
||||
func (l *Logger) WithField(key string, value interface{}) *Logger {
|
||||
return l.withField(key, value)
|
||||
}
|
||||
|
||||
// WithFields returns a new logger with additional fields
|
||||
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
|
||||
return &Logger{
|
||||
serviceName: l.serviceName,
|
||||
level: l.level,
|
||||
output: l.output,
|
||||
jsonFormat: l.jsonFormat,
|
||||
}
|
||||
}
|
||||
|
||||
// LogDuration logs the duration of an operation
|
||||
func (l *Logger) LogDuration(start time.Time, operation string, fields ...map[string]interface{}) {
|
||||
duration := time.Since(start)
|
||||
l.Info(fmt.Sprintf("%s completed", operation), append(fields, map[string]interface{}{
|
||||
"duration": duration.String(),
|
||||
"duration_ms": duration.Milliseconds(),
|
||||
})...)
|
||||
}
|
||||
|
||||
// log is the internal logging method
|
||||
func (l *Logger) log(level LogLevel, msg string, duration *time.Duration, fields ...map[string]interface{}) {
|
||||
if level < l.level {
|
||||
return
|
||||
}
|
||||
|
||||
// Get caller information
|
||||
_, file, line, ok := runtime.Caller(3) // Adjust caller depth
|
||||
var callerFile string
|
||||
var callerLine int
|
||||
if ok {
|
||||
// Shorten file path
|
||||
parts := strings.Split(file, "/")
|
||||
if len(parts) > 2 {
|
||||
callerFile = strings.Join(parts[len(parts)-2:], "/")
|
||||
} else {
|
||||
callerFile = file
|
||||
}
|
||||
callerLine = line
|
||||
}
|
||||
|
||||
// Merge all fields
|
||||
mergedFields := make(map[string]interface{})
|
||||
for _, f := range fields {
|
||||
for k, v := range f {
|
||||
mergedFields[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
entry := LogEntry{
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Level: levelStrings[level],
|
||||
Service: l.serviceName,
|
||||
Message: msg,
|
||||
File: callerFile,
|
||||
Line: callerLine,
|
||||
Fields: mergedFields,
|
||||
}
|
||||
|
||||
if duration != nil {
|
||||
entry.Duration = duration.String()
|
||||
}
|
||||
|
||||
if l.jsonFormat {
|
||||
l.outputJSON(entry)
|
||||
} else {
|
||||
l.outputText(entry)
|
||||
}
|
||||
|
||||
if level == FATAL {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// outputJSON outputs the log entry in JSON format
|
||||
func (l *Logger) outputJSON(entry LogEntry) {
|
||||
jsonData, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
// Fallback to text output if JSON marshaling fails
|
||||
l.outputText(entry)
|
||||
return
|
||||
}
|
||||
l.output.Println(string(jsonData))
|
||||
}
|
||||
|
||||
// outputText outputs the log entry in text format
|
||||
func (l *Logger) outputText(entry LogEntry) {
|
||||
timestamp := entry.Timestamp
|
||||
level := entry.Level
|
||||
service := entry.Service
|
||||
message := entry.Message
|
||||
|
||||
// Base log line
|
||||
logLine := fmt.Sprintf("%s [%s] %s: %s", timestamp, level, service, message)
|
||||
|
||||
// Add file and line if available
|
||||
if entry.File != "" && entry.Line > 0 {
|
||||
logLine += fmt.Sprintf(" (%s:%d)", entry.File, entry.Line)
|
||||
}
|
||||
|
||||
// Add request ID if available
|
||||
if entry.RequestID != "" {
|
||||
logLine += fmt.Sprintf(" [req:%s]", entry.RequestID)
|
||||
}
|
||||
|
||||
// Add correlation ID if available
|
||||
if entry.CorrelationID != "" {
|
||||
logLine += fmt.Sprintf(" [corr:%s]", entry.CorrelationID)
|
||||
}
|
||||
|
||||
// Add duration if available
|
||||
if entry.Duration != "" {
|
||||
logLine += fmt.Sprintf(" [dur:%s]", entry.Duration)
|
||||
}
|
||||
|
||||
// Add additional fields
|
||||
if len(entry.Fields) > 0 {
|
||||
fields := make([]string, 0, len(entry.Fields))
|
||||
for k, v := range entry.Fields {
|
||||
fields = append(fields, fmt.Sprintf("%s=%v", k, v))
|
||||
}
|
||||
logLine += " [" + strings.Join(fields, " ") + "]"
|
||||
}
|
||||
|
||||
l.output.Println(logLine)
|
||||
}
|
||||
|
||||
// withField creates a new logger with an additional field
|
||||
func (l *Logger) withField(key string, value interface{}) *Logger {
|
||||
return &Logger{
|
||||
serviceName: l.serviceName,
|
||||
level: l.level,
|
||||
output: l.output,
|
||||
jsonFormat: l.jsonFormat,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of a log level
|
||||
func (l LogLevel) String() string {
|
||||
return levelStrings[l]
|
||||
}
|
||||
|
||||
// ParseLevel parses a string into a LogLevel
|
||||
func ParseLevel(level string) (LogLevel, error) {
|
||||
if l, exists := stringLevels[strings.ToUpper(level)]; exists {
|
||||
return l, nil
|
||||
}
|
||||
return INFO, fmt.Errorf("invalid log level: %s", level)
|
||||
}
|
||||
|
||||
// Global logger instance
|
||||
var globalLogger = Default()
|
||||
|
||||
// SetGlobalLogger sets the global logger instance
|
||||
func SetGlobalLogger(logger *Logger) {
|
||||
globalLogger = logger
|
||||
}
|
||||
|
||||
// Global logging functions
|
||||
func Debug(msg string, fields ...map[string]interface{}) {
|
||||
globalLogger.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
globalLogger.Debugf(format, args...)
|
||||
}
|
||||
|
||||
func Info(msg string, fields ...map[string]interface{}) {
|
||||
globalLogger.Info(msg, fields...)
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
globalLogger.Infof(format, args...)
|
||||
}
|
||||
|
||||
func Warn(msg string, fields ...map[string]interface{}) {
|
||||
globalLogger.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
globalLogger.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func Error(msg string, fields ...map[string]interface{}) {
|
||||
globalLogger.Error(msg, fields...)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
globalLogger.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func Fatal(msg string, fields ...map[string]interface{}) {
|
||||
globalLogger.Fatal(msg, fields...)
|
||||
}
|
||||
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
globalLogger.Fatalf(format, args...)
|
||||
}
|
||||
Reference in New Issue
Block a user