Hi guys,
It’s the first time I use a logger. After a few searches, I ended up with zerolog
.
I’ve created a simple web server that call a dummy api endpoint to illustrate my concern.
GitHub Repo of the example: https://github.com/BigBoulard/logger
var (
router *gin.Engine
)
func main() {
// load the env variables
conf.LoadEnv()
// GinMode is set to "debug" in the /.env file
gin.SetMode(conf.Env.GinMode)
router = gin.Default()
// creates a controller that uses an httpclient to call https://jsonplaceholder.typicode.com/todos
controller := controller.NewController(
httpclient.NewClient(),
)
router.GET("/todos", controller.GetTodos)
// run the router on host and port specified in the /.env file
err := router.Run(fmt.Sprintf("%s:%s", conf.Env.Host, conf.Env.Port))
if err != nil {
log.Fatal("application/main", err)
}
}
As you already noticed, This server uses environment variables that need to be loaded differently if I’m working locally or if the server is in a production environment, right? So to manage this, I’ve created a conf
package which looks like this.
package conf
import (
"log"
"os"
"github.com/joho/godotenv"
)
var Env = &env{}
type env struct {
AppMode string
GinMode string
Host string
Port string
}
var IsLoaded = false
func LoadEnv() {
// the httpclient and the Gin server both need the env vars
// but we don't want to load them twice
if IsLoaded {
return
}
// if not "prod"
if Env.AppMode != "prod" {
curDir, err := os.Getwd()
if err != nil {
log.Fatal(err, "conf", "LoadEnv", "error loading os.Getwd()")
}
// load the /.env file
loadErr := godotenv.Load(curDir + "/.env")
if loadErr != nil {
log.Fatal(loadErr, "conf", "LoadEnv", "can't load env file from current directory: "+curDir)
}
Env.GinMode = "debug"
} else {
Env.GinMode = "release"
}
// load the env vars
Env.AppMode = os.Getenv("APP_MODE")
Env.Host = os.Getenv("HOST")
Env.Port = os.Getenv("PORT")
IsLoaded = true
}
To make a long story short, I’ve created a log
package that I would include in each microservice.
This log
package uses an environment variable at startup to determine the log level and decide whether to activate certain features. The log
package looks like this:
package log
var l *logger = NewLogger()
const API = "test api"
type logger struct {
logger zerolog.Logger // see https://github.com/rs/zerolog#leveled-logging
}
func NewLogger() *logger {
loadEnvFile()
var zlog zerolog.Logger
if os.Getenv("APP_ENV") == "dev" {
// setup for dev ...
} else {
// setup for prod ...
}
return &logger{
logger: zlog,
}
func Error(path string, err error) {
l.logger.
Error().
Stack().
Str("path", path).
Msg(err.Error())
}
func Fatal(path string, err error) {
l.logger.
Fatal().
Stack().
Str("path", path).
Msg(err.Error())
}
// PROBLEM: I need to duplicate loadEnvFile() from conf.load_env.go
// because conf uses log ... but conversely, log need conf cause it needs the env var
func loadEnvFile() {
curDir, err := os.Getwd()
if err != nil {
log.Fatal(err, "test api", "App", "gw - conf - LoadEnv - os.Getwd()")
}
loadErr := godotenv.Load(curDir + "/.env")
if loadErr != nil {
log.Fatal(err, "test api", "conf - LoadEnv", "godotenv.Load("+curDir+"/.env\")")
}
}
As you can see, I can’t use loadEnvFile()
from the conf
package as it causes a circular dependency, so I guess that there’s probably a better way to implement this…
Thank you so much.