Pela primeira vez na minha vida, me encontro em uma posição em que estou escrevendo uma API Java que será de código aberto. Espero que seja incluído em muitos outros projetos.
Para o registro, eu (e de fato as pessoas com quem trabalho) sempre usei o JUL (java.util.logging) e nunca tive problemas com ele. No entanto, agora eu preciso entender com mais detalhes o que devo fazer para o desenvolvimento da minha API. Eu fiz algumas pesquisas sobre isso e com as informações que tenho, fico mais confuso. Daí este post.
Desde que eu venho de JUL, estou inclinado a isso. Meu conhecimento do resto não é tão grande.
A partir da pesquisa que fiz, descobri estas razões pelas quais as pessoas não gostam do JUL:
"Comecei a desenvolver em Java muito antes da Sun lançar o JUL e era mais fácil continuar com o logging-framework-X do que aprender algo novo" . Hmm. Não estou brincando, é isso que as pessoas dizem. Com esse argumento, todos nós poderíamos estar fazendo COBOL. (no entanto, eu certamente posso me relacionar com esse ser um cara preguiçoso)
"Não gosto dos nomes dos níveis de registro em JUL" . Ok, sério, isso não é motivo suficiente para introduzir uma nova dependência.
"Não gosto do formato padrão da saída de JUL" . Hmm. Isso é apenas configuração. Você nem precisa fazer nada em termos de código. (verdade, nos velhos tempos, você pode ter que criar sua própria classe Formatter para fazer a coisa certa).
"Eu uso outras bibliotecas que também usam o logging-framework-X, então achei mais fácil usar essa" . Este é um argumento circular, não é? Por que 'todo mundo' usa o logging-framework-X e não o JUL?
"Todo mundo está usando o logging-framework-X" . Isso para mim é apenas um caso especial do exposto acima. A maioria nem sempre está certa.
Então a grande questão é: por que não JUL? . Do que sinto falta? A razão de ser das fachadas de log (SLF4J, JCL) é que várias implementações de log existem historicamente e a razão disso realmente remonta à era anterior a JUL, como eu a vejo. Se JUL fosse perfeito, as fachadas de madeira não existiriam, ou o quê? Para tornar as coisas mais confusas, o JUL é, de certa forma, uma fachada, permitindo que os manipuladores, formatadores e até o LogManager sejam trocados.
Em vez de adotar várias maneiras de fazer a mesma coisa (registro), não deveríamos questionar por que elas eram necessárias em primeiro lugar? (e veja se esses motivos ainda existem)
Ok, minha pesquisa até agora levou a algumas coisas que eu posso ver que podem ser problemas reais com JUL:
Desempenho . Alguns dizem que o desempenho no SLF4J é superior ao resto. Parece-me um caso de otimização prematura. Se você precisar registrar centenas de megabytes por segundo, não tenho certeza se você está no caminho certo. O JUL também evoluiu e os testes que você fez no Java 1.4 podem não ser mais verdadeiros. Você pode ler sobre isso aqui e esta correção chegou ao Java 7. Muitos também falam sobre a sobrecarga da concatenação de strings nos métodos de log. No entanto, o log baseado em modelo evita esse custo e ele também existe em JUL. Pessoalmente, eu nunca escrevo logs baseados em modelos. Com preguiça de fazer isso. Por exemplo, se eu fizer isso com JUL:
log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY));
meu IDE me avisará e pedirá permissão para alterá-lo para:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY});
.. o que eu obviamente aceitarei. Permissão garantida ! Obrigado pela ajuda.
Então, eu realmente não escrevo essas declarações pessoalmente, isso é feito pelo IDE.
Concluindo sobre a questão do desempenho, não encontrei nada que sugerisse que o desempenho do JUL não seja bom em comparação com a concorrência.
Configuração do caminho de classe . O JUL pronto para o uso não pode carregar um arquivo de configuração do caminho de classe. São algumas linhas de código para fazê-lo. Eu posso ver por que isso pode ser irritante, mas a solução é curta e simples.
Disponibilidade de manipuladores de saída . O JUL vem com 5 manipuladores de saída prontos para uso: console, fluxo de arquivos, soquete e memória. Estes podem ser estendidos ou novos podem ser escritos. Por exemplo, isso pode estar gravando no Syslog do UNIX / Linux e no Log de Eventos do Windows. Pessoalmente, nunca tive esse requisito nem o vi usado, mas certamente posso me relacionar com o porquê de ser um recurso útil. O Logback vem com um aplicativo para o Syslog, por exemplo. Ainda assim, eu argumentaria que
- 99,5% das necessidades de destinos de saída são cobertas pelo que está em JUL pronto para uso.
- Necessidades especiais podem ser atendidas por manipuladores personalizados em cima de JUL em vez de em cima de outra coisa. Não há nada que sugira que demore mais tempo para escrever um manipulador de saída Syslog para JUL do que para outra estrutura de log.
Estou realmente preocupado que haja algo que eu tenha esquecido. O uso de fachadas de log e implementações de log diferentes de JUL é tão difundido que tenho que chegar à conclusão de que sou eu quem simplesmente não entende. Receio que não seja a primeira vez. :-)
Então, o que devo fazer com a minha API? Eu quero que seja bem sucedido. É claro que posso simplesmente "seguir o fluxo" e implementar o SLF4J (que parece o mais popular hoje em dia), mas, por mim mesmo, ainda preciso entender exatamente o que há de errado com o JUL de hoje que justifica toda essa confusão. Vou me sabotar escolhendo JUL para minha biblioteca?
Teste de desempenho
(seção adicionada por nolan600 em 07-JUL-2012)
Há uma referência abaixo de Ceki sobre a parametrização do SLF4J ser 10 vezes ou mais rápida que a JUL. Então eu comecei a fazer alguns testes simples. À primeira vista, a afirmação está certamente correta. Aqui estão os resultados preliminares (mas continue a ler!):
- Tempo de execução SLF4J, back-end Logback: 1515
- Tempo de execução SLF4J, back-end JUL: 12938
- Tempo de execução JUL: 16911
Os números acima são ms, portanto menos é melhor. Então, 10 vezes a diferença de desempenho é, na verdade, bem próxima. Minha reação inicial: Isso é muito!
Aqui está o núcleo do teste. Como pode ser visto, um número inteiro e uma string são construídos em um loop, que é usado na instrução de log:
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(Eu queria que a instrução de log tivesse um tipo de dados primitivo (neste caso, um int) e um tipo de dados mais complexo (nesse caso, uma String). Não tenho certeza se isso importa, mas você o possui.)
A instrução de log para SLF4J:
logger.info("Logging {} and {} ", i, someString);
A instrução de log para JUL:
logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});
A JVM foi 'aquecida' com o mesmo teste executado uma vez antes da medição real ter sido feita. O Java 1.7.03 foi usado no Windows 7. As versões mais recentes do SLF4J (v1.6.6) e Logback (v1.0.6) foram usadas. Stdout e stderr foram redirecionados para o dispositivo nulo.
No entanto, com cuidado agora, verifica-se que JUL está gastando a maior parte do tempo getSourceClassName()
porque JUL, por padrão, imprime o nome da classe de origem na saída, enquanto o Logback não. Então, estamos comparando maçãs e laranjas. Eu tenho que fazer o teste novamente e configurar as implementações de log de maneira semelhante para que elas realmente produzam as mesmas coisas. No entanto, desconfio que o SLF4J + Logback ainda esteja no topo, mas longe dos números iniciais, como indicado acima. Fique ligado.
Btw: O teste foi a primeira vez que trabalhei com SLF4J ou Logback. Uma experiência agradável. JUL é certamente muito menos acolhedor quando você está começando.
Testando o desempenho (parte 2)
(seção adicionada por nolan600 em 08-JUL-2012)
Como se vê, não importa muito para o desempenho como você configura seu padrão em JUL, ou seja, se inclui ou não o nome da fonte. Eu tentei com um padrão muito simples:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
e isso não mudou os horários acima. Meu criador de perfil revelou que o criador de logs ainda passava muito tempo em chamadas, getSourceClassName()
mesmo que isso não fizesse parte do meu padrão. O padrão não importa.
Portanto, estou concluindo sobre a questão do desempenho que, pelo menos para a declaração de log baseada em modelo testada, parece haver um fator de aproximadamente 10 na diferença real de desempenho entre JUL (lento) e SLF4J + Logback (rápido). Assim como Ceki disse.
Também posso ver outra coisa, a saber, que a getLogger()
ligação do SLF4J é muito mais cara que a da JUL. (95 ms vs 0,3 ms se meu criador de perfil for preciso). Isso faz sentido. O SLF4J precisa dedicar algum tempo à ligação da implementação de log subjacente. Isso não me assusta. Essas chamadas devem ser um pouco raras durante a vida útil de um aplicativo. A rapidez deve estar nas chamadas de log reais.
Conclusão final
(seção adicionada por nolan600 em 08-JUL-2012)
Obrigado por todas as suas respostas. Ao contrário do que pensei inicialmente, decidi usar o SLF4J para minha API. Isso é baseado em várias coisas e na sua entrada:
Ele oferece flexibilidade para escolher a implementação do log no momento da implantação.
Problemas com falta de flexibilidade da configuração do JUL quando executados dentro de um servidor de aplicativos.
O SLF4J é certamente muito mais rápido, conforme detalhado acima, principalmente se você o associar ao Logback. Mesmo que este fosse apenas um teste aproximado, tenho motivos para acreditar que muito mais esforço foi feito na otimização no SLF4J + Logback do que no JUL.
Documentação. A documentação do SLF4J é simplesmente muito mais abrangente e precisa.
Flexibilidade de padrões. Ao fazer os testes, decidi imitar JUL o padrão padrão do Logback. Esse padrão inclui o nome do encadeamento. Acontece que o JUL não pode fazer isso imediatamente. Ok, não perdi até agora, mas acho que não deve faltar em uma estrutura de log. Período!
Muitos (ou muitos) projetos Java hoje usam o Maven, portanto, adicionar uma dependência não é algo tão importante, especialmente se essa dependência é bastante estável, ou seja, não muda constantemente sua API. Isso parece ser verdade para o SLF4J. Além disso, o frasco e os amigos do SLF4J são pequenos.
Então o estranho que aconteceu foi que eu fiquei bastante chateado com o JUL depois de trabalhar um pouco com o SLF4J. Ainda me arrependo de que tenha sido assim com JUL. JUL está longe de ser perfeito, mas meio que faz o trabalho. Apenas não muito bem o suficiente. O mesmo pode ser dito Properties
como exemplo, mas não pensamos em abstrair que as pessoas possam conectar sua própria biblioteca de configuração e o que você possui. Eu acho que a razão é que Properties
aparece logo acima da barra, enquanto o oposto é verdadeiro para JUL de hoje ... e no passado ele chegou a zero porque não existia.
java.lang.System.Logger
, que é uma interface que pode ser redirecionada para qualquer estrutura de log real que você desejar, desde que essa estrutura seja atualizada e forneça uma implementação dessa interface. Combinado com a modularização, você pode até implementar um aplicativo com um JRE em pacote que não contenha java.util.logging
, se preferir uma estrutura diferente.