diff --git a/cmd/test_dynamic_logging/main.go b/cmd/test_dynamic_logging/main.go new file mode 100644 index 00000000..97774de8 --- /dev/null +++ b/cmd/test_dynamic_logging/main.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "log" + "time" + + "api-service/pkg/logger" +) + +func main() { + fmt.Println("Testing Dynamic Logging Functions...") + fmt.Println("====================================") + + // Test fungsi penyimpanan log dinamis + testDynamicLogging() + + // Tunggu sebentar untuk memastikan goroutine selesai + time.Sleep(500 * time.Millisecond) + + fmt.Println("\n====================================") + fmt.Println("Dynamic logging test completed!") + fmt.Println("Check the log files in pkg/logger/data/ directory") +} + +func testDynamicLogging() { + // Buat logger instance + loggerInstance := logger.New("test-app", logger.DEBUG, false) + + // Test 1: Log dengan penyimpanan otomatis + fmt.Println("\n1. Testing automatic log saving...") + loggerInstance.LogAndSave(logger.INFO, "Application started successfully", map[string]interface{}{ + "version": "1.0.0", + "build_date": time.Now().Format("2006-01-02"), + "environment": "development", + }) + + // Test 2: Log dengan request context + fmt.Println("\n2. Testing log with request context...") + requestLogger := loggerInstance.WithRequestID("req-001").WithCorrelationID("corr-001") + requestLogger.LogAndSave(logger.INFO, "User login attempt", map[string]interface{}{ + "username": "john_doe", + "ip": "192.168.1.100", + "success": true, + }) + + // Test 3: Error logging + fmt.Println("\n3. Testing error logging...") + loggerInstance.LogAndSave(logger.ERROR, "Database connection failed", map[string]interface{}{ + "error": "connection timeout", + "retry_count": 3, + "host": "db.example.com:5432", + }) + + // Test 4: Manual log entry saving + fmt.Println("\n4. Testing manual log entry saving...") + manualEntry := logger.LogEntry{ + Timestamp: time.Now().Format(time.RFC3339), + Level: "DEBUG", + Service: "manual-test", + Message: "Manual log entry created", + RequestID: "manual-req-001", + CorrelationID: "manual-corr-001", + File: "main.go", + Line: 42, + Fields: map[string]interface{}{ + "custom_field": "test_value", + "number": 123, + "active": true, + }, + } + + // Simpan manual ke berbagai format + if err := logger.SaveLogText(manualEntry); err != nil { + log.Printf("Error saving text log: %v", err) + } else { + fmt.Println("āœ“ Text log saved successfully") + } + + if err := logger.SaveLogJSON(manualEntry); err != nil { + log.Printf("Error saving JSON log: %v", err) + } else { + fmt.Println("āœ“ JSON log saved successfully") + } + + if err := logger.SaveLogToDatabase(manualEntry); err != nil { + log.Printf("Error saving database log: %v", err) + } else { + fmt.Println("āœ“ Database log saved successfully") + } + + // Test 5: Performance logging dengan durasi + fmt.Println("\n5. Testing performance logging...") + start := time.Now() + + // Simulasi proses yang memakan waktu + time.Sleep(200 * time.Millisecond) + + duration := time.Since(start) + loggerInstance.LogAndSave(logger.INFO, "Data processing completed", map[string]interface{}{ + "operation": "data_import", + "duration": duration.String(), + "duration_ms": duration.Milliseconds(), + "records": 1000, + "throughput": fmt.Sprintf("%.2f records/ms", 1000/float64(duration.Milliseconds())), + }) + + fmt.Println("\nāœ“ All logging tests completed successfully!") +} diff --git a/pkg/logger/dynamic_logging_test.go b/pkg/logger/dynamic_logging_test.go new file mode 100644 index 00000000..c85581cf --- /dev/null +++ b/pkg/logger/dynamic_logging_test.go @@ -0,0 +1,94 @@ +package logger + +import ( + "os" + "testing" + "time" +) + +func TestDynamicLogging(t *testing.T) { + // Pastikan direktori data ada + os.RemoveAll("pkg/logger/data") + + t.Run("TestSaveLogText", testSaveLogText) + t.Run("TestSaveLogJSON", testSaveLogJSON) + t.Run("TestSaveLogToDatabase", testSaveLogToDatabase) + t.Run("TestLogAndSave", testLogAndSave) +} + +func testSaveLogText(t *testing.T) { + logger := New("test-service", INFO, false) + + entry := LogEntry{ + Timestamp: time.Now().Format(time.RFC3339), + Level: "INFO", + Service: "test-service", + Message: "Test log message", + File: "test.go", + Line: 10, + Fields: map[string]interface{}{ + "test_field": "test_value", + "number": 42, + }, + } + + err := logger.SaveLogText(entry) + if err != nil { + t.Errorf("SaveLogText failed: %v", err) + } + + // Verifikasi file dibuat + if _, err := os.Stat("pkg/logger/data/logs.txt"); os.IsNotExist(err) { + t.Error("Text log file was not created") + } +} + +func testSaveLogJSON(t *testing.T) { + logger := New("test-service", INFO, false) + + entry := LogEntry{ + Timestamp: time.Now().Format(time.RFC3339), + Level: "INFO", + Service: "test-service", + Message: "Test JSON log message", + File: "test.go", + Line: 20, + Fields: map[string]interface{}{ + "json_field": "json_value", + "count": 100, + }, + } + + err := logger.SaveLogJSON(entry) + if err != nil { + t.Errorf("SaveLogJSON failed: %v", err) + } + + // Verifikasi file dibuat + if _, err := os.Stat("pkg/logger/data/logs.json"); os.IsNotExist(err) { + t.Error("JSON log file was not created") + } +} + +func testSaveLogToDatabase(t *testing.T) { + logger := New("test-service", INFO, false) + + entry := LogEntry{ + Timestamp: time.Now().Format(time.RFC3339), + Level: "INFO", + Service: "test-service", + Message: "Test database log message", + File: "test.go", + Line: 30, + } + + err := logger.SaveLogToDatabase(entry) + if err != nil { + t.Errorf("SaveLogToDatabase failed: %v", err) + } + + // Verifikasi file dibuat (placeholder untuk database) + if _, err := os.Stat("pkg/logger/data/database_logs.txt"); os.IsNotExist(err) { + t.Error("Database log file was not created") + } +} diff --git a/pkg/logger/example_dynamic_logging.go b/pkg/logger/example_dynamic_logging.go new file mode 100644 index 00000000..ba2f7196 --- /dev/null +++ b/pkg/logger/example_dynamic_logging.go @@ -0,0 +1,105 @@ +package logger + +import ( + "fmt" + "time" +) + +// ExampleDynamicLogging menunjukkan cara menggunakan fungsi penyimpanan log dinamis +func ExampleDynamicLogging() { + // Buat logger instance + logger := New("test-service", DEBUG, false) + + // Contoh 1: Log biasa dengan penyimpanan otomatis + fmt.Println("=== Contoh 1: Log biasa dengan penyimpanan otomatis ===") + logger.LogAndSave(INFO, "Aplikasi dimulai", map[string]interface{}{ + "version": "1.0.0", + "mode": "development", + }) + + // Contoh 2: Log dengan request ID + fmt.Println("\n=== Contoh 2: Log dengan request ID ===") + reqLogger := logger.WithRequestID("req-123456") + reqLogger.LogAndSave(INFO, "Request diproses", map[string]interface{}{ + "endpoint": "/api/v1/users", + "method": "GET", + "user_id": 1001, + }) + + // Contoh 3: Log error + fmt.Println("\n=== Contoh 3: Log error ===") + logger.LogAndSave(ERROR, "Database connection failed", map[string]interface{}{ + "error": "connection timeout", + "timeout": "30s", + "host": "localhost:5432", + }) + + // Contoh 4: Manual save ke berbagai format + fmt.Println("\n=== Contoh 4: Manual save ke berbagai format ===") + manualEntry := LogEntry{ + Timestamp: time.Now().Format(time.RFC3339), + Level: "INFO", + Service: "manual-service", + Message: "Manual log entry", + RequestID: "manual-req-001", + File: "example.go", + Line: 42, + Fields: map[string]interface{}{ + "custom_field": "custom_value", + "number": 42, + }, + } + + // Simpan manual ke berbagai format + if err := SaveLogText(manualEntry); err != nil { + fmt.Printf("Error saving text log: %v\n", err) + } + + if err := SaveLogJSON(manualEntry); err != nil { + fmt.Printf("Error saving JSON log: %v\n", err) + } + + if err := SaveLogToDatabase(manualEntry); err != nil { + fmt.Printf("Error saving to database log: %v\n", err) + } + + // Contoh 5: Log dengan durasi + fmt.Println("\n=== Contoh 5: Log dengan durasi ===") + start := time.Now() + time.Sleep(100 * time.Millisecond) // Simulasi proses + duration := time.Since(start) + + logger.LogAndSave(INFO, "Process completed", map[string]interface{}{ + "operation": "data_processing", + "duration": duration.String(), + "items": 150, + }) + + fmt.Println("\n=== Semua log telah disimpan dalam berbagai format ===") + fmt.Println("1. Format teks dengan pemisah |: pkg/logger/data/logs.txt") + fmt.Println("2. Format JSON: pkg/logger/data/logs.json") + fmt.Println("3. Format database (placeholder): pkg/logger/data/database_logs.txt") +} + +// ExampleMiddlewareLogging menunjukkan penggunaan dalam middleware +func ExampleMiddlewareLogging() { + fmt.Println("\n=== Contoh Penggunaan dalam Middleware ===") + + middlewareLogger := New("middleware-service", INFO, false) + + // Simulasi request processing + middlewareLogger.LogAndSave(INFO, "Request received", map[string]interface{}{ + "method": "POST", + "path": "/api/v1/auth/login", + "client_ip": "192.168.1.100", + "user_agent": "Mozilla/5.0", + "content_type": "application/json", + }) + + // Simulasi response + middlewareLogger.LogAndSave(INFO, "Response sent", map[string]interface{}{ + "status_code": 200, + "duration": "150ms", + "response_size": "2.5KB", + }) +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 92a01641..632d18fe 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -376,3 +376,159 @@ func Fatal(msg string, fields ...map[string]interface{}) { func Fatalf(format string, args ...interface{}) { globalLogger.Fatalf(format, args...) } + +// SaveLogText menyimpan log dalam format teks dengan pemisah | +func (l *Logger) SaveLogText(entry LogEntry) error { + // Format log dengan pemisah | + logLine := fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s:%d", + entry.Timestamp, + entry.Level, + entry.Service, + entry.Message, + entry.RequestID, + entry.CorrelationID, + entry.Duration, + entry.File, + entry.Line) + + // Tambahkan fields jika ada + if len(entry.Fields) > 0 { + fieldsStr := "" + for k, v := range entry.Fields { + fieldsStr += fmt.Sprintf("|%s=%v", k, v) + } + logLine += fieldsStr + } + logLine += "\n" + + // Buat direktori jika belum ada + dirPath := "pkg/logger/data" + if err := os.MkdirAll(dirPath, 0755); err != nil { + return err + } + + // Tulis ke file + filePath := dirPath + "/logs.txt" + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + if _, err := f.WriteString(logLine); err != nil { + return err + } + return nil +} + +// SaveLogJSON menyimpan log dalam format JSON +func (l *Logger) SaveLogJSON(entry LogEntry) error { + jsonData, err := json.Marshal(entry) + if err != nil { + return err + } + + // Buat direktori jika belum ada + dirPath := "pkg/logger/data" + if err := os.MkdirAll(dirPath, 0755); err != nil { + return err + } + + // Tulis ke file + filePath := dirPath + "/logs.json" + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + if _, err := f.WriteString(string(jsonData) + "\n"); err != nil { + return err + } + return nil +} + +// SaveLogToDatabase menyimpan log ke database +func (l *Logger) SaveLogToDatabase(entry LogEntry) error { + // Implementasi penyimpanan ke database + // Ini adalah contoh implementasi, sesuaikan dengan struktur database Anda + + // Untuk saat ini, kita akan simpan ke file sebagai placeholder + // Anda dapat mengganti ini dengan koneksi database yang sesuai + dbLogLine := fmt.Sprintf("DB_LOG: %s|%s|%s|%s\n", + entry.Timestamp, entry.Level, entry.Service, entry.Message) + + dirPath := "pkg/logger/data" + if err := os.MkdirAll(dirPath, 0755); err != nil { + return err + } + + filePath := dirPath + "/database_logs.txt" + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + if _, err := f.WriteString(dbLogLine); err != nil { + return err + } + return nil +} + +// LogAndSave melakukan logging dan menyimpan ke semua format +func (l *Logger) LogAndSave(level LogLevel, msg string, fields ...map[string]interface{}) { + // Panggil fungsi log biasa + l.log(level, msg, nil, fields...) + + // Dapatkan entry log yang baru dibuat + _, file, line, ok := runtime.Caller(2) + var callerFile string + var callerLine int + if ok { + parts := strings.Split(file, "/") + if len(parts) > 2 { + callerFile = strings.Join(parts[len(parts)-2:], "/") + } else { + callerFile = file + } + callerLine = line + } + + 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, + } + + // Simpan ke semua format + go func() { + l.SaveLogText(entry) + l.SaveLogJSON(entry) + l.SaveLogToDatabase(entry) + }() +} + +// Global fungsi untuk menyimpan log +func SaveLogText(entry LogEntry) error { + return globalLogger.SaveLogText(entry) +} + +func SaveLogJSON(entry LogEntry) error { + return globalLogger.SaveLogJSON(entry) +} + +func SaveLogToDatabase(entry LogEntry) error { + return globalLogger.SaveLogToDatabase(entry) +}