Install Seq sebagai Logger di Golang Project

M. Husni Nur Fadillah
Sainseni
Published in
5 min readAug 22, 2023

Latar Belakang

Sederhananya, logger merupakan tool yang digunakan untuk mencatat atau menyimpan informasi aktivitas yang dihasilkan oleh program atau sistem yang sudah dipasangi logger sebelumnya.

supaya ada bayangan

“Kenapa perlu install logger?”

anime bingung

Aku ambil contoh berdasarkan pengalamanku. Pada saat itu, ada aplikasi yang sudah berjalan di production tiba-tiba crash. Untungnya, aplikasi itu udah dipasangi logger sebelumnya. Jadi, ketika crash terjadi, notifikasi langsung dikirim ke Slack channel-nya company. Dengan begitu, developer bisa notice dan fixing lebih cepat. Ataupun pada saat development, ketika QA menemukan bug, lumayan merepotkan dan lambat kalo kita sebagai developer selalu minta payload yang menyebabkan bug tersebut atau meminta QA untuk record screen untuk menjunkan bagaimana cara reproduce bug tersebut (meskipun ada beberapa kasus dimana kita mesti tau bagaimana cara reproduce bug-nya dari sisi user).

Berikut daftar alasan lainnya:

  1. Monitoring Aplikasi
  2. Analisis Kinerja Aplikasi
  3. Sebagai Bug Tracker
  4. Audit dan Keamanan
  5. Dokumentasi

Install SEQ

Di artikel ini aku menggunakan Linux Environtment dan kalo diliat dari dokumentasi, mereka ngak menyediakan Seq untuk linux tetapi mereka menyediakan docker image-nya, jadi aku akan menggunakan docker untuk menginstall Seq di local.

PH=$(echo '<password>' | docker run --rm -i datalust/seq config hash)
  • ganti <password> dengan password yang pengen di-hash
  • menjalankan docker container dengan image datalust/seq menggunakan mode interaktif, untuk menghasilkan hash dari password yang akan digunakan untuk akun ‘admin’ di dalam Seq.
  • Docker otomatis akan mengunduh datalust/seq jika belum terdapat image tersebut di local.
  • menyimpan hash password tadi ke dalam variabel PH .

Selanjutnya jalankan perintah berikut untuk menjalankan docker container dengan menggunakan image datalust/seq .

docker run \
--name seq \
-d \
--restart unless-stopped \
-e ACCEPT_EULA=Y \
-e SEQ_API_CANONICALURI=https://seq.example.com \
-e SEQ_FIRSTRUN_ADMINPASSWORDHASH="$PH" \
-v /path/to/seq/data:/data \
-p 80:80 \
-p 5341:5341 \
datalust/seq

Setelah berhasil menjalankan perintah di atas, kemudian periksa status docker container-nya dengan perintah docker ps , jika masih gagal kalian bisa check peyebab gagalnya dengan perintah docker logs .

Buka Seq UI di http://localhost:80, kemudian input username dan password (sebelum di-hash) yang sebelumnya digunakan ketika ngejalanin docker container.

Go Seq Logger

Aku menggunakan Fiber framework dan menerapkan flat structure di project ini biar lebih gampang untuk dipahamin. Untuk full code-nya bisa dilihat di github repository berikut:

Dependencies

Implementasi

Bikin fungsi NewLogger untuk menginisialisasi logger, dengan menambahkan hook seq ke instance logger sehingga setiap log message dari instance tersebut akan masuk ke seq service. Dan di sini menggunakan singleton design pattern, kenapa? karena aku pengen memastikan kalo instance logger hanya dieksekusi (dibuat atau dikonfigurasi) satu kali.

var (
logger *logrus.Logger
loggerInit sync.Once
)

func LogrusGetLevel(logLevel string) logrus.Level {
switch strings.ToLower(logLevel) {
case "fatal":
return logrus.FatalLevel
case "error":
return logrus.ErrorLevel
case "warn":
return logrus.WarnLevel
case "info":
return logrus.InfoLevel
case "debug":
return logrus.DebugLevel
case "trace":
return logrus.TraceLevel
}
return logrus.InfoLevel
}

func NewLogger() *logrus.Logger {
loggerInit.Do(func() {
logger = logrus.New()
logger.SetFormatter(&easy.Formatter{
TimestampFormat: FullTimeFormat,
LogFormat: fmt.Sprintf("%s\n", `[%lvl%]: "%time%" %msg%`),
})
logger.SetLevel(LogrusGetLevel("debug"))
logger.AddHook(logruseq.NewSeqHook("http://localhost:5341"))
})

return logger
}

Selanjutnya, http handler berikut mengirimkan log level info berserta data path dan response-nya.

 log := NewLogger()

app.Get("/", func(c *fiber.Ctx) error {
data := "Yahallo, World!"
fmt.Println(c.Request().URI())
log.Infof("Info | %s |%s", c.Request().URI().String(), data)
return c.SendString(data)
})

Pada saat API di-hit, log message akan dikirim ke seq.

Kustomisasi Log Message

Aku pengen log message berisi beberapa informasi seperti kapan endpoint di-hit, url endpoint yang di-hit, request body yang dikirimkan oleh klien, response yang didapat klien, lama durasi endpoint di-hit, message, dan nama project-nya.

func CreateLog(c *fiber.Ctx, log *logrus.Logger, code int, message string, respData ResponseData) {
reqBody := string(c.Request().Body())
path := c.Request().URI().String()

if code == fiber.StatusOK || code == fiber.StatusAccepted || code == fiber.StatusCreated {
log.WithFields(logrus.Fields{
"At": time.Now(),
"Method": c.Method(),
"Path": path,
"ParamRequest": reqBody,
"ParamResponse": respData,
"Message": message,
"Duration": time.Since(time.Now()),
"Project": projectName,
}).Info(c.Method() + " " + path)
} else if code == fiber.StatusBadRequest || code == fiber.StatusConflict || code == fiber.StatusUnauthorized || code == fiber.StatusNotFound {
log.WithFields(logrus.Fields{
"At": time.Now(),
"Method": c.Method(),
"Path": path,
"ParamRequest": reqBody,
"ParamResponse": respData,
"Message": message,
"Duration": time.Since(time.Now()),
"Project": projectName,
}).Warn(c.Method() + " " + path)
} else {
log.WithFields(logrus.Fields{
"At": time.Now(),
"Method": c.Method(),
"Path": path,
"ParamRequest": reqBody,
"ParamResponse": respData,
"Message": message,
"Duration": time.Since(time.Now()),
"Project": projectName,
}).Error(c.Method() + " " + path)
}
}
func Response_Log(ctx *fiber.Ctx, log *logrus.Logger, code int, message string, data interface{}) error {
RespData := ResponseData{
Data: data,
Status: StatusResponseData{
Code: code,
Message: message,
},
TimeStamp: GenerateTimeJakarta(),
}

CreateLog(ctx, log, code, message, RespData)
return ctx.Status(code).JSON(RespData)
}

Implementasi ke http handlers berikut:

 app.Get("/yahallos", func(c *fiber.Ctx) error {

data := []string{
"yahallo 1",
"yahallo 2",
"yahallo 3",
}

return Response_Log(c, log, fiber.StatusOK, "Success to get all yahallos", data)
})

app.Post("/say-yahallo", func(c *fiber.Ctx) error {
var payload SayYahalloReq

err := c.BodyParser(&payload)
if err != nil {
return Response_Log(c, log, fiber.StatusBadRequest, "fail to parse request body", nil)
}

data := fmt.Sprintf("%s (yahallo) %s", payload.Hello, payload.Name)

return Response_Log(c, log, fiber.StatusOK, "Success to say hello", data)
})

Hasil dari log message yang udah dikustomisasi sebelumnya.

Penutup

Sekian artikel “Install Seq sebagai Logger di Golang Project”, jika pengen diskusi atau ngasih feedback bisa tinggalkan di comment section. Jika ada pertanyaan “kemana log mestinya disimpan”, teman aku M. Zakiyuddin Munziri sempat open discussion di twitter

Aku harap kalian enjoy selama membaca dan menemukan apa yang kalian cari di artikel ini.

Thank you. Written on 22th August 2023

--

--