Kodeloka
  1. Kodeloka
  2. Kelas Golang
  3. Materi

Domain 10 · 8 bab

Golang Logging

128. Pengenalan Logging & Package Logrus

Memahami pentingnya logging terstruktur pada aplikasi Go serta pengenalan library Sirupsen Logrus.

Apa itu Logging?

Logging adalah proses mencatat informasi penting mengenai kejadian (*events*) yang terjadi saat aplikasi sedang berjalan (*runtime*). Logging sangat vital untuk debugging, pemantauan sistem (monitoring), audit keamanan, dan analisa error di lingkungan produksi.

💡 Mengapa Memakai Library External (Logrus)?

Package bawaan Go log sangat sederhana dan tidak mendukung fitur modern seperti level severity (Debug, Info, Error), format JSON terstruktur, serta metadata kontekstual (field). Oleh karena itu, Sirupsen Logrus menjadi salah satu library logging terpopuler di ekosistem Go.

Keunggulan Sirupsen Logrus:

  • Structured Logging: Mendukung format JSON out-of-the-box.
  • Log Severity Levels: Trace, Debug, Info, Warn, Error, Fatal, Panic.
  • Field Metadata: Menambahkan informasi dinamis key-value pada baris log.
  • Extensible Hooks: Mengirim log otomatis ke platform lain (Slack, Elasticsearch, Sentry).

Instalasi Logrus:

go get github.com/sirupsen/logrus

Contoh program

package main

import (
	"fmt"
)

// Simulasi penggunaan Logrus di Go Playground
type Logger struct {
	Name string
}

func (l *Logger) Info(msg string) {
	fmt.Printf("[INFO] %s: %s\n", l.Name, msg)
}

func main() {
	fmt.Println("=== DEMO GOLANG LOGGING ===")
	logger := &Logger{Name: "AppLogger"}
	logger.Info("Aplikasi Golang berhasil diinisialisasi.")
	logger.Info("Logrus menyediakan fitur logging terstruktur modern.")
}

Praktik dan kuis bab ini di kelas interaktif →

129. Level Logging (Trace, Debug, Info, Warn, Error, Fatal, Panic)

Memahami 7 tingkatan severity log di Logrus dan mengaturnya dengan SetLevel.

Tingkatan Level Logging (Severity Levels)

Log level membantu memfilter informasi pesan log sesuai dengan tingkat kedaruratannya. Logrus memiliki 7 tingkatan level:

LevelMethodPenggunaan Utama
TraceLevellog.Trace()Informasi sangat detail/fine-grained untuk lacak pergerakan data.
DebugLevellog.Debug()Pesan diagnostik pengembang saat proses development.
InfoLevellog.Info()Informasi umum operasional aplikasi (misal: Server started, User logged in).
WarnLevellog.Warn()Peringatan indikasi masalah non-kritis (misal: Memory usage 80%).
ErrorLevellog.Error()Kegagalan fungsi tetapi aplikasi masih bisa terus berjalan.
FatalLevellog.Fatal()Error fatal yang langsung menghentikan aplikasi (os.Exit(1)).
PanicLevellog.Panic()Error kritis yang memicu mekanisme panic() Go.
📌 Mengatur Level Log Minimum:
log.SetLevel(log.WarnLevel) // Hanya Warn, Error, Fatal, & Panic yang dicetak

Contoh program

package main

import (
	"fmt"
)

// Simulasi Log Levels
const (
	LevelTrace = iota
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
)

func logMessage(level int, msg string) {
	levels := []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"}
	fmt.Printf("[%s] %s\n", levels[level], msg)
}

func main() {
	fmt.Println("=== DEMO LOG LEVELS ===")
	logMessage(LevelDebug, "Menghitung query database...")
	logMessage(LevelInfo, "User ID #102 berhasil melakukan checkout.")
	logMessage(LevelWarn, "Koneksi Redis lambat (latency > 200ms).")
	logMessage(LevelError, "Gagal mengirim email verifikasi ke user.")
}

Praktik dan kuis bab ini di kelas interaktif →

130. Output Logging (File & Multi-Writer)

Mengarahkan log ke standar output, file fisik, atau multiple destination secara bersamaan.

Mengarahkan Target Output Logging

Secara default, Logrus mencetak log ke os.Stdout (terminal). Namun di produksi, kita sering perlu menyimpan log ke file fisik atau mengirimnya ke terminal dan file sekaligus.

Mencetak Log ke File:

file, err := os.OpenFile("application.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
    log.SetOutput(file)
}
💡 Menggunakan io.MultiWriter:

Jika ingin mencetak ke terminal DAN menyimpan ke file secara simultan, gunakan io.MultiWriter:

multiWriter := io.MultiWriter(os.Stdout, file)
log.SetOutput(multiWriter)

Contoh program

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
)

func main() {
	var buf bytes.Buffer
	// MultiWriter menggabungkan os.Stdout dan in-memory buffer
	writer := io.MultiWriter(os.Stdout, &buf)

	fmt.Fprintln(writer, "[INFO] Log ini dicetak ke console DAN disimpan ke buffer/file.")

	fmt.Println("\n--- Isi Buffer yang Tersimpan ---")
	fmt.Print(buf.String())
}

Praktik dan kuis bab ini di kelas interaktif →

131. Log Formatter (TextFormatter vs JSONFormatter)

Mengubah format tampilan log dari Plain Text berwarna menjadi JSON terstruktur.

Format Output Log

Formatter menentukan bagaimana data log disajikan. Logrus menyediakan dua formatter bawaan yang sangat fleksibel:

  • TextFormatter: Format teks biasa berwarna yang cocok dipandang mata di terminal saat pengembangan local.
  • JSONFormatter: Format JSON terstruktur yang ideal dikonsumsi oleh log aggregator seperti ElasticSearch/Kibana, Datadog, atau Loki.
📌 Cara Mengubah Formatter:
// Mengubah ke JSON
log.SetFormatter(&log.JSONFormatter{})

// Kustomisasi Text Formatter
log.SetFormatter(&log.TextFormatter{
    FullTimestamp: true,
})

Contoh program

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type LogEntry struct {
	Level     string    `json:"level"`
	Message   string    `json:"msg"`
	Timestamp time.Time `json:"time"`
}

func main() {
	entry := LogEntry{
		Level:     "info",
		Message:   "User order #9921 berhasil dibuat",
		Timestamp: time.Now(),
	}

	jsonLog, _ := json.MarshalIndent(entry, "", "  ")
	fmt.Println("=== DEMO JSON FORMATTER ===")
	fmt.Println(string(jsonLog))
}

Praktik dan kuis bab ini di kelas interaktif →

132. Fields & Entry (Contextual Logging dengan WithField/WithFields)

Menambahkan metadata kontekstual berbentuk key-value pair pada setiap baris pesan log.

Contextual Logging dengan Fields

Pesan log yang baik tidak hanya berisi teks mentah seperti "Payment failed", tetapi juga menyertakan konteks seperti user_id, amount, dan payment_method.

💡 Penggunaan WithField & WithFields:
// Tunggal: WithField
log.WithField("user", "eko").Info("User login")

// Jamak: WithFields
log.WithFields(log.Fields{
    "username": "eko",
    "ip":       "192.168.1.100",
    "status":   "success",
}).Info("User authentication successful")

Penggunaan WithFields mengembalikan sebuah *log.Entry yang dapat disimpan dan dipakai berulang kali (*reusable context*).

Contoh program

package main

import (
	"fmt"
)

type Fields map[string]interface{}

func LogWithFields(fields Fields, msg string) {
	fmt.Printf("[INFO] %s | Context: ", msg)
	for k, v := in fields { // simulasi print fields
		fmt.Printf("%s=%v ", k, v)
	}
	fmt.Println()
}

func main() {
	fmt.Println("=== DEMO CONTEXTUAL LOGGING ===")
	ctxFields := Fields{
		"user_id": 1001,
		"email":   "[email protected]",
		"role":    "admin",
	}

	LogWithFields(ctxFields, "Proses pembaharuan profil berhasil")
}

Praktik dan kuis bab ini di kelas interaktif →

133. Logger Singleton vs Instance Kustom

Membuat instance logger terisolasi (`log.New()`) untuk mencegah efek samping global state.

Singleton vs Custom Logger Instance

Secara bawaan, memanggil log.Info() menggunakan instance Global Singleton milik Logrus. Namun pada aplikasi besar, disarankan membuat Custom Logger Instance terpisah untuk tiap modul.

⚠️ Bahaya Global Singleton Logger:

Mengubah konfigurasi logger global (seperti `SetLevel` atau `SetFormatter`) di satu paket dapat memengaruhi seluruh paket lain di dalam aplikasi Go Anda secara tidak sengaja.

Membuat Instance Logger Kustom:

logger := log.New()
logger.SetLevel(log.DebugLevel)
logger.SetFormatter(&log.JSONFormatter{})
logger.SetOutput(os.Stdout)

Contoh program

package main

import (
	"fmt"
)

type CustomLogger struct {
	Prefix string
}

func NewLogger(prefix string) *CustomLogger {
	return &CustomLogger{Prefix: prefix}
}

func (l *CustomLogger) Log(msg string) {
	fmt.Printf("[%s] %s\n", l.Prefix, msg)
}

func main() {
	dbLogger := NewLogger("DATABASE")
	authLogger := NewLogger("AUTH_SERVICE")

	dbLogger.Log("Connected to MySQL on port 3306")
	authLogger.Log("Token JWT validated for user #5")
}

Praktik dan kuis bab ini di kelas interaktif →

134. Logrus Hook (Mengirim Log ke System External)

Mekanisme interceptor pesan log untuk dikirimkan secara otomatis ke Slack, Sentry, atau Message Broker.

Mengenal Logrus Hook

Hook di Logrus adalah mekanisme plugin yang mengeksekusi logika tambahan setiap kali event log dibuat sesuai dengan level tertentu. Hook sangat berguna untuk mendeteksi error dan mengirim notifikasi secara instant.

Syarat Membuat Custom Hook:

Sebuah struct harus mengimplementasikan interface log.Hook yang memiliki 2 method wajib:

type Hook interface {
    Levels() []Level       // Menentukan level log mana saja yang memicu hook
    Fire(*Entry) error     // Logika eksekusi ketika hook dipicu
}
📌 Menambahkan Hook ke Logger:
log.AddHook(&SampleHook{})

Contoh program

package main

import (
	"fmt"
)

// Simulasi Hook Interceptor
type SampleHook struct{}

func (h *SampleHook) Fire(level string, msg string) {
	if level == "ERROR" {
		fmt.Printf("🔔 HOOK ALERT: Mengirim notifikasi Slack untuk error: '%s'\n", msg)
	}
}

func main() {
	hook := &SampleHook{}
	fmt.Println("=== DEMO LOGRUS HOOK ===")
	
	// Simulasi pembuatan log
	hook.Fire("INFO", "Normal operation")
	hook.Fire("ERROR", "Database connection timeout!")
}

Praktik dan kuis bab ini di kelas interaktif →

135. Standard Library log/slog (Structured Logging Bawaan Go 1.21+)

Mengenal package `log/slog` bawaan Go 1.21+ sebagai standar resmi logging terstruktur.

Standard Library log/slog

Sejak Go 1.21, Go menambahkan package official baru bernama log/slog ke dalam Standard Library. Paket ini menghadirkan fitur structured logging bawaan tanpa tergantung library pihak ketiga seperti Logrus atau Zap.

Fitur Utama log/slog:

  • Handler Architecture: Mendukung TextHandler dan JSONHandler.
  • Attribute Key-Value: Menggunakan slog.String(), slog.Int(), slog.Group() untuk performa tinggi tanpa alokasi memori berlebih.
  • Zero Dependency: Bagian dari standar instalasi Go 1.21+.
💡 Contoh Penggunaan slog:
import "log/slog"

slog.Info("User registered", "id", 42, "role", "user")
slog.Error("Failed to save", slog.String("reason", "db timeout"))

Contoh program

package main

import (
	"fmt"
)

// Simulasi perilaku slog di Go 1.21+
func SlogInfo(msg string, args ...interface{}) {
	fmt.Printf("[slog INFO] %s ", msg)
	for i := 0; i < len(args); i += 2 {
		fmt.Printf("%v=%v ", args[i], args[i+1])
	}
	fmt.Println()
}

func main() {
	fmt.Println("=== DEMO LOG/SLOG (GO 1.21+) ===")
	SlogInfo("Proses Transaksi Selesai", "trx_id", "TRX-9011", "total", 150000)
}

Praktik dan kuis bab ini di kelas interaktif →