05 Implementasi Singleton Logger Logrus Pada Golang
Pada kali ini kita akan mencoba untuk mengimplementasikan Singleton Design Pattern. Apa itu Singletone? Bisa kamu baca terlebih dahulu artikel disini. Agar lebih paham lagi jadi Singleton adalah desain standar perangkat lunak yang memberikan jaminan keberadaan hanya satu instance kelas, sambil mempertahankan titik akses global ke objeknya. Singleton adalah pola desain yang membatasi instantiation ke suatu objek, kita harus memastikan bahwa ini hanya terjadi sekali.
Ada 2 cara yang akan kita coba implementasikan Logrus diantaranya yaitu dengan langsung menggunakan Singleton Logrus sendiri dan membuat custom sendiri dikarenakan ada beberapa kebutuhan misalkan, set fields tersendiri atau kebutuhan yang lainnya.
Penggunaan Singletone Logrus
Nah pada kali ini kita akan coba membuat inisialisasi object dari Logrus sebagai Logger sehingga kita tidak perlu lagi membuat object logger sendiri. Jika kita menggunakan Singleton maka akan otomatis logger tersebut akan berubah. Secara default Logger singleton yang ada di Logrus itu menggunakan TextFormatter dan Info Level. Cara agar membuat Logger singletone kita akan langsung menggunakan package pada Logrus-nya saja.
Pertama akan buat fungsi unit test seperti dibawah ini.
1func TestSingletone(t *testing.T) {
2 // set formatter using JSON
3 logrus.SetFormatter(&logrus.JSONFormatter{})
4 // initiate log rus
5 logrus.Info("info using Logrus singleton")
6 logrus.Warn("warn using Logrus singleton")
7 logrus.Error("error using Logrus singleton")
8}Maka ketika dijalankan akan tampil seperti dibawah ini.
1{"level":"info","msg":"info using Logrus singleton","time":"2024-04-30T23:40:00+07:00"}
2{"level":"warning","msg":"warn using Logrus singleton","time":"2024-04-30T23:40:00+07:00"}
3{"level":"error","msg":"error using Logrus singleton","time":"2024-04-30T23:40:00+07:00"}Membuat Custom Singleton untuk inisialisasi Logrus
Kita akan membuat Singleton untuk inisialisasi Logrus yang kita custom agar lebih mudah dan sesuai dengan kebutuhan kita. Pertama kita buat folder pkg/env ini digunakan untuk kebutuhan pengecekan environment yang diguanakn pada sistem kita. Package yang kita buat ini yaitu kebutuhan environment yang digunakan pada sistem kita agar lebih mudah penggunaannya.
Pertama, kita buat file env.go dengan fungsi inisialisasi sebagai berikut.
1type ServiceEnvironment = string
2
3const (
4 DevelopmentEnv = "development"
5 StagingEnv = "staging"
6 ProductionEnv = "production"
7 EnvironmentName = "environment"
8 GoVersionName = "go_version"
9)
10
11var (
12 envName = "SERVICE_ENV"
13 goVersion string
14)
15
16func Init() error {
17 err := SetFromEnvFile(".env")
18 if err != nil && !os.IsNotExist(err) {
19 log.Printf("failed to set env file: %v\n", err)
20 return err
21 }
22
23 goVersion = runtime.Version()
24 return nil
25}
26
27func SetFromEnvFile(filepath string) error {
28 if _, err := os.Stat(filepath); err != nil {
29 return err
30 }
31
32 f, err := os.Open(filepath)
33 if err != nil {
34 return err
35 }
36 scanner := bufio.NewScanner(f)
37 if err := scanner.Err(); err != nil {
38 return err
39 }
40 for scanner.Scan() {
41 text := scanner.Text()
42 text = strings.TrimSpace(text)
43 vars := strings.SplitN(text, "=", 2)
44 if len(vars) < 2 {
45 return err
46 }
47 if err := os.Setenv(vars[0], vars[1]); err != nil {
48 return err
49 }
50 }
51 return nil
52}Fungsi initialize ini digunakan saat berjalannya sistem dengan membaca file .env lalu membaca beberapa tag yang ada pada file tersebut kemudian di set ke dalam enrironment sistem di server.
Ada beberapa fungsi tambahan untuk kebutuhan pada sistem diantaranya yaitu kebutuhan untuk mengambil data sesuai environment yang sedang berjalan misalkan development, staging, dan production.
1func ServiceEnv() ServiceEnvironment {
2 e := os.Getenv(envName)
3 if e == "" {
4 e = DevelopmentEnv
5 }
6 return e
7}
8
9func GetVersion() string {
10 return goVersion
11}
12
13func IsDevelopment() bool {
14 return ServiceEnv() == DevelopmentEnv
15}
16
17func IsStaging() bool {
18 return ServiceEnv() == StagingEnv
19}
20
21func IsProduction() bool {
22 return ServiceEnv() == ProductionEnv
23}Kemudian selanjutnya kita membuat file baru pada folder baru juga dengan nama pkg/log/log.go dengan isi seperti dibawah ini.
1package log
2
3import (
4 "errors"
5 "os"
6
7 "github.com/santekno/learn-golang-logging/pkg/env"
8 "github.com/sirupsen/logrus"
9)
10
11var (
12 log, _ = NewLogger(&Config{Formatter: &TextFormatter, Level: InfoLevel, LogName: "application.log"})
13 JSONFormatter logrus.JSONFormatter
14 TextFormatter logrus.TextFormatter
15 serviceFields = map[string]interface{}{
16 env.EnvironmentName: env.ServiceEnv(),
17 env.GoVersionName: env.GetVersion(),
18 }
19)
20
21type (
22 Level = logrus.Level
23 Logger = *logrus.Logger
24)
25
26const (
27 // override logrus level
28 Paniclevel = logrus.PanicLevel
29 FatalLevel = logrus.FatalLevel
30 ErrorLevel = logrus.ErrorLevel
31 WarnLevel = logrus.WarnLevel
32 InfoLevel = logrus.InfoLevel
33 DebugLevel = logrus.DebugLevel
34 TraceLevel = logrus.TraceLevel
35)
36
37type Config struct {
38 logrus.Formatter
39 logrus.Level
40 LogName string
41}
42
43func NewLogger(cfg *Config) (Logger, error) {
44 l := logrus.New()
45 if env.IsDevelopment() {
46 l.SetFormatter(&logrus.TextFormatter{})
47 }
48 l.SetFormatter(cfg.Formatter)
49 l.SetLevel(cfg.Level)
50 return l, nil
51}
52
53func SetConfig(cfg *Config) error {
54 if cfg.LogName == "" {
55 return errors.New("log name is empty")
56 }
57
58 if !env.IsDevelopment() {
59 // initiation create file for logger
60 file, err := os.OpenFile(cfg.LogName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
61 if err != nil {
62 log.Fatal(err)
63 }
64
65 // set output file into logrus
66 log.SetOutput(file)
67 }
68
69 log.SetFormatter(cfg.Formatter)
70 log.SetLevel(cfg.Level)
71 return nil
72}Pada file tersebut terdapat fungsi NewLogger disini kita akan inisialisasi Logrus dengan kebutuhan yang sudah ditentukan sesuai dengan parameter yang diinginkan pada config. Setelah itu kita juga membuat SetConfig lalu jika ingin membuat logging pada package yg kita buat ini sudah disematkan fields yang dibutuhkan seperti mengeset environment dan versi golang yang digunakan. Maka kita sediakan fungsi yang melakukan set tersebut seperti ini.
1func Debug(args ...interface{}) {
2 log.WithFields(serviceFields).Debug(args...)
3}
4
5func Info(args ...interface{}) {
6 log.WithFields(serviceFields).Info(args...)
7}
8
9func Warn(args ...interface{}) {
10 log.WithFields(serviceFields).Warn(args...)
11}
12
13func Error(args ...interface{}) {
14 log.WithFields(serviceFields).Error(args...)
15}
16
17func Fatal(args ...interface{}) {
18 log.WithFields(serviceFields).Fatal(args...)
19}Terakhir kita buat fungsi main pada file main.go dengan isi seperti dibawah ini.
1func main() {
2 // initialize environment
3 err := env.Init()
4 if err != nil {
5 log.Fatal(err)
6 }
7
8 // initialize config log
9 err = log.SetConfig(&log.Config{
10 Formatter: &log.TextFormatter,
11 Level: log.TraceLevel,
12 LogName: "application.log",
13 })
14 if err != nil {
15 log.Fatal(err)
16 }
17
18 log.Debug("singleton debug")
19 log.Info("singleton info")
20 log.Warn("singleton warn")
21 log.Error("singleton debug")
22 log.Fatal("singleton fatal")
23}Pada kode diatas bisa dilihat pertama ada inisialisasi environment untuk memastikan sistem kita berjalan dalam environment yang sesuai. Kemudian dilanjutkan dengan inisialisasi konfigurasi logging pada package yang sudah kita buat dan selanjutnya lakukan Logger seperti biasa.
Maka jika kita jalankan kode main tersebut akan terlihat seperti ini.
1DEBU[0000] singleton debug environment=development go_version=go1.21.1
2INFO[0000] singleton info environment=development go_version=go1.21.1
3WARN[0000] singleton warn environment=development go_version=go1.21.1
4ERRO[0000] singleton debug environment=development go_version=go1.21.1
5FATA[0000] singleton fatal environment=development go_version=go1.21.1Dan jika kita pindah dengan enviromnet misalkan untuk kebutuhan di production maka kita bisa tambahkan file environment menjadi ini
1SERVICE_ENV=production