Golang ile Proje Çapında Global Loglama
Merhabalar, bu yazımızda Golang’da proje çapında loglama işleminin nasıl yapılacağını anlatıyorum. Proje çapında loglama derken kastteiğim şey, projedeki her paketten global loglama değişkenini çağırarak log dosyasına not düşebilmek. Ayrıca Gin-Gonic ile yazdığımız REST API’mize gelen her isteği de yine bu yapı sayesinde loglayabiliyor ve gerekli durumlarda debug yapabiliyoruz.
Proje Yapısı
Burada kullanacağımız paketler, logger
ve middlewares
paketleri.
- Logger paketi, Go’nun varsayılan “log” paketi kullanılarak geliştirilen “logrus”u kullanıyor. Logger paketi sayesinde “
logger.Log.Infoln(”Log mesajı”)
” şeklinde logger’imizi herhangi bir paketten çağırabileceğiz.
- Middlewares paketi ise Gin-Gonic ile yazdığımız REST API’ye gelen istekleri keserek istek bilgilerini alacak ve ardından bu bilgileri “api.log” dosyamıza yazacak.
Logger Paketini Hazırlayalım
logger/logger.go dosyasında aşağıdaki gibi paket tanımımızı oluşturup ardından kullanacağımız logrus
ve godotenv
paketlerini ve diğer built-in paketleri import edelim.
ve diğer built-in paketleri import edelim.
package logger
import (
"io"
logging "log"
"os"
"strconv"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
)
var (
Log *logrus.Logger // share will all packages
)
Ayrıca, burada diğer paketlerden de erişerek kullanacağımız “Log” değişkenini global olarak tanımlayalım.
- Global değişken tanımlama: Go’da bir değişkeni global olarak tanımlamak için paketin en tepesinde (herhangi bir fonksiyonun içine yazmadan) “var” belirteci ile tanımlarız. Değişken ve fonksiyonları
public
yapmak için ise ilk harfini büyük harf yaparız, eğerprivate
yapmamız gerekirse ilk harfi küçük olmalıdır. Bu değişken proje çapında global olarak kullanacağımız için public yapıyoruz.
Artık fonksiyonlarımızı yazmaya başlayabiliriz. Burada kullanacağımız ilk fonksiyon init fonksiyonu. Init fonksiyonu, paketin varsayılan fonksiyonu olup doğrudan çalıştırılır. Herhangi bir konumdan onu çağırmanıza gerek kalmaz.
package logger
import (
"io"
logging "log"
"os"
"strconv"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
)
var (
Log *logrus.Logger // share will all packages
)
func init() {
// The file needs to exist prior
f, err := os.OpenFile("api.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
// Use go's logger, while we configure Logrus
logging.Fatalf("error opening file: %v", err)
}
}
Şuanki tanımlama ile “api.log” adında bir dosya oluşturup bu dosya için kendimize okuma, oluşturma ve yazma izinlerini verdik. Eğer dosya açılmazsa da Go’nun default log paketini kullanarak bir hata logunu ekrana basacağız.
Sırada ise logrus
paketini projemize ekleyip konfigüre etmek var. Burada çıktıyı text formatında istediğim için TextFormatter’i kullandım. Ayrıca logları hem log dosyamıza hem de ekrana basacak şekilde konifgürasyonu tamamladım.
package logger
import (
"io"
logging "log"
"os"
"strconv"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
)
var (
Log *logrus.Logger // share will all packages
)
func init() {
// The file needs to exist prior
f, err := os.OpenFile("api.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
// Use go's logger, while we configure Logrus
logging.Fatalf("error opening file: %v", err)
}
// Configure Logrus
Log = logrus.New()
Log.Formatter = &logrus.TextFormatter{}
Log.SetReportCaller(true)
mw := io.MultiWriter(os.Stdout, f)
Log.SetOutput(mw)
}
Şimdi ise sırada logger’imize son dokunuşları yapmak var. Aşağıda eklediğim kod bloğu ile de projemizde bulunan .env dosyasını okuyarak buradaki “DEBUG_MODE” değişkenine göre loglama işlemini debug ve üstü seviyeye getirdim.
package logger
import (
"io"
logging "log"
"os"
"strconv"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
)
var (
Log *logrus.Logger // share will all packages
)
func init() {
// The file needs to exist prior
f, err := os.OpenFile("api.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
// Use go's logger, while we configure Logrus
logging.Fatalf("error opening file: %v", err)
}
// Configure Logrus
Log = logrus.New()
Log.Formatter = &logrus.TextFormatter{}
Log.SetReportCaller(true)
mw := io.MultiWriter(os.Stdout, f)
Log.SetOutput(mw)
// Set the log level from .env file
debug_mode := goDotEnvVariable("DEBUG_MODE")
debug_mode_bool, _ := strconv.ParseBool(debug_mode)
if debug_mode_bool == true {
Log.Level = logrus.DebugLevel
Log.Infoln("DEBUG_MODE=" + debug_mode)
} else {
Log.Level = logrus.InfoLevel
Log.Infoln("DEBUG_MODE=" + debug_mode)
}
}
HTTP İsteklerini Loglamak İçin Middleware Paketini Hazırlayalım
logger paketimiz artık kullanıma hazır ve diğer tüm paketlerden (main, controller vs.) çağrılabilir. Şimdi middlewares paketinde bulunan loggingMiddleware.go dosyasını konfigüre edelim.
package middlewares
import (
"time"
"github.com/rbozburun/rest_api/logger"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func LoggingMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
// Starting time request
startTime := time.Now()
// Processing request
ctx.Next()
// End Time request
endTime := time.Now()
// execution time
latencyTime := endTime.Sub(startTime)
// Request method
reqMethod := ctx.Request.Method
// Request route
reqUri := ctx.Request.RequestURI
// status code
statusCode := ctx.Writer.Status()
// Request IP
clientIP := ctx.ClientIP()
//Use global logger as middleware logger
logger.Log.WithFields(log.Fields{
"METHOD": reqMethod,
"URI": reqUri,
"STATUS": statusCode,
"LATENCY": latencyTime,
"CLIENT_IP": clientIP,
}).Info("HTTP REQUEST")
ctx.Next()
}
}
Yukarıdaki kodda gin’in contextini alıp bu context içerisindeki client ip, method, uri, status ve latency bilgilerini loglaması için logger’a ilettik. Sırada son bir adım kaldı; Gin Framework’ünü bu middleware’i kullanacak şekilde yapılandırmak.
Ben gin’i main içerisinde başlattığım için middleware’i kullandırtan kodu da burada ekledim.
package main
import (
"github.com/rbozburun/rest_api/config"
"github.com/rbozburun/rest_apilogger"
"github.com/rbozburun/rest_api/middlewares"
"github.com/rbozburun/rest_api/routes"
"github.com/gin-gonic/gin"
)
func main() {
// Connect o db
config.Connect()
logger.Log.Debugln("API Main function started...")
router := gin.Default()
// Use middleware
router.Use(gin.Recovery())
router.Use(middlewares.LoggingMiddleware())
// Init UserRoute router
routes.UserRoute(router)
logger.Log.Debugln("UserRoute initiliazed.")
// Init Login router
routes.LoginRoute(router)
logger.Log.Debugln("LoginRoute initiliazed.")
// Start the API service
router.Run("0.0.0.0:8080")
}
Artık herşey hazır, logger’i diğer paketlerinizde import ederek “logger.Log.<logLevel(”msg”)>
” şeklinde çağırıp kullanabilirsiniz. UserController içerisindeki örnek bir kullanımını aşağıya bırakıyorum.
package controller
import (
"crypto/sha256"
"fmt"
"net/http"
"strconv"
"github.com/rbozburun/rest_api/config"
"github.com/rbozburun/rest_api/logger"
"github.com/rbozburun/rest_api/models"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// POST /api/v1/user
func CreateUser(ctx *gin.Context) {
logger.Log.Debugln("CreateUser controller called.")
var user models.User
ctx.ShouldBindJSON(&user)
token := uuid.Must(uuid.NewRandom()).String()
// Convert the password of the user to sha256 hash & set
if user.Password != "" {
pwdHash := sha256.Sum256([]byte(user.Password))
// String reference of created hash
pwdHash_asStr := fmt.Sprintf("%x", pwdHash[:])
user.Password = pwdHash_asStr
} else {
// Throw error
ctx.JSON(http.StatusBadRequest, gin.H{"Error": "Provide password!"})
logger.Log.Debugln("User password is empty.")
return
}
// Convert created access to token to sha256
hashed_token := sha256.Sum256([]byte(token))
// String reference of created hash
tokenhash_asStr := fmt.Sprintf("%x", hashed_token[:])
// Set the user's token as hashed access token (string reference)
user.AccessToken = tokenhash_asStr
if err := config.DB.Create(&user).Error; err != nil {
// Throw error
logger.Log.Debugln("User creation error. Probable there is smth with database.")
ctx.JSON(http.StatusBadRequest, gin.H{"Error": err.Error()})
return
} else {
// Create user succesfuly and respond with access token (non-hashed)
ctx.JSON(http.StatusCreated, gin.H{"token": token})
logger.Log.Infoln("A new user created.")
}
}
Örnek bir log çıktısı aşağıdaki gibidir.
time="2023-04-21T16:55:55+03:00" level=info msg="DEBUG_MODE=false" func=github.com/testuser/main_rest_api/logger.init.0 file="C:/Users/ResulBozburun/Desktop/Personal/testuser_Main_API/api/logger/logger.go:54"
time="2023-04-21T16:56:13+03:00" level=info msg="Access token provided." func=github.com/testuser/main_rest_api/controller.Login file="C:/Users/ResulBozburun/Desktop/Personal/testuser_Main_API/api/controller/login.go:34"
time="2023-04-21T16:56:13+03:00" level=info msg="HTTP REQUEST" func=github.com/testuser/main_rest_api/middlewares.LoggingMiddleware.func1 file="C:/Users/ResulBozburun/Desktop/Personal/testuser_Main_API/api/middlewares/loggingMiddleware.go:44" CLIENT_IP=127.0.0.1 LATENCY=2.6564ms METHOD=POST STATUS=200 URI=/api/1.0/login
time="2023-04-21T16:56:24+03:00" level=info msg="Access token provided." func=github.com/testuser/main_rest_api/controller.Login file="C:/Users/ResulBozburun/Desktop/Personal/testuser_Main_API/api/controller/login.go:34"
time="2023-04-21T16:56:24+03:00" level=info msg="HTTP REQUEST" func=github.com/testuser/main_rest_api/middlewares.LoggingMiddleware.func1 file="C:/Users/ResulBozburun/Desktop/Personal/testuser_Main_API/api/middlewares/loggingMiddleware.go:44" CLIENT_IP=127.0.0.1 LATENCY=1.8069ms METHOD=POST STATUS=200 URI=/api/1.0/login
Uzun süre Blogger’de acaaba.com adresinde yazılar yazdım. Genel olarak teknoloji, sanat ve psikoloji üzerine bir internet sitesi. Yaklaşık 3 yıldır aralıklı olarak siber güvenlik ile ilgilendim, 2 yıldır da profesyonel olarak bu alanda çeşitli çalışmalar yapmaktayım.