En analysant l’espace disque d’un serveur, je me suis aperçu que sur notre nexus (repository manager) nous avions un historique assez impressionnant de snapshot. En effet, nous avions tous les builds depuis près de 4 ans à raison d’un build par jour. Cela occupait pas moins de 700Go.
Archives de catégorie : java
Mais pourquoi je loggue autant?
La semaine dernière, j’ai trouvé un problème intéressant. Lors de la mise à jour d’une application, celle-ci a commencé à logguer en mode DEBUG de façon aléatoire. Sur certains serveurs tout marchait correctement, mais sur d’autres, non. En apparence, tout semblait normal, le fichier log4j.properties n’avait pas changé.
En regardant les logs, un message à attiré mon attention:
1 2 3 4 |
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:.../slf4j-log4j12-1.6.4.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:.../logback-classic.1.0.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. |
Chose intéressante, sur les serveurs à problème le message était plutôt de ce type:
1 2 3 4 |
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:.../logback-classic.1.0.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:.../slf4j-log4j12-1.6.4.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. |
SLF4j va prendre la première implémentation qu’il trouve. Dans notre cas ce sera logback-classic sur certains serveurs (cela va dépendre du chargement des classes dans le classloader).
Là où le problème arrive c’est que nous ne sommes pas supposés utiliser logback mais plutôt log4j. Nous n’avons pas de fichier de configuration de logback.
En suivant la documentation, voici ce qui se passe:
-
1234567891011Let us begin by discussing the initialization steps that logback follows to try to configure itself:Logback tries to find a file called logback.groovy in the classpath.If no such file is found, logback tries to find a file called logback-test.xml in the classpath.If no such file is found, it checks for the file logback.xml in the classpath..If no such file is found, and the executing JVM has the ServiceLoader (JDK 6 and above) the ServiceLoader will be used to resolve an implementation of com.qos.logback.classic.spi.Configurator. The first implementation found will be used. See ServiceLoader documentation for more details.If none of the above succeeds, logback configures itself automatically using the BasicConfigurator which will cause logging output to be directed to the console.
Nous n’avons aucun de ces fichiers, donc logback va écrire dans la console. Mais avec quel niveau de logs ? Et bien c’est écrit dans la doc :
1 |
by default the root logger is assigned theDEBUG level. |
C’était donc ca. Un manque de fichier de configuration et notre serveur passe en mode DEBUG.
Mais pourquoi avoir rajouter une telle librairie (logback-classic)?
Ce n’était pas volontaire, c’est une dépendance transitive qui est remontée en ajoutant une autre dépendance à un projet.
conclusion 1: toujours surveiller les dépendances remontées par les librairies
conclusion 2: toujours prêter attention aux messages dans les logs
PS: dans la version 1.6.6, slf4j va indiquer en plus dans les logs, la classe StaticLoggerBinder qu’il utilise. Cela permet de contrôler encore mieux ce qu’il se passe réellement sur le serveur.
Redis Cluster en java
En commençant à regarder le mode cluster de redis à travers une image docker déjà construire https://github.com/Grokzen/docker-redis-cluster, je me suis aperçu que ce n’est pas si transparent. En effaçant, si on essaye d’ajouter une valeur mais que la clé ne correspond au bon noeud, redis ne renvoi pas OK mais plutôt l’indication de ou insérer la donnée:
1 2 3 |
$ redis-cli -p 7000 127.0.0.1:7000> set toto foo (error) MOVED 9738 127.0.0.1:7001 |
Je me suis demandé comment gérer cela dans un programme Java. Est-ce le driver qui va faire le travail ou il faut le gérer à la main.
Si on utilise le driver standard de Jedis, voici ce qu’on obtiens:
1 2 3 4 5 |
Jedis jedis=new Jedis("localhost",7000); jedis.connect(); jedis.set("toto","bar"); String value=jedis.get("toto"); System.out.println("res - "+value); |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Exception in thread "main" redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 9738 127.0.0.1:7001 at redis.clients.jedis.Protocol.processError(Protocol.java:108) at redis.clients.jedis.Protocol.process(Protocol.java:142) at redis.clients.jedis.Protocol.read(Protocol.java:196) at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:288) at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:187) at redis.clients.jedis.Jedis.set(Jedis.java:66) at com.axioconsulting.redis.TestCluster.main(TestCluster.java:21) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) |
Heureusement, Jedis propose une classe pour résoudre ce problème (Je n’ai rien trouvé coté JRedis).
1 2 3 4 |
Set jedisClusterNodes=new HashSet(); jedisClusterNodes.add(new HostAndPort("localhost",7000)); jc.set("toto","bar"); String value=jc.get("toto"); System.out.println("res - "+value); |
1 |
res - bar |
Le driver est même capable de récupérer les autres noeuds. Dans l’exemple j’ai indiqué un seul noeud, mais avec le code suivant :
1 2 3 |
for (Map.Entry<string,jedispool> entry:jc.getClusterNodes().entrySet()){ System.out.println(entry.getKey()); } |
on voit bien que le driver a pu récupérer les autres noeuds:
1 2 3 4 5 6 7 |
localhost:7000 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7000 127.0.0.1:7001 |
les sources sont ici
Quelques liens utiles:
– commandes redis cluster
– À la découverte de Redis Cluster 3.0
Intellij consomme de la CPU
J’ai rencontré un problème récemment de consommation CPU élevée de la part d’Intellij sous Windows.
Après quelques recherches sur internet, il semble que cela soit un problème de mémoire pas assez important.
voici comment faire pour améliorer les choses:
Fermer Intellij
Dans le répertoire %USER_HOME%\.IntelliJIdea14\config\options éditer le fichier project.default.xml
et modifier la ligne
1 |
name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m -ea" /> |
pour augmenter la taille max de la mémoire
1 |
name="VM_PARAMETERS" value="-Xmx2048m -Xms256m -XX:MaxPermSize=250m -ea" /> |
Relancer Intellij. La consommation CPU devrait baissée drastiquement.
Partage de session avec spring-session
Un collègue m’a fait découvrir spring-session lors d’un échange sur la façon de stocker les sessions dans un tiers.
J’ai donc décidé de reprendre mon poc pour l’adapter à cette librairie.
La mise en place est assez simple. Il faut juste créer 2 classes.
1 pour la configuration (Config) et 1 pour que spring initialise mon application (Initializer). Dans une application existante la dernière classe n’est pas obligatoire. Il suffira de déclarer le bean config dans le fichier de context XML spring.
- configuration:
1 2 3 4 5 6 7 8 9 10 11 |
@Configuration @EnableRedisHttpSession public class Config { @Bean public JedisConnectionFactory connectionFactory() { JedisConnectionFactory factory = new JedisConnectionFactory(); factory.setHostName("db"); return factory; } } |
Il suffit de placer l’annotation EnableRedisHttpSession et de déclarer un bean pour la connectionFactory. Par defaut JedisConnectionFactory se connecte sur la base redis en localhost. Dans mon cas, j’ai surchargé pour aller sur le host db (container redis)
Il faut supprimer aussi le fichier context.xml qui n’est plus nécessaire.
Il faut passer en servlet 3.0 pour que l’initialisation fonctionne.
et voilà, c’est tout.
On peut tester le tout avec les commandes:
1 2 |
mvn clean install ./start_all.sh |
- Changements:
Par rapport au précédent article, le poc a subi quelques modifications pour fonctionner:
– passage à tomcat 8
– inclusion de spring 4
– inclusion de spring web
– inclusion de spring-data
– servlet 3.0
Les sources sont disponibles ici
Merci Raphaël pour l’info.
Erreur de commit SVN avec intellij
Si vous rencontrez l’erreur suivante lors d’un commit SVN :
1 |
wrong committed revision number: -1 |
Il s’agit apparemment de problème lié à localisation de SVN.
pour solutionner le problème, il faut ajouter au fichier idea.sh
1 2 |
<span class="kwd">export</span><span class="pln"> LANG</span><span class="pun">=</span><span class="pln">C</span> <span class="kwd">export</span><span class="pln"> LC_MESSAGES</span><span class="pun">=</span><span class="pln">C</span> |
Solution trouvée ici
Configurer Selenium par annotations
Pour faire suite à l’article précédent sur la configuration spring, voici le même type de configuration pour Selenium (outil de test d’intégration pilotant un navigateur).
On va pouvoir lancer les tests avec seulement 3 classes et 0 xml.
1 – on définit une classe de configuration qui va contenir l’url de base du site et le driver du navigateur à tester:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Configuration public class TestConfig { @Bean public URI getSiteBase() throws URISyntaxException { return new URI("http://localhost:10001/demo-spring-1.0-SNAPSHOT"); } @Bean(destroyMethod = "quit") public FirefoxDriver getDrv() { return new FirefoxDriver(); } } |
2 – on définit ensuite une classe abstraite qui va référencer la configuration et va contenir un petit « tips » pour ne pas avoir plusieurs processus de firefox en même temps, lorsque qu’on lance plusieurs tests
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={ TestConfig.class }) public abstract class AbstractTestIT { @Autowired protected URI siteBase; @Autowired protected WebDriver drv; { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { drv.close(); } }); } } |
3- et enfin la classe de test qui vérifie que le code du controller est correcte:
1 2 3 4 5 6 7 8 9 |
public class HelloControllerTestIT extends AbstractTestIT { @Test public void testHello() { drv.get(siteBase.toString()+"/hello"); assertTrue(drv.getPageSource().contains("Welcome anonymous")); } } |
4- pour que tout s’enchaîne correctement, il faut modifier le pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.12</version> <executions> <execution> <id>default</id> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <version>1.2.0</version> <configuration> <type>standalone</type> <configuration> <properties> <cargo.servlet.port>10001</cargo.servlet.port> </properties> </configuration> </configuration> <executions> <execution> <id>start</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> </plugins> </build> |
et ajouter les dépendances suivantes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-firefox-driver</artifactId> <version>2.43.1</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> |
Attention: La version de selenium est fortement lié à la version de firefox. Si l’exemple ne marche pas, il faut vérifier qu’une nouvelle version n’est pas disponible.
Le lancement de la commande
1 |
mvn verify |
devrait donner un résultat du type:
1 2 3 |
Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 |
Les sources sont ici: github
références:
Configurer spring sans fichier XML
Pour changer un peu de docker, voici un petit article pour initier un projet spring sans fichier de configuration XML.
Je vais créer une petite application avec un Controller Spring MVC.
Tout d’abord, on ajoute les dépendances. Servlet 3.1, spring-context , spring mvc suffisent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<properties> <spring.version>4.1.0.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> </dependencies> |
On va ensuite créer une classe qui va permettre de gérer l’initialisation de la configuration spring. Pour cela, j’utilise une nouveauté de servlet 3.0 qui est l’interface WebApplicationInitializer:
1 2 3 |
public interface WebApplicationInitializer { void onStartup(javax.servlet.ServletContext servletContext) throws javax.servlet.ServletException; } |
En implémentant une classe avec la méthode onStartup, on peut faire l’initialisation de son application. C’est plus simple que de faire une « fausse » servlet avec un paramètre loadOnStartup dans le fichier web.xml
Voici le code de la classe que j’utilise:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class MyWebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { WebApplicationContext context = getContext(); servletContext.addListener(new ContextLoaderListener(context)); ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context)); dispatcher.setLoadOnStartup(1); dispatcher.addMapping("/*"); } private AnnotationConfigWebApplicationContext getContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setConfigLocation("com.axioconsulting.demo.spring.config"); return context; } } |
La méthode getContext va permettre de gérer la configuration par annotation en commençant par le package com.axioconsulting.demo.spring.config.
Dans ce package, j’ai défini 2 classes.
1 2 3 4 |
@Configuration @ComponentScan("com.axioconsulting.demo.spring.controller") public class AppConfig { } |
L’annotation @Configuration permet à Spring de savoir que cette classe est à prendre en compte pour la configuration. Dans notre cas, on précise juste qu’il faut scanner le package com.axioconsulting.demo.spring.controller (c’est là que va se trouver le controller)
1 2 3 4 |
@Configuration @EnableWebMvc public class WebMvcConfig { } |
Cette classe permet d’importer la configuration spring MVC.
NB: On aurait pu fusionner les 2 classes.
Il ne reste plus qu’à mettre en place le controller.
1 2 3 4 5 6 7 8 9 10 11 |
@Controller public class HelloController { @RequestMapping("/hello") @ResponseBody public String hello(@RequestParam(required = false) String name){ if (StringUtils.isEmpty(name)){ return "Welcome anonymous"; } return "Hello "+name; } } |
Il s’agit d’un controller basic qui va répondre un message sur /hello ou sur /hello?name=monNom
Et voila, on a réussi à initier une appli spring MVC en manipulant seulement des annotations ce qui me parait plus simple.
A voir sur le long terme et sur un projet un peu plus gros si les annotations sont suffisantes.
Les sources sont ici: https://github.com/BenoitCharret/demoSpring