API de log da plataforma Java 9
1. Introdução
Neste tutorial, vamos explorar a API Logging recém-introduzida no Java 9 e implementar alguns exemplos para cobrir os casos mais comuns.
Essa API foi introduzida em Java paraprovide a common mechanism to handle all the platform logs and to expose a service interface that can be customized by libraries and applications.. Dessa forma, os logs da plataforma JDK podem usar a mesma estrutura de log que o aplicativo e as dependências do projeto podem ser reduzidas.
2. Criação de uma implementação personalizada
Nesta seção, mostraremos as principais classes da API Logging que devemos implementar para criar um novo registrador. Faremos isso implementando um registrador simples que imprime todos os registros no console.
2.1. Criando oLogger
A principal classe que devemos criar é aLogger. Esta classe deve implementar a interfaceSystem.Logger e pelo menos estes quatro métodos:
-
getName(): retorna o nome do logger. Ele será usado pelo JDK para criar registradores por nome
-
isLoggable(): indica para quais níveis o registrador está habilitado
-
log(): é o método que imprime o log em qualquer sistema subjacente que o aplicativo esteja usando - o console em nosso caso. Existem métodos de 2log() para implementar, cada um deles recebendo parâmetros diferentes
Vamos ver como nossa implementação ficará:
public class ConsoleLogger implements System.Logger {
@Override
public String getName() {
return "ConsoleLogger";
}
@Override
public boolean isLoggable(Level level) {
return true;
}
@Override
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
System.out.printf("ConsoleLogger [%s]: %s - %s%n", level, msg, thrown);
}
@Override
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
System.out.printf("ConsoleLogger [%s]: %s%n", level,
MessageFormat.format(format, params));
}
}
Nossa classeConsoleLogger substitui os quatro métodos mencionados. O métodogetName() retorna umString,, enquanto o métodoisLoggable() retornatrue em todos os casos. Por fim, temos o método 2log() que dá saída para o console.
2.2. Criando oLoggerFinder
Assim que tivermos nosso registrador criado,we need to implement a LoggerFinder that creates instances of our ConsoleLogger.
Para fazer isso, temos que estender a classe abstrataSystem.LoggerFindere implementar o métodogetLogger():
public class CustomLoggerFinder extends System.LoggerFinder {
@Override
public System.Logger getLogger(String name, Module module) {
return new ConsoleLogger();
}
}
Nesse caso, estamos sempre retornando nossoConsoleLogger.
Finally, we need to register our LoggerFinder as a Service so it can be discovered by the JDK. Se não fornecermos uma implementação, oSimpleConsoleLogger será usado por padrão.
O mecanismo usado pelo JDK para carregar as implementações é oServiceLoader. Você pode encontrar mais informações sobre isso emthis tutorial.
Como estamos usando o Java 9, empacotaremos nossa classe em um módulo e registraremos nosso serviço no arquivomodule-info.java:
module com.example.logging {
provides java.lang.System.LoggerFinder
with com.example.logging.CustomLoggerFinder;
exports com.example.logging;
}
Para obter mais informações sobre módulos Java, verifiquethis other tutorial.
2.3. Testando nosso exemplo
Para testar nosso exemplo, vamos criar outro módulo que funcionará como um aplicativo. Isso conterá apenas a classeMain que usa nossa implementação de serviço.
Esta classe obterá uma instância de nossoConsoleLogger chamando o métodoSystem.getLogger():
public class MainApp {
private static System.Logger LOGGER = System.getLogger("MainApp");
public static void main(String[] args) {
LOGGER.log(Level.ERROR, "error test");
LOGGER.log(Level.INFO, "info test");
}
}
Internamente, o JDK pegará nossa implementação deCustomLoggerFinder e criará uma instância de nossoConsoleLogger.
Depois disso, vamos criar o arquivomodule-info para este módulo:
module com.example.logging.app {
}
Nesse ponto, nossa estrutura de projeto ficará assim:
├── src
│ ├── modules
│ │ ├── com.example.logging
│ │ │ ├── com
│ │ │ │ └── example
│ │ │ │ └── logging
│ │ │ │ ├── ConsoleLogger.java
│ │ │ │ └── CustomLoggerFinder.java
│ │ │ └── module-info.java
│ │ ├── com.example.logging.app
│ │ │ ├── com
│ │ │ │ └── example
│ │ │ │ └── logging
│ │ │ │ └── app
│ │ │ │ └── MainApp.java
│ │ │ └── module-info.java
└──
Finalmente, vamos compilar nossos dois módulos e colocá-los em um diretóriomods:
javac --module-path mods -d mods/com.example.logging \
src/modules/com.example.logging/module-info.java \
src/modules/com.example.logging/com/example/logging/*.java
javac --module-path mods -d mods/com.example.logging.app \
src/modules/com.example.logging.app/module-info.java \
src/modules/com.example.logging.app/com/example/logging/app/*.java
Finalmente, vamos executar a classeMain do móduloapp:
java --module-path mods \
-m com.example.logging.app/com.example.logging.app.MainApp
Se dermos uma olhada na saída do console, podemos ver que nossos logs são impressos usando nossoConsoleLogger:
ConsoleLogger [ERROR]: error test
ConsoleLogger [INFO]: info test
3. Adicionar uma estrutura de registro externo
No exemplo anterior, estávamos registrando todas as nossas mensagens no console, que é igual ao que o criador de logs padrão faz. One of the most useful uses of the Logging API in Java 9 is to let applications route the JDK logs to the same logging framework the application is using, e é isso que faremos nesta seção.
Vamos criar um novo módulo que usa SLF4J como fachada de registro e Logback como estrutura de registro.
Como já explicamos o básico na seção anterior, agora podemos nos concentrar em como adicionar uma estrutura de registro externa.
3.1. Implementações personalizadas usando SLF4J
Primeiro, implementaremos outroLogger que criará um novo registrador SLF4J para cada instância:
public class Slf4jLogger implements System.Logger {
private final String name;
private final Logger logger;
public Slf4jLogger(String name) {
this.name = name;
logger = LoggerFactory.getLogger(name);
}
@Override
public String getName() {
return name;
}
//...
}
Observe que esteLogger é umorg.slf4j.Logger.
Para o restante dos métodos,we’ll rely on the implementation on the SLF4J logger instance. Portanto, nossoLogger será habilitado se o registrador SLF4J estiver habilitado:
@Override
public boolean isLoggable(Level level) {
switch (level) {
case OFF:
return false;
case TRACE:
return logger.isTraceEnabled();
case DEBUG:
return logger.isDebugEnabled();
case INFO:
return logger.isInfoEnabled();
case WARNING:
return logger.isWarnEnabled();
case ERROR:
return logger.isErrorEnabled();
case ALL:
default:
return true;
}
}
E os métodos de log chamarão o método de log SLF4J apropriado, dependendo do nível de log usado:
@Override
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
if (!isLoggable(level)) {
return;
}
switch (level) {
case TRACE:
logger.trace(msg, thrown);
break;
case DEBUG:
logger.debug(msg, thrown);
break;
case INFO:
logger.info(msg, thrown);
break;
case WARNING:
logger.warn(msg, thrown);
break;
case ERROR:
logger.error(msg, thrown);
break;
case ALL:
default:
logger.info(msg, thrown);
}
}
@Override
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
if (!isLoggable(level)) {
return;
}
String message = MessageFormat.format(format, params);
switch (level) {
case TRACE:
logger.trace(message);
break;
// ...
// same as the previous switch
}
}
Finalmente, vamos criar um novoLoggerFinder que usa nossoSlf4jLogger:
public class Slf4jLoggerFinder extends System.LoggerFinder {
@Override
public System.Logger getLogger(String name, Module module) {
return new Slf4jLogger(name);
}
}
3.2. Configuração do módulo
Assim que tivermos todas as nossas classes implementadas, vamos registrar nosso serviço em nosso módulo e adicionar a dependência do módulo SLF4J:
module com.example.logging.slf4j {
requires org.slf4j;
provides java.lang.System.LoggerFinder
with com.example.logging.slf4j.Slf4jLoggerFinder;
exports com.example.logging.slf4j;
}
Este módulo terá a seguinte estrutura:
├── src
│ ├── modules
│ │ ├── com.example.logging.slf4j
│ │ │ ├── com
│ │ │ │ └── example
│ │ │ │ └── logging
│ │ │ │ └── slf4j
│ │ │ │ ├── Slf4jLoggerFinder.java
│ │ │ │ └── Slf4jLogger.java
│ │ │ └── module-info.java
└──
Agora podemos compilar este módulo no diretóriomods como fizemos na seção anterior.
Notice that we have to place the slf4j-api jar in the mods directory to compile this module. Also, keep in mind to use a modularized version of the library. A versão mais recente pode ser encontrada emMaven Central.
3.3. Adicionando Logback
Estamos quase terminando, mas ainda precisamos adicionar as dependências e configurações do Logback. Para fazer isso, coloque os jarslogback-classicelogback-core no diretóriomods.
As before, we have to make sure we’re using a modularized version of the library. Novamente, a versão mais recente pode ser encontrada emMaven Central.
Por fim, vamos criar um arquivo de configuração Logback e colocá-lo em nosso diretóriomods:
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} -- %msg%n
3.4. Executando nosso aplicativo
Neste ponto, podemos executar nossoapp usando nosso módulo SLF4J.
Neste caso,we also need to specify our Logback configuration file:
java --module-path mods \
-Dlogback.configurationFile=mods/logback.xml \
-m com.example.logging.app/com.example.logging.app.MainApp
Finalmente, se verificarmos a saída, podemos ver que nossos logs são impressos usando nossa configuração de Logback:
2018-08-25 14:02:40 [main] ERROR MainApp -- error test
2018-08-25 14:02:40 [main] INFO MainApp -- info test
4. Conclusão
Mostramos neste artigo como criar um registrador personalizado em Java 9 usando a nova API Platform Logging. Além disso, implementamos um exemplo usando uma estrutura de registro externa, que é um dos casos de uso mais úteis desta nova API.
Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.