Jenkins and the deployment plugin

What I really love about Jenkins is his straightforward approach, when you do things with it. A short installation, a fast first configuration and a lot of good plugins, which can do a lot for you.

So I had also a look to the “Deploy to container Plugin”. It looked tempting, because I have an enterprise application which I want to deploy into an Application Server (GlassFish) after a success build.

First try first error. The plugin alert with

ERROR: Publisher hudson.plugins.deploy.DeployPublisher aborted due to exception
org.codehaus.cargo.util.CargoException: Deployment has failed: Action failed Deploying application to target server failed; File not found : /var/lib/jenkins/jobs/shop/workspace/shop-ear/target/shop-ear-1.0-SNAPSHOT.ear
	at org.codehaus.cargo.container.spi.deployer.AbstractJsr88Deployer.waitForProgressObject(AbstractJsr88Deployer.java:220)
	at org.codehaus.cargo.container.spi.deployer.AbstractJsr88Deployer.deploy(AbstractJsr88Deployer.java:76)
	at org.codehaus.cargo.container.spi.deployer.AbstractJsr88Deployer.redeploy(AbstractJsr88Deployer.java:142)
	at hudson.plugins.deploy.CargoContainerAdapter.deploy(CargoContainerAdapter.java:60)
	at hudson.plugins.deploy.CargoContainerAdapter$1.invoke(CargoContainerAdapter.java:86)
	at hudson.plugins.deploy.CargoContainerAdapter$1.invoke(CargoContainerAdapter.java:73)
	at hudson.FilePath.act(FilePath.java:785)
	at hudson.FilePath.act(FilePath.java:767)
	at hudson.plugins.deploy.CargoContainerAdapter.redeploy(CargoContainerAdapter.java:73)
	at hudson.plugins.deploy.DeployPublisher.perform(DeployPublisher.java:45)
	at hudson.tasks.BuildStepMonitor$3.perform(BuildStepMonitor.java:36)
	at hudson.model.AbstractBuild$AbstractRunner.perform(AbstractBuild.java:694)
	at hudson.model.AbstractBuild$AbstractRunner.performAllBuildSteps(AbstractBuild.java:669)
	at hudson.maven.MavenModuleSetBuild$RunnerImpl.post2(MavenModuleSetBuild.java:978)
	at hudson.model.AbstractBuild$AbstractRunner.post(AbstractBuild.java:616)
	at hudson.model.Run.run(Run.java:1429)
	at hudson.maven.MavenModuleSetBuild.run(MavenModuleSetBuild.java:470)
	at hudson.model.ResourceController.execute(ResourceController.java:88)
	at hudson.model.Executor.run(Executor.java:230)
Finished: FAILURE

After a while of search, try and error I found the problem. My Application Server has not enough permission to access the ear file which is created by the jenkins user. After I include the glassfish user to the same group as the jenkins user it works – yeah!

I was happy and proceed with my work.

But then when I did a new commit on the scm system and jenkins try to build the project new, it couldn’t deploy it again. The build was a success ([INFO] BUILD SUCCESS), but the the deployment plugin alert with:

Deploying /var/lib/jenkins/jobs/shop/workspace/shop-ear/target/shop-ear-1.0-SNAPSHOT.ear to container GlassFish 3.x Remote
ERROR: Publisher hudson.plugins.deploy.DeployPublisher aborted due to exception
org.codehaus.cargo.util.CargoException: Deployment has failed: Action failed Deploying application to target server failed; Error occurred during deployment: Application with name shop-ear-1.0-SNAPSHOT is already registered. Either specify that redeployment must be forced, or redeploy the application. Or if this is a new deployment, pick a different name. Please see server.log for more details.
	at org.codehaus.cargo.container.spi.deployer.AbstractJsr88Deployer.waitForProgressObject(AbstractJsr88Deployer.java:220)
	at org.codehaus.cargo.container.spi.deployer.AbstractJsr88Deployer.deploy(AbstractJsr88Deployer.java:76)
	at org.codehaus.cargo.container.spi.deployer.AbstractJsr88Deployer.redeploy(AbstractJsr88Deployer.java:142)
	at hudson.plugins.deploy.CargoContainerAdapter.deploy(CargoContainerAdapter.java:60)
	at hudson.plugins.deploy.CargoContainerAdapter$1.invoke(CargoContainerAdapter.java:86)
	at hudson.plugins.deploy.CargoContainerAdapter$1.invoke(CargoContainerAdapter.java:73)
	at hudson.FilePath.act(FilePath.java:785)
	at hudson.FilePath.act(FilePath.java:767)
	at hudson.plugins.deploy.CargoContainerAdapter.redeploy(CargoContainerAdapter.java:73)
	at hudson.plugins.deploy.DeployPublisher.perform(DeployPublisher.java:45)
	at hudson.tasks.BuildStepMonitor$3.perform(BuildStepMonitor.java:36)
	at hudson.model.AbstractBuild$AbstractRunner.perform(AbstractBuild.java:694)
	at hudson.model.AbstractBuild$AbstractRunner.performAllBuildSteps(AbstractBuild.java:669)
	at hudson.maven.MavenModuleSetBuild$RunnerImpl.post2(MavenModuleSetBuild.java:978)
	at hudson.model.AbstractBuild$AbstractRunner.post(AbstractBuild.java:616)
	at hudson.model.Run.run(Run.java:1429)
	at hudson.maven.MavenModuleSetBuild.run(MavenModuleSetBuild.java:470)
	at hudson.model.ResourceController.execute(ResourceController.java:88)
	at hudson.model.Executor.run(Executor.java:230)

Now the message was a bit more precisely. I have to force the deployment. In the console I can easily add the parameter “--force” to my asadmin deployment command, but the plugin doesn’t allow this option to me. So what can I do?

My not so well solution, add a “Hudson Post build task” which undeploy the current application from the server. In detail I add a condition which search the “Log text” for the following regex pattern “target\/(.+)\.ear“.
If match is found (means, that the ear file was created) it execute the following script

/PATH/TO/THE/ASADMIN/COMMAND/asadmin undeploy $(basename $(find -name "*.ear") .ear)

Explanation:
$(find -name "*.ear") – looks for a file (inclusiv path) with the extension ear
$basename – strip directory and suffix from filenames
So, the script execution looks like:

/PATH/TO/THE/ASADMIN/COMMAND/asadmin undeploy shop-ear-1.0-SNAPSHOT.ear

After that, I can now deploy the application each time, when a build success. So a success story? Not at all!
When I rethink my solution, why do I deploy my Application not at all with the post build task and skip the deployment plugin…

Lightweight web application logging on tomcat 6

Some Days ago, I had to refactor a web application to make it fit for production. The existing code was more like a proof-of-concept, not really an application. Especially the logging part was a mess.
In this post I will explain the improvements and also simplifications I made for the logging.

The requirements were quite simple: Log was happen and send an email if an exception occurs.
After study the existing Sourcecode I found in the web-application:

  • Used Log4J component
  • Own programmed Loghandler
  • JavaMail component

My simplification: kick them all out!
All requirements can be accomplished with functions from JDK and the usage of tomcat.

java.util.logging.Logger

Maybe early the java standard logger was limited and log4j was/is great, but since I’m coding in java, I could fulfill all logging requirements with the java standard logger API.

So I added to all class with a logging method a new logger field as follow:

private static transient Logger logger = Logger.getLogger(MyClass.class.getName());

transient because some classes are ‘’Serializable’’ and the logger don’t have to be persistent.

Next part, was to rewrite the logging statements

logger.log(Level.SEVERE, ex.toString());
// or
logger.log(Level.FINE, "MyMessage ({0}): {1}", new Object[]{id, myObject.getText()});

Until now, it was straightforward.

logging.properties – the configuration file

This can be a bit wired. Because of the major limitation of the jdk implementation (configure the logging per web-application), tomcat us JULI. With the JULI implementation, you can configure logging at a variety of levels

  • Globally for the JVM ($JAVA_HOME/jre/lib/logging.properties)
  • Per Tomcat Runtime instance ($TOMCAT_HOME/conf/logging.properties)
  • Per Web Application (WEB-INF/classes/ logging.properties)

At each level you can use a logging.properties file to configure logging granularity.

The default tomcat logging.properties file ($TOMCAT_HOME/conf/) specifies two types of handlers: ConsoleHandler for routing logging to stdout and a FileHandler for writing to a file.

When you configure the logging.properties file for tomcat instance or web application, you use a similar configuration as that of the JDK logging.properties file. You can also specify additional extensions to allow better flexibility in assigning loggers. Some Hints:

  • Declare a list of handlers using handlers
  • Set handlers for the root logger using the .handlers property
  • define a list of handlers using the loggerName.handlers property
  • Use the handlerName.level property to specify the level of logging you want for a given handler
  • System property replacement for property values expressed using the format ${systemPropertyName}

See the example config file on the end of this blog entry. This should clarify this above.

Email send

Next step what I had to re-implement (or configure) was sending emails, when an exception occur. Fortunately it exists a smtphandler for the java.util.logging library (smtphandler). It uses the JavaMail API to communicate with the SMTP server. Because of that, I had to copy the library smtphandler-0.7.jar, mailapi.jar, smtp.jar, activation.jar in the endorsed folder of tomcat (see java.endorsed.dirs property). You find them on the smtphandler page or with your favorite search engine.
After that, I had to set the smtphandler as a new log handler and configure it. That’s all.
See the example config file on the end of this blog entry. This should clarify this above.

Console output

The tomcat server will run as a service on a solaris system. No one will sit in front of a console and follow the stdout (or read the catalina.out file). For a better performance the console handler can be removed. From

handlers = 1catalina.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

To

.handlers = 1catalina.org.apache.juli.FileHandler

That stops anything getting written to catalina.out – file.

Log Level

You can set a various log level in the logging.properties files (ALL, SEVERE,… ,FINEST, OFF). Enabling logging at a given level also enables logging at all higher levels. In general, the lower level of logging you enable, the more data is written to the log files, so be careful when setting the logging level very low.

To configure the applications log level, set the level property of the specific handler (f.e. 10aailogin.org.apache.juli.FileHandler.level = FINEST. It is possible to configure the log level for each package or class. ch.enterpriselab.login.level = INFO. The log level of the handler has to be lower than that of the package or class.

Example Configuration Files

For better readability on blog page, I omit all standard comment line

$TOMCAT_HOME/conf/logging.properties

The most part was untouched from the default file.

# Created tdmarti, HSLU T&A - D3S (2011)
handlers =  1catalina.org.apache.juli.FileHandler, \
            2localhost.org.apache.juli.FileHandler, \
            3manager.org.apache.juli.FileHandler, \
            4host-manager.org.apache.juli.FileHandler, \
            java.util.logging.ConsoleHandler
 
.handlers = 1catalina.org.apache.juli.FileHandler
 
1catalina.org.apache.juli.FileHandler.level = SEVERE
1catalina.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.FileHandler.prefix = catalina.
 
2localhost.org.apache.juli.FileHandler.level = SEVERE
2localhost.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.FileHandler.prefix = localhost.
 
3manager.org.apache.juli.FileHandler.level = FINE
3manager.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
3manager.org.apache.juli.FileHandler.prefix = manager.
 
4host-manager.org.apache.juli.FileHandler.level = FINE
4host-manager.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
4host-manager.org.apache.juli.FileHandler.prefix = host-manager.
 
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.FileHandler
 
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.FileHandler
 
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.FileHandler

WEB-INF/classes/ logging.properties

# Created tdmarti, HSLU T&A - D3S (2011)
handlers =  10webappname.org.apache.juli.FileHandler, \
            smtphandler.SMTPHandler
.handlers = 10webappname.org.apache.juli.FileHandler, \
            smtphandler.SMTPHandler
 
## Define my webapplication logger
#Set the log level
10aailogin.org.apache.juli.FileHandler.level = FINEST
10aailogin.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
10aailogin.org.apache.juli.FileHandler.prefix = webappname.
10aailogin.org.apache.juli.FileHandler.limit = 5242880
10aailogin.org.apache.juli.FileHandler.count = 5
 
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/secure].level = FINEST
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/secure].handlers = 10webappname.org.apache.juli.FileHandler
 
#Set the log level per package
ch.enterpriselab.login.level = INFO
 
smtphandler.SMTPHandler.level=SEVERE
smtphandler.SMTPHandler.smtpHost=mta.mymta.com
smtphandler.SMTPHandler.to=admin@mymta.com
smtphandler.SMTPHandler.from=webapp@mymta.com
smtphandler.SMTPHandler.subject=[SMTPHandler] Application message
smtphandler.SMTPHandler.bufferSize=512
smtphandler.SMTPHandler.formatter=java.util.logging.SimpleFormatter