Files

105 lines
2.8 KiB
Go
Raw Permalink Normal View History

2026-07-30 09:51:25 +07:00
package logger
import (
"encoding/json"
"io"
"log"
"os"
"strings"
"time"
)
// Fields adalah data tambahan yang disisipkan ke satu baris log, contoh:
//
// logger.Info("request selesai", logger.Fields{"path": "/users", "status": 200})
type Fields map[string]interface{}
var std = log.New(os.Stdout, "", 0)
// SetOutput mengganti tujuan tulis log. Berguna untuk unit test (redirect ke buffer)
// atau kalau nanti mau kirim log ke file/agent lain.
func SetOutput(w io.Writer) {
std = log.New(w, "", 0)
}
var sensitiveKeys = map[string]bool{
"password": true,
"password_confirmation": true,
"old_password": true,
"new_password": true,
"confirm_password": true,
"token": true,
"access_token": true,
"refresh_token": true,
"authorization": true,
"secret": true,
"jwt_secret": true,
"credit_card": true,
"card_number": true,
"cvv": true,
"otp": true,
}
const redactedValue = "***REDACTED***"
// sanitize menyensor field sensitif secara rekursif (termasuk di dalam nested object/body JSON)
// supaya data seperti password tidak pernah ikut tercetak di log, walau caller lupa menyaringnya.
func sanitize(fields Fields) Fields {
if fields == nil {
return nil
}
clean := make(Fields, len(fields))
for key, value := range fields {
if sensitiveKeys[strings.ToLower(key)] {
clean[key] = redactedValue
continue
}
switch v := value.(type) {
case map[string]interface{}:
clean[key] = sanitize(Fields(v))
case Fields:
clean[key] = sanitize(v)
default:
clean[key] = value
}
}
return clean
}
type entry struct {
Time string `json:"time"`
Level string `json:"level"`
Message string `json:"message"`
Fields Fields `json:"fields,omitempty"`
}
func write(level, msg string, fields Fields) {
e := entry{
Time: time.Now().Format(time.RFC3339),
Level: level,
Message: msg,
Fields: sanitize(fields),
}
b, err := json.Marshal(e)
if err != nil {
// fallback kalau ternyata ada value yang gagal di-marshal, tetap harus ada jejak log-nya
std.Printf("[%s] %s (gagal marshal fields: %v)", level, msg, err)
return
}
std.Println(string(b))
}
// Info dipakai untuk mencatat aktivitas normal, contoh: request masuk dan sukses diproses.
func Info(msg string, fields Fields) { write("INFO", msg, fields) }
// Warn dipakai untuk kondisi yang bukan bug tapi perlu diperhatikan, contoh: request 4xx
// (input salah, tidak diotorisasi, dst).
func Warn(msg string, fields Fields) { write("WARN", msg, fields) }
// Error dipakai untuk kegagalan sisi server (5xx) atau error tak terduga lainnya yang
// butuh investigasi lebih lanjut oleh developer.
func Error(msg string, fields Fields) { write("ERROR", msg, fields) }