Escrevendo logs em formato JSON com zerolog

Algum tempo atrás fizemos um post falando sobre como escrever logs em arquivos. No post em questão, logamos algumas mensagens de forma bem simples. Normalmente esses logs em texto puro são um pouco complicados para filtrar mensagens, já que é preciso muito regex.

Nesse post vamos abordar os logs de uma forma diferente, pois vamos escreve-los em formato JSON.

Para nos auxiliar nessa tarefa, vamos utilizar um package chamado zerolog. Esse package foi criado pelo Olivier Poitrey, atual diretor de engenharia do Netflix.

Gosto muito desse package pois ele é fácil de usar e tem uma performance muito boa.

Sem mais enrolação, vamos instalar o package e começar a brincar com ele.

$ go get -u github.com/rs/zerolog/log

Com o package em nossa máquina, vamos escrever um pequeno programa para logar um “Olá Mundo!”.

package main

import (
    "github.com/rs/zerolog/log"
)

func main() {
    log.Info().Msg("Olá Mundo!")
}

Ao fazer o go run main.go teremos o seguinte output:

{"level":"info","time":"2021-12-17T05:37:50-03:00","message":"olá mundo!"}

Agora que já vimos como será o output, vamos fazer uma pequena mudança no código para que esse log vá para um arquivo.

Primeira coisa que precisamos fazer é abrir o arquivo para que o zerolog possa escrever nele.

func main() {
    file, err := os.OpenFile("zero.log", os.O_APPEND|os.O_RDWR|os.O_CREATE, 0644)
    if err != nil {
        log.Panic().Err(err)
    }
    defer file.Close()
}

Feito isso, agora precisamos iniciar um novo logger e configurá-lo para utilizar o nosso arquivo como output dos logs.

import (
    "os"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    file, err := os.OpenFile("zero.log", os.O_APPEND|os.O_RDWR|os.O_CREATE, 0644)
    if err != nil {
        log.Panic().Err(err)
    }
    defer file.Close()

    logger := zerolog.New(file).With().Timestamp().Logger()

    logger.Debug().Msg("Isso é só para debug")
    logger.Info().Msg("Olá Mundo!")
    logger.Warn().Msg("CUIDADO!!!")
}

Ao fazer go run main.go devemos ver um novo arquivo chamado zero.log em nosso diretório com o seguinte conteúdo.

{"level":"debug","time":"2021-12-17T06:06:11-03:00","message":"Isso é só para debug"}
{"level":"info","time":"2021-12-17T06:06:11-03:00","message":"Olá Mundo!"}
{"level":"warn","time":"2021-12-17T06:06:11-03:00","message":"CUIDADO!!!"}

O problema agora é que ele sempre vai escrever os logs para o arquivo, até mesmo quando estivermos em ambiente de desenvolvimento. Para dar uma incrementada no nosso programa, vamos adicionar a flag -prod para que ele só escreva em arquivo quando o valor dela for true.

import (
    "os"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    prod := flag.Bool("prod", false, "write log to file")

    flag.Parse()

    logger := zerolog.New(os.Stdout)

    if *prod {
        file, err := os.OpenFile("zero.log", os.O_APPEND|os.O_RDWR|os.O_CREATE, 0644)
        if err != nil {
            log.Panic().Err(err)
        }
        defer file.Close()

        logger = zerolog.New(file).With().Timestamp().Logger()
    }

    logger.Debug().Msg("Isso é só para debug")
    logger.Info().Msg("Olá Mundo!")
    logger.Warn().Msg("CUIDADO!!!")
}

Ao executar go run main.go, devemos receber o output no terminal:

{"level":"debug","message":"Isso é só para debug"}
{"level":"info","message":"Olá Mundo!"}
{"level":"warn","message":"CUIDADO!!!"}

Para que ele escreva no arquivo é só adicionar a flag -prod ao comando executado.

Por último, normalmente quando temos um programa em produção, não queremos que ele fique salvando as mensagens de debug o tempo todo.

Com isso em mente, vamos adicionar mais uma flag para controlar o level de log que queremos.

import (
    "os"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    prod := flag.Bool("prod", false, "write log to file")
    debug := flag.Bool("debug", false, "enable debug mode")

    flag.Parse()

    zerolog.SetGlobalLevel(zerolog.DebugLevel)
    logger := zerolog.New(os.Stdout)

    if *prod {
        if !*debug {
            zerolog.SetGlobalLevel(zerolog.InfoLevel)
        }

        file, err := os.OpenFile("zero.log", os.O_APPEND|os.O_RDWR|os.O_CREATE, 0644)
        if err != nil {
            log.Panic().Err(err)
        }
        defer file.Close()

        logger = zerolog.New(file).With().Timestamp().Logger()
    }

    logger.Debug().Msg("Isso é só para debug")
    logger.Info().Msg("Olá Mundo!")
    logger.Warn().Msg("CUIDADO!!!")
}

Pronto! Agora quando executarmos o programa sem a flag -prod, utilizaremos o level de debug. Quando a flag -prod for utilizada, por padrão vamos usar o level info, apenas usando o de debug se a flag -debug for utilizada em conjunto.

Deixem suas dúvidas nos comentários.

Até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta