Background
Simply put, a logger is a tool used to record or store information about activities generated by a program or system that has been equipped with a logger.
"Why install a logger?"
I'll draw from my own experience. Once, an application running in production suddenly crashed. Fortunately, the application already had a logger installed. When the crash occurred, a notification was immediately sent to the company's Slack channel. This allowed developers to notice and fix the issue quickly. During development, when QA finds a bug, it can be tedious and slow if we constantly ask for the payload that caused the bug or request QA to record a screen to show how to reproduce it (though there are cases where we need to understand how to reproduce it from the user's perspective).
Here are other reasons to use logging:
- Application Monitoring
- Application Performance Analysis
- Bug Tracking
- Audit and Security
- Documentation
Installing Seq
For this article, I'm using a Linux environment. According to the documentation, Seq doesn't provide a native Linux build, but they do provide a Docker image. So I'll use Docker to install Seq locally.
PH=$(echo '<password>' | docker run --rm -i datalust/seq config hash)
- Replace
<password>with the password you want to hash - This runs a Docker container with the
datalust/seqimage in interactive mode to generate a hash of the password used for the 'admin' account in Seq - Docker will automatically download the
datalust/seqimage if it doesn't already exist locally - The hashed password is stored in the
PHvariable
Next, run the following command to start a Docker container using the datalust/seq image:
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
After successfully running the above command, check the Docker container status with docker ps. If it fails, you can check the error logs with docker logs.
Open the Seq UI at http://localhost:80, then enter the username and password (before hashing) that you used when running the Docker container.
Go Seq Logger
I'm using the Fiber framework and a flat project structure for easier understanding. For the complete code, see the GitHub repository link. Source Code
Implementation
Create a NewLogger function to initialize the logger by adding a Seq hook to the logger instance, so every log message from that instance will be sent to the Seq service. I'm using the singleton design pattern here because I want to ensure the logger instance is created and configured only once.
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
}
The following HTTP handler sends an info log level with path data and the response:
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)
})
Customizing Log Messages
I wanted log messages to contain information such as when the endpoint was hit, which endpoint URL was accessed, the request body sent by the client, the response received by the client, the duration of the endpoint call, message, and project name.
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)
}
Implementation in HTTP handlers:
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)
})
Output from the customized log message:
Closing
That concludes the article "Installing Seq as a Logger in a Golang Project." I hope you enjoyed reading this article and found what you were looking for.