Buscar este blog

sábado, 22 de agosto de 2015

Remote Desktop with SSH Tunnel

In this post I will show you how to access to a PC with Windows Remote Desktop. Well, this would be quite easy, so to make it more funny, the target Machine is not accessible from the Source Machine, but do there is a third machine which is accessible from both of them.

The topology of the problem is depicted in the following image:



We have these elements:
  • Target PC (windows). This is the machine I want to connect to.
  • Source PC (windows). This is the machine from where I want to connect to the Target PC.
  • Gateway (windows/Unix). This is the intermediary between Source and Target.
You have to keep in mind these constraints:
  • Source cannot reach Target
  • Source can reach Gateway
  • Target can reach Gateway
  • Gateway cannot reach Target
So, the idea is to use Gateway as a bridge between Source and Target. To make this happen we will use SSH Tunnels.

Previous preparation

In my concrete scenery, Gateway is a Windows Server PC, so I will have to configure SSH Server in it, but if you have a Unix Machine this step is already taken for granted.

You can use FreeSSHD as SSH Server. It is free (obvious) and very easy to configure. For example you can check this page.
The basic steps to configure it are:
  1. Install with default options
  2. Run as a service
  3. Configure SSH port, only if 22 were already in use
  4. Configure authentication to require password
  5. Add some user
Besides, I'll add another step. Enable Tunneling options



Once you are done, you can check that all works properly by connecting with Putty to this machine. You should see one active user connected:


Tunneling

There will be two SSH tunnels: 
  • From Target to Gateway
  • From Source to Gateway
The topology of the solution is depicted in the following image:



Target will open a tunnel in which all connections in Gateway to port 9999 will be redirected to itself in port 3389. Port 3389 is the default Windows Remote Desktop port.

Source will open a tunnel in which all connections in Source to port 13389 will be redirected to Gateway in port 9999.

Now you should have noted that the bridge is Gateway´s port 9999.

Tunnel from Target to Gateway

Working on Target PC, with putty you have to set the following configuration.
In Session category:
  • Host name and port of the SSH Server of Gateway
In Connection category:
  • Set the parametter "Seconds beween keepalives" to 180
In Connection > SSH > Tunnels category:
  • Check "Local ports accept connections from other hosts" option
  • Check "Remote ports do the seame (SSH-2 only)" option
  • Set source port 9999, destination localhost:3389 and check the option Remote. Then push "Add"

Tunnel from Source to Gateway

Working on Source PC, with putty you have to set the following configuration.
In Session category:
  • Host name and port of the SSH Server of Gateway
In Connection category:
  • Set the parametter "Seconds beween keepalives" to 180
In Connection > SSH > Tunnels category:
  • Check "Local ports accept connections from other hosts" option
  • Check "Remote ports do the seame (SSH-2 only)" option
  • Set source port 13389, destination 192.168.65.133:9999 and check the option Local. Then push "Add"

Testing the tunnel

From Source, you only need to try to connect with remote desktop to localhost:13389. The connection will travel throught the first tunnel to Gateway, and from Gateway to Target throught the other tunnel.



Thanks to Mr V (alias coffee man) for helping me with this.


Bonus

In Target, you can configure a script to use Putty Command Line options to connect automatically.
In my example I named my session as "Tunel a Gateway", but for command line is better to set a one-word name, for example gatewayTunnel.

putty.exe -load gatewayTunnel -l userName -pw password

domingo, 16 de agosto de 2015

wsdl2java - Use java.util.Date instead of XMLGregorianCalendar

If you have a web service definition, WSDL, and you want to generate the equivalent java code (either for the client or for the server side) one of the simplest way is by using Apache CXF wsdl2java Maven plugin. Check here: http://cxf.apache.org/docs/maven-cxf-codegen-plugin-wsdl-to-java.html

Problems with XMLGregorianCalendar

The conversion between wsdl dataTypes and java dataTypes is held automatically by this plugin, so in most cases, you don´t need to worry about anything more.
But one annoying thing most people notes is the conversión between date/dateTime and XMLGregorianCalendar.

For example, by having this detifintion:
<xs:complexType name="operacionesPagoFiltroDTO">
 <xs:sequence>
  <xs:element minOccurs="0" name="entidad" type="xs:long" />
  <xs:element minOccurs="0" name="fechaDesde" type="xs:dateTime" />
  <xs:element minOccurs="0" name="fechaHasta" type="xs:dateTime" />
  <xs:element minOccurs="0" name="nifUsuario" type="xs:string" />
  <xs:element minOccurs="0" name="seccion" type="xs:long" />
 </xs:sequence>
</xs:complexType>

You will get this POJO:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "operacionesPagoFiltroDTO", propOrder = {
    "entidad",
    "fechaDesde",
    "fechaHasta",
    "nifUsuario",
    "seccion"
})
public class OperacionesPagoFiltroDTO {

    protected Long entidad;
    @XmlSchemaType(name = "dateTime")
    protected XMLGregorianCalendar fechaDesde;
    @XmlSchemaType(name = "dateTime")
    protected XMLGregorianCalendar fechaHasta;
    protected String nifUsuario;
    protected Long seccion;

    (...)

}

So, you have to manually make the conversion in order to obtain java.util.Date.

Solution

The solution is to use a custom binding file and a dateAdapter. When CXF plugin is executed, it will check the binding configuration and will generate the java.util.Date automatically.

In pom.xml you have to specify the custom binding file:
<plugin>
 <groupId>org.apache.cxf</groupId>
 <artifactId>cxf-codegen-plugin</artifactId>
 <version>${cxf.version}</version>
 <executions>
  <execution>
   <id>generate-sources</id>
   <phase>generate-sources</phase>
   <configuration>
    <sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
    <wsdlOptions>        
     <wsdlOption>
      <wsdl>${basedir}/src/main/resources/wsdl/gestionEconomica/GestionEconomica.wsdl</wsdl>       
      <bindingFiles>
       <bindingFile>${basedir}/src/main/resources/wsdl/gestionEconomica/GestionEconomicaBinding.xml</bindingFile>
      </bindingFiles>
     </wsdlOption>
    </wsdlOptions>
   </configuration>
   <goals>
    <goal>wsdl2java</goal>
   </goals>
  </execution>
 </executions>
</plugin>

And this would be the binding file:
<jaxws:bindings wsdlLocation="GestionEconomica.xsd.wsdl"
 xmlns:jaxws="http://java.sun.com/xml/ns/jaxws" xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

 <jaxws:bindings node="wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='http://mytargetspace.es/']">
  
  <jxb:globalBindings xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <jxb:javaType 
       name="java.util.Date" 
       xmlType="xs:dateTime"
    parseMethod="org.apache.cxf.xjc.runtime.DataTypeAdapter.parseDateTime"
    printMethod="org.apache.cxf.xjc.runtime.DataTypeAdapter.printDateTime" />
  </jxb:globalBindings>
 </jaxws:bindings>
</jaxws:bindings>

By doing so, you will get this POJO:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "operacionesPagoFiltroDTO", propOrder = {
    "entidad",
    "fechaDesde",
    "fechaHasta",
    "nifUsuario",
    "seccion"
})
public class OperacionesPagoFiltroDTO {

    protected Long entidad;
    @XmlElement(type = String.class)
    @XmlJavaTypeAdapter(Adapter1 .class)
    @XmlSchemaType(name = "dateTime")
    protected Date fechaDesde;
    @XmlElement(type = String.class)
    @XmlJavaTypeAdapter(Adapter1 .class)
    @XmlSchemaType(name = "dateTime")
    protected Date fechaHasta;
    protected String nifUsuario;
    protected Long seccion;

    (...)
}

You can specify any adapter you want, but CXF also provides you with a basic implementation: org.apache.cxf.xjc.runtime.DataTypeAdapter. In order to use it, you need to add this extra dependency to your pom.xml:
<dependency>
 <groupId>org.apache.cxf.xjc-utils</groupId>
 <artifactId>cxf-xjc-runtime</artifactId>
 <version>${cxf-xjc-runtime.version}</version>
</dependency>


Note: You can check more about wsdl2java and eclipse in one of my previous posts: http://trabajosdesisifo.blogspot.com.es/2015/04/eclipse-maven-configure-wsdl2java.html

sábado, 8 de agosto de 2015

JBoss CLI - Domain monitoring

These are some commands to get insight of the health of your deployments.
The idea was to have one script and run it periodically to get some KPI. This script invokes jboss-cli by passing the command as a parameter.
You can ask why don´t have a jboss-cli script file and to pass it as a parameter (./jboss-cli --file ). By doing so, you can not print custom messages, because we were using JBoss EAP 6.2. In 6.4 you do have an echo command in CLI.

echo "--------------------------------------------------"
echo "$(date)"

echo "Session info"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=expired-sessions)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=rejected-sessions)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=max-active-sessions)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=session-max-alive-time)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=sessions-created)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=session-avg-alive-time)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=context-root)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=virtual-host)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=active-sessions)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=web:read-attribute(name=duplicated-session-ids)"


echo "EJB info"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=methods)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=timers)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=pool-available-count)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=peak-concurrent-invocations)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=invocations)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=pool-create-count)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=declared-roles)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=security-domain)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=pool-name)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=run-as-role)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=component-class-name)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=pool-max-size)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=wait-time)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=execution-time)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=pool-remove-count)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/deployment=<deployment>/subsystem=ejb3/stateless-session-bean=EjbInvoker:read-attribute(name=pool-current-size)"


echo "Memory info"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/core-service=platform-mbean/type=memory : read-attribute(name=heap-memory-usage)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/core-service=platform-mbean/type=memory : read-attribute(name=non-heap-memory-usage)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/core-service=platform-mbean/type=memory : read-attribute(name=verbose)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/core-service=platform-mbean/type=memory : read-attribute(name=object-name)"
./jboss-cli.sh -c --user=<user> --password=<pass> --command="/host=<host>/server=<server>/core-service=platform-mbean/type=memory : read-attribute(name= object-pending-finalization-count)"

lunes, 3 de agosto de 2015

JBoss Domain JConsole connection

JBoss EAP is shipped with its own JConsole, so you can get insight about your server via JMX.
In this post I´ll talk about domain mode in JBoss EAP 6.2.

JConsole is in ${JBoss.home}/bin/jconsole.sh

Topology

This is my topology:
  • Domain Controller in 192.168.56.101
  • Host Controller 1 in 192.168.56.102
  • Host Controller 2 in 192.168.56.103

I Have a server-group called jbossEAP6-sg1 with two servers, each one in its own HC:
  • server-full-ha-1 in HC 1, bound to 192.168.56.130
  • server-full-ha-2 in HC 2, bound to 192.168.56.140

This domain is in a CentOS remote PC and I Will connect from my Windows installation.

Configuration

I want to monitor Domain Controller in general, and my servers in particular. So I will make two different connections.

You have to configure jmx subsystem in domain.xml (this file is placed inside de Domain Controller). Once there you have to set the following config:
<subsystem xmlns="urn:jboss:domain:jmx:1.3">
 <expose-resolved-model/>
 <expose-expression-model/>
 <remoting-connector use-management-endpoint="false"/>
</subsystem>

Then, you need an application user in all the nodes (asuming you already have a management user in the domain). Simply use ./add-user.sh in DC, add a user, and copy the application-roles.properties and  application-users.properties files in the others HCs.

Connection to DC

You can start your connection with the followning parameters:
  • Remote Process: service:jmx:remoting-jmx://192.168.56.101:9999
  • Username/Password: domain user




Note that there is a CLI tab due to this is a DC.

Connection to server-full-ha-1 in HC 1

You can starts your connection with the followning parameters:
  • Remote Process: service:jmx:remoting-jmx://192.168.56.130:4447
  • Username/Password: application user
Note that in this case you need to set de 4447 port. I'm using a custom binding address for each server, so I don´t need to specify a port offset, otherwise you need to calculate the real port of your server.



Aditional info:

domingo, 2 de agosto de 2015

JBoss CLI - Datasources

I´ll show you the procedure to create a XA-DATASOURCE and a DATASOURECE in standalone and domain mode. First you have to create the driver, then create the datasource and finally enable it. As a bonus, you can check the connection.

The driver is created manually as a JBoss Module in directory ${JBossHome}/modules/com/microsoft/sqlserver/main. In this directory you need two files:
  1. module.xml
  2. the jdbc driver, sqljdbc4.0.jar
This is the content of module.xml:
<module xmlns="urn:jboss:module:1.1" name="com.microsoft.sqlserver"> 
  <resources> 
    <resource-root path="sqljdbc4.0.jar"/> 
  </resources> 
  <dependencies> 
    <module name="javax.api"/> 
    <module name="javax.transaction.api"/>
  </dependencies> 
</module>


My dabatabase configuration:
  • Type: SQL Server
  • Address: 192.168.65.133:1433
  • Database name: Pruebas
  • User/password: user-pruebas / user-pruebas

Standalone

/subsystem=datasources/jdbc-driver=sqlserver-xa:add(driver-module-name=com.microsoft.sqlserver, driver-name=sqlserver-xa, driver-xa-datasource-class-name=com.microsoft.sqlserver.jdbc.SQLServerXADataSource)

xa-data-source add --name=pruebasDSXA --jndi-name=java:jboss/datasources/pruebasDSXA --driver-name=sqlserver-xa --user-name="user-pruebas" --password="user-pruebas" --same-rm-override=false --background-validation=true --valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker --xa-datasource-properties=ServerName=192.168.65.133,PortNumber=1433,DatabaseName=PruebasDS,SelectMethod=cursor

xa-data-source enable --name=pruebasDSXA

/subsystem=datasources/xa-data-source=pruebasDSXA:test-connection-in-pool



/subsystem=datasources/jdbc-driver=sqlserver:add(driver-module-name=com.microsoft.sqlserver, driver-name=sqlserver, driver-datasource-class-name= com.microsoft.sqlserver.jdbc.SQLServerDriver)

data-source add --name=pruebasDS --jndi-name=java:jboss/datasources/pruebasDS --driver-name=sqlserver --connection-url="jdbc:sqlserver://192.168.65.133;DatabaseName=Pruebas" --user-name="user-pruebas" --password="user-pruebas" --background-validation=true

data-source enable --name=pruebasDS

/subsystem=datasources/data-source=pruebasDS:test-connection-in-pool

Domain

/profile=full-ha/subsystem=datasources/jdbc-driver=sqlserver-xa:add(driver-module-name=com.microsoft.sqlserver, driver-name=sqlserver-xa, driver-xa-datasource-class-name=com.microsoft.sqlserver.jdbc.SQLServerXADataSource)

xa-data-source --profile=full-ha add --name=pruebasDSXA --jndi-name=java:jboss/datasources/pruebasDSXA --driver-name=sqlserver-xa --user-name="user-pruebas" --password="user-pruebas" --same-rm-override=false --background-validation=true --valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker --xa-datasource-properties=ServerName=192.168.65.133,PortNumber=1433,DatabaseName=Pruebas,SelectMethod=cursor

xa-data-source --profile=full-ha enable --name=pruebasDSXA

/host=hc-001/server=server-full-ha-1/subsystem=datasources/xa-data-source=pruebasDSXA:test-connection-in-pool

/host=hc-002/server=server-full-ha-2/subsystem=datasources/xa-data-source=pruebasDSXA:test-connection-in-pool



/profile=full-ha/subsystem=datasources/jdbc-driver=sqlserver:add(driver-module-name=com.microsoft.sqlserver, driver-name=sqlserver, driver-datasource-class-name= com.microsoft.sqlserver.jdbc.SQLServerDriver)

data-source --profile=full-ha add --name=pruebasDS --jndi-name= java:jboss/datasources/pruebasDS --driver-name=sqlserver --connection-url="jdbc:sqlserver://192.168.65.133;DatabaseName=Pruebas" --user-name="user-pruebas"  --password="user-pruebas" --background-validation=true --valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker

data-source --profile=full-ha enable --name=pruebasDS

/host=hc-001/server=server-full-ha-1/subsystem=datasources/data-source=pruebasDS:test-connection-in-pool

/host=hc-002/server=server-full-ha-2/subsystem=datasources/data-source=pruebasDS:test-connection-in-pool

JBoss Domain set server binding address

The purpose of this blog is to show how to start each server inside a domain in its own IP address.

In JBoss domain you have:
  • Domain controller. One node is the DC that manage the Host Controllers.
  • Host controllers. There would be one HC for each physical node in the domain.
  • Server group: A server group is a logical group of servers that can span over multiple HC
  • Server. A server is the worker instance of a node in which applications are executed

Inside a HC you can have multiple servers, but in order they don't collide, you have to:
  • Set a port offset in each server. For example, server-1 starts in 0, server-2 starts in +150, etc.
  • Set a unique IP address for each one.

The cleanest way is to put each server in a separate IP and let the default port offset to 0. In order to do that, you have to set two System Properties in the server:
  • jboss.bind.address
  • jboss.bind.address.unsecure

I have two HCs and a server group called "jbossEAP6-sg1". This server group is composed by two servers, "server-full-ha1" and "server-full-ha-2" with a zero port offset:



Each server has its own bind address:, 192.168.56.130 and 192.168.56.140:




sábado, 1 de agosto de 2015

Externalize log4j configuration file in webapp

Note: This solution was updated using spring in http://trabajosdesisifo.blogspot.com.es/2015/11/externalize-log4j-configuration-file-in.html


All web application needs a Log. With the information written in a log file you can audit your application, check its health and trace problems.

Log4j is just one framework you can use to generate logs. You configure log behaviour by using a configuration file: log4j.xml or log4j.properties.
Usually this files are stored inside your app, i.e packed inside your war. This implies that you will have the same logging options in all of the environments: development, test, preproduction and production.

This could be a simple example of a log4j.xml file:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <!-- ************************************************ -->
    <!-- ****************** APPENDERS ******************* -->
    <!-- ************************************************ -->
    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value="[%d] (%F\:%L) %p %t %c %m%n" />
  </layout>
 </appender>

 <appender name="LOG" class="org.apache.log4j.DailyRollingFileAppender">
  <param name="File" value="/var/log/jboss/myApp.log" />
  <param name="DatePattern" value="'.'yyyy-MM-dd"/>
  <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%-5p][%d{ISO8601}][%-4L] %F>%m%n" />
        </layout>
 </appender>
    
    <!-- ************************************************ -->
    <!-- ****************** LOGGERS ********************* -->
    <!-- ************************************************ -->
    <logger name="es.sisifo.package1">
        <level value="DEBUG" />        
    </logger>
    
 <logger name="es.sisifo.package2">
        <level value="DEBUG" />     
    </logger>
 
    <logger name="org.springframework">
        <level value="DEBUG" />  
    </logger>

 <logger name="es.sisifo">
  <level value="INFO" />
 </logger>

 <root>
  <priority value="WARN" />
  <appender-ref ref="CONSOLE" />
  <appender-ref ref="LOG" />
 </root>
</log4j:configuration>

Here you have two packages of your app plus all spring classes in DEBUG level, the rest of your app in INFO and the rest of the classes in WARN.
A file called myApp.log is generated in /var/log/jboss directory.


You can use environment properties inside configuration files in order to externalize some parameters, for example, the path in which files are written. Also, you can use system properties passed to the JVM in boot time.
The previous log4j.xml config file would be this:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <!-- ************************************************ -->
    <!-- ****************** APPENDERS ******************* -->
    <!-- ************************************************ -->
    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value="[%d] (%F\:%L) %p %t %c %m%n" />
  </layout>
 </appender>

 <appender name="LOG" class="org.apache.log4j.DailyRollingFileAppender">
  <param name="File" value="${jboss.server.log.dir}/jboss/myApp.log" />
  <param name="DatePattern" value="'.'yyyy-MM-dd"/>
  <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%-5p][%d{ISO8601}][%-4L] %F>%m%n" />
        </layout>
 </appender>
    
    <!-- ************************************************ -->
    <!-- ****************** LOGGERS ********************* -->
    <!-- ************************************************ -->
    <logger name="es.sisifo.package1">
        <level value="DEBUG" />        
    </logger>
    
 <logger name="es.sisifo.package2">
        <level value="DEBUG" />     
    </logger>
 
    <logger name="org.springframework">
        <level value="DEBUG" />  
    </logger>

 <logger name="es.sisifo">
  <level value="INFO" />
 </logger>

 <root>
  <priority value="${system.myApp.defaultLogLevel}" />
  <appender-ref ref="CONSOLE" />
  <appender-ref ref="LOG" />
 </root>
</log4j:configuration>

Note that "jboss.server.log.dir" is a well-known JBoss property and "system.myApp.defaultLogLevel" would be a custom system property.

But imagine you have your app in production and some strange behaviour suddenly  appears in a apache-commons class. You would like to add a new logger tag and put this package in DEBUG in order to obtain more information. In this scenario, you would need to deploy a new version of your app with a modified log4j.xml.
The solution is to externalize the whole configuration file, and to force your app to read it in boot time. In this way you simple need to change the file and restart the app..


To externalize configuration file you have Log4jConfigListener. This class allow you to specify a external location of this file and load it when the app starts. In your web.xml you must set a context param called log4jConfigLocation that points to this file. This param could be expressed in multiples ways:
  • classpath:/... in order to indicate a custom path to the file inside your classpath.
  • file:... in order to indicate a external path to the file
  • ${} in order to indicate a system property whose value contains the path to the external file
For example:
<!-- Configuración de log -->
 <context-param>
  <param-name>log4jConfigLocation</param-name>
  <param-value>${system.myApp.log4jConfigLocation.web:classpath:/internal_log4j.xml}</param-value>
 </context-param>

 <context-param>
  <param-name>log4jExposeWebAppRoot</param-name>
  <param-value>false</param-value>
 </context-param>

 <listener>
  <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
 </listener>

Note that ${} accepts a default value expressed after the ":". In this example, the file would be allocated in the path expressed by "system.myApp.log4jConfigLocation.web" system property and, if this property does not exists, the default value would be "classpath:/internal_log4j.xml" (inside classpath).
In this example, de value of system.myApp.log4jConfigLocation.web would be "file:/home/jboss/myAppConfig/log4j-custom.xml" (note de "file:" prefix).

In JBoss you can either set system properties through web admin console (localhost:9990) in standalone or domain modes (in server-groups). In domain mode, the best way is to set the property in the server-group, keeping in mind that all servers share the same path.

When app starts you should see this log:
[Server:server-full-ha-1] 12:42:12,235 INFO  [org.springframework.beans.factory.config.PropertiesFactoryBean] (ServerService Thread Pool -- 107) Loading properties file from URL [file:/home/jboss/myAppConfig/log4j-custom.xml]