Um dos grandes problemas de aplicações, são Logs, e para sua solução existem diversas ferramentas que ajudam a coletar, armazenar e monitorar. Nesse post vou mostrar como configurar sua aplicação para que a saída de log com o logback
seja no formato dos coletores mais utilizados do mercado, o LogStash
. O LogStash
é um coletor / transformador de logs, no caso, muito utilizado na stack ELK
(Elastic-Search, Logstash, Kibana), ele coleta logs
de diferentes fontes e persiste no Elastic-search
; Não irei entrar em detalhes da configuração do LogStash
ou da ferramenta e sua utilização, irei apenas mostrar como configurar sua aplicação para que a saida do arquivo de log seja compatível com o coletor;
Dependências
Primeiramente vamos adicionar algumas dependências em nosso projeto:
<!-- Dependencia do log -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- Dependencias do logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Dependencia do encoder do logstash para o logback -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>6.0</version>
</dependency>
Configurando o logback.xml
Após configurado as dependências, iremos configurar nosso arquivo logback.xml
, o Logback
é a implementação da especificação do SLF4J
, sua implementação precisa de um arquivo de configuração aonde é definido o formato da saida de log;
O arquivo a seguir, inseri dois appenders, um para o formato json
e um para o formato tradicional de console, fiz isso para que seja mais fácil alternar de um para outro, tendo em vista que o formato json
é ruim para a leitura em desenvolvimento, porém não se esqueça que o formato json
é o formato em que o LogStash
irá reconhecer;
Note também que é nesse arquivo que iremos configurar o encoder
do formato da saída do log.
Encoder: “net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder”
<configuration>
<appender name="out-json" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<fieldName>ts</fieldName>
<timeZone>UTC</timeZone>
</timestamp>
<loggerName>
<fieldName>logger</fieldName>
</loggerName>
<logLevel>
<fieldName>severity</fieldName>
</logLevel>
<callerData>
<classFieldName>class</classFieldName>
<methodFieldName>method</methodFieldName>
<lineFieldName>line</lineFieldName>
<fileFieldName>file</fileFieldName>
</callerData>
<threadName>
<fieldName>thread</fieldName>
</threadName>
<mdc/>
<arguments>
<includeNonStructuredArguments>false</includeNonStructuredArguments>
</arguments>
<stackTrace>
<fieldName>stack</fieldName>
</stackTrace>
<message>
<fieldName>message</fieldName>
</message>
</providers>
</encoder>
</appender>
<appender name="out-console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<!--
Can be custom your own environment (LOG_APPENDER)
to load a specify appender ( out-json or out-console ).
Default value: out-json
-->
<appender-ref ref="${LOG_APPENDER:-out-json}"/>
</root>
</configuration>
Exemplo de uso
Praticamente é só isso! dessa maneira você já consegue logar usando o Logger
normalmente e toda a saída será reconhecida pelo leitor do LogStash
, exemplo de impressão de log:
package br.com.helpdev.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SampleLog {
private static final Logger LOG = LoggerFactory.getLogger(SampleLog.class);
public static void main(String[] args) {
LOG.info("Olá");
}
}
Saída no console:
{ “ts”:”2020-02-24T18:57:55.258Z”,
“logger”:”br.com.helpdev.log.SampleLog”,
“severity”:”INFO”,
“class”:”br.com.helpdev.log.SampleLog”,
“method”:”main”,
“file”:”SampleLog.java”,
“line”:11,
“thread”:”main”,
“message”:”Olá”
}
Configuração do Logstash
Como curiosidade estou inserindo a configuração do arquivo do logstash
para a leitura do arquivo de log. Disse que não entraria em detalhes sobre esse quesito, porém, o arquivo de configuração não é algo complexo que não possa ser mostrado;
Crie um arquivo para representar a leitura do seu log, exemplo logback.conf
, esse arquivo contém instruções para ler os arquivos de log de um path e jogor no elasticsearch
com seu devido index;
input {
file {
path => "/var/log/java/my_app/*.log"
codec => "json"
type => "logback"
}
}
output {
if [type]=="logback" {
elasticsearch {
hosts => [ "localhost:9200" ]
index => "logback-%{+YYYY.MM.dd}"
}
}
}
Para execução do Logstash
com esse arquivo de configuração, pode-se executa-lo da seguinte maneira:
$
bin/logstash
-f logback.conf
Structured Logging
Criando uma estrutura para o log
. Seria muito comum pensarmos em um objeto estruturado, ou até mesmo logar objetos complexos no formato que o logstash
consiga interpretar ( json
). Para isso a própria dependência do et.logstash.logback
nos fornece uma classe chamada StructuredArguments
que permite realizarmos conversões de objetos no formato adequado para a impressão de Log, podemos converter objetos complexos, dicionários, etc;
Veja a seguir como usar a StructuredArguments
para imprimirmos um objeto complexo em nosso Log;
package br.com.helpdev.log;
import net.logstash.logback.argument.StructuredArguments;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SampleLog {
private static final Logger LOG = LoggerFactory.getLogger(SampleLog.class);
public static void main(String[] args) {
SampleClass sampleClass = new SampleClass("Olá", 123);
LOG.info("Any message", StructuredArguments.keyValue("model", sampleClass));
}
public static class SampleClass {
private String arg1;
private int arg2;
public SampleClass(String arg1, int arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
}
public String getArg1() {
return arg1;
}
public int getArg2() {
return arg2;
}
}
}
Saída no console:
{ “ts”:”2020-02-24T19:22:00.907Z”,
“logger”:”br.com.helpdev.log.SampleLog”,
“severity”:”INFO”,
“class”:”br.com.helpdev.log.SampleLog”,
“method”:”main”,
“file”:”SampleLog.java”,
“line”:13,
“thread”:”main”,
“model”:{ “arg1″:”Olá”,
“arg2”:123
},
“message”:”Any message”
}
Note que agora temos como organizar todo nosso log, ou imprimir objetos complexos, tornando cada vez melhor e indexável ao ElasticSearch
nossos dados;
Referências:
https://www.innoq.com/en/blog/structured-logging/
https://www.baeldung.com/java-application-logs-to-elastic-stack
http://logback.qos.ch
https://github.com/jochenchrist/structured-logging-plain