views:

224

answers:

2

Our project uses Log4J, configured via log4j.properties file. We have multiple production servers, which log to different log files, so that the logs can be differentiated. So log4j.properties for node 1 looks like this:

...
log4j.appender.Application.File=D:/logs/application_1.log
...
log4j.appender.tx_info.File=D:/logs/tx_info_1.log
...

while the log4j.properties for node 2 looks like

...
log4j.appender.Application.File=D:/logs/application_2.log
...
log4j.appender.tx_info.File=D:/logs/tx_info_2.log
...

We already use Maven profiles for generating our server configuration. Up to now it contained several distinct log4j.properties files which differed only in the log file names as shown above. I would like to generate these files with Maven using a resource template file like this:

...
log4j.appender.Application.File=${log.location}/application${log.file.postfix}.log
...
log4j.appender.tx_info.File=${log.location}/tx_info${log.file.postfix}.log
...

It is easy to run Maven multiple times with different ${log.file.postfix} values to generate a single different log property file each time. However, what I would like is to generate a distinct property file (with the name/path different) for each server in one build. I am fairly sure this can be done, e.g. via the antrun plugin, but I am not familiar with that. What is the simplest way to achieve this?

+1  A: 

Here are some approaches that you can try:

  1. use the antrun plugin, and the copy task to make duplicates of your resource file in the generate-resources phase. An example of using the antrun plugin is given in the answer to this SO question on copying with maven. You could even use ant's property expansion to expand each ${log.file.postfix} to a distinct value, either the literal values, 1,2,3 etc. or unique placeholders, ${log.file.postfix1}, ${log.file.postfix2} which are finally replaced when maven does the resource filtering.

  2. Rather than using antrun, use your version control system to set up multiple copies of the same file. You can then run multiple instances of the resources:copy-resources goal, each with different property values configured, and a different target file name.

mdma
Thanks for the ideas. Using resources:copy-resources, wouldn't it be possible to copy _the same file_ multiple times to different targets, with different properties? However, I couldn't figure out how to configure properties for the different executions - could you point me to a concrete example?
Péter Török
You can invoke the copy-resources goal multiple times in your pom, each time specifying the same source different target. I don't know if copy-resources allows files to be renamed, but it does allow setting the target directory. The sun is shining - I have to go work in the garden, but I'll try to put something concrete together later.
mdma
+2  A: 

(...) I am fairly sure this can be done, e.g. via the antrun plugin, but I am not familiar with that. What is the simplest way to achieve this?

You could indeed use resources:copy-resources and several <execution> in your POM (note that resources:copy-resources doesn't allow to change the name of the target file though).

Let's assume you have the following structure:

$ tree .
.
├── pom.xml
└── src
    ├── main
    │   ├── filters
    │   │   ├── filter-node1.properties
    │   │   └── filter-node2.properties
    │   ├── java
    │   └── resources
    │       ├── log4j.properties
    │       └── another.xml
    └── test
        └── java

Where log4j.properties is using place holders and the filter-nodeN.properties files contain the values. For example:

# filter-node1.properties

log.location=D:/logs
log.file.postfix=_1

Then, in your pom.xml, configure the resources plugin and define one <execution> per node to call copy-resources with a specific output directory and a specific filter to use:

<project>
  ...
  <build>
    <resources>
      <!-- this is for "normal" resources processing -->
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering><!-- you might still want to filter them -->
        <excludes>
          <!-- we exclude the file from "normal" resource processing -->
          <exclude>**/log4j.properties</exclude>
        </excludes>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.4.3</version>
        <executions>
          <execution>
            <id>copy-resources-node1</id>
            <phase>process-resources</phase>
            <goals>
              <goal>copy-resources</goal>
            </goals>
            <configuration>
              <outputDirectory>${basedir}/target/node1</outputDirectory>
              <resources>
                <resource>
                  <directory>src/main/resources</directory>
                  <filtering>true</filtering>
                  <includes>
                    <include>**/log4j.properties</include>
                  </includes>
                </resource>
              </resources>
              <filters>
                <filter>src/main/filters/filter-node1.properties</filter>
              </filters>
            </configuration>
          </execution>
          <execution>
            <id>copy-resources-node2</id>
            <phase>process-resources</phase>
            <goals>
              <goal>copy-resources</goal>
            </goals>
            <configuration>
              <outputDirectory>${basedir}/target/node2</outputDirectory>
              <resources>
                <resource>
                  <directory>src/main/resources</directory>
                  <filtering>true</filtering>
                  <includes>
                    <include>**/log4j.properties</include>
                  </includes>
                </resource>
              </resources>
              <filters>
                <filter>src/main/filters/filter-node2.properties</filter>
              </filters>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Running mvn process-resources would produce the following result:

$ tree .
.
├── pom.xml
├── src
│   ├── main
│   │   ├── filters
│   │   │   ├── filter-node1.properties
│   │   │   └── filter-node2.properties
│   │   ├── java
│   │   └── resources
│   │       ├── log4j.properties
│   │       └── another.xml
│   └── test
│       └── java
└── target
    ├── classes
    │   └── another.xml
    ├── node1
    │   └── log4j.properties
    └── node2
        └── log4j.properties

With the appropriate values in each log4j.properties.

$ cat target/node1/log4j.properties 
log4j.appender.Application.File=D:/logs/application_1.log
log4j.appender.tx_info.File=D:/logs/tx_info_1.log

This kinda works, but is verbose and this might be a problem if you have a decent amount of nodes.


I tried to write something more concise and maintainable using the Maven AntRun Plugin but I couldn't get the for task from ant-contrib to work under Maven (for an unknown reason, the for task isn't recognized) and I gave up.

Here is an alternative using the Maven AntRun Plugin. Nothing complicated, no loop, I'm just copying the source file to another location, changing its name on the fly and filtering the content:

  <plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.3</version>
    <executions>
      <execution>
        <id>copy-resources-all-nodes</id>
        <phase>process-resources</phase>
        <configuration>
          <tasks>
            <copy file="src/main/resources/log4j.properties" toFile="target/antrun/log4j-node1.properties">
              <filterset>
                <filter token="log.location" value="D:/logs"/>
                <filter token="log.file.postfix" value="_1"/>
              </filterset>
            </copy>
            <copy file="src/main/resources/log4j.properties" toFile="target/antrun/log4j-node2.properties">
              <filterset>
                <filter token="log.location" value="D:/logs"/>
                <filter token="log.file.postfix" value="_2"/>
              </filterset>
            </copy>
          </tasks>
        </configuration>
        <goals>
          <goal>run</goal>
        </goals>
      </execution>
    </executions>
  </plugin>

Note that Ant uses @ by default as delimiters for token (couldn't get it to use maven style delimiters) so the log4j.properties became:

[email protected]@/[email protected]@.log
[email protected]@/[email protected]@.log

But, since these values seem to be node specific, did you consider using system properties instead (that you could place in the startup scripts)? This is something I've already done (with a log4j.xml), it works well and it would highly simplify things.

Pascal Thivent
Thanks for the full example. It is indeed very verbose - luckily we have only 3 nodes so it wouldn't get _too_ out of hand... btw do you know any way to define just a property per execution profile instead of a full property file? We have only a single property that varies across the nodes, so a full property file sounds a bit like overkill.
Péter Török
Using system properties instead of the multiple execution profiles sounds definitely more elegant. I guess you leaned on JBoss to resolve the properties in log4j.xml, did you? That may not work for us right now, as our log4j.properties is in an external directory, not loaded via JBoss. Nevertheless we plan to migrate our configuration under JBoss, so this may become the perfect solution once we have migrated our Log4j config into jboss-log4j.xml...
Péter Török
@Péter: Sadly, no, I don't know how to use `copy-resources` without using a file for filtering. Based on the mentioned details, I've added an antrun implementation though. There is still some duplication (which is why I wanted to use a `for` loop initially) but it's still more concise. Personally, I prefer the antrun approach for this use case. Regarding the system properties, yes this is something inspired by JBoss (that I used successfully on a big weblogic cluster). I thought it was worth mentioning this option.
Pascal Thivent