SLF4J - Logging to different files based on a tag

Abhijith.M :

I have a requirement wherein I need to create log files such that all the logs based on a tag (Preferably text) need to be logged to corresponding log files.

For example,

I have logs regarding apples, oranges, and Mangoes.

logger.info("Apples: They are red in color");
logger.info("Oranges: They are orange in color");
logger.info("Mangoes: They are yellowish in color");

As per my requirement, the first log should be logged to Apples.log, the second to Oranges.log and the third to Mangoes.log

The log files should be created dynamically.

Below shown is my logback.xml file

    <statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>
                %d %-5p - %marker%m%n
            </Pattern>
        </encoder>
    </appender>
  <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator>
      <key>fruitName</key>
      <defaultValue>Common_logs</defaultValue>
    </discriminator>
    <sift>
      <appender name="FILE-${instanceName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
            <fileNamePattern>./Logs/${fruitName}/${instanceName}.log</fileNamePattern>
            <maxHistory>50</maxHistory>
            <cleanHistoryOnStart>false</cleanHistoryOnStart>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d %-5p - %m%n</pattern>
        </encoder>
        </appender>
    </sift>
  </appender>

  <logger name="AssetInstanceService" level="info" additivity="false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
    <appender-ref ref="SIFT" />
  </logger>

  <root level="info">
    <appender-ref ref="SIFT" />
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
  </root>
</configuration>

Given below is my LogManager.java file

package Data;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class LogManager {

    static String configFile = "./logback.xml";

    private static Logger _logger = LoggerFactory.getLogger(LogManager.class);
    private static boolean _isInitialized = false;

    private LogManager() {

    }

    public static Logger Instance() {
        if (_logger == null) {
            _logger = LoggerFactory.getLogger(LogManager.class);
        }
        return _logger;
    }

    public static Logger Instance(String instanceName) {
        if (!_isInitialized) {
            if (!CommonMethods.CheckFileExist(configFile)) {
                configFile = "../logback.xml";
                if (!CommonMethods.CheckFileExist(configFile)) {
                    return null;
                }
            }
            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
            JoranConfigurator configurator = new JoranConfigurator();
            lc.reset();
            configurator.setContext(lc);
            try {
                configurator.doConfigure(configFile);
                MDC.put("fruitName", instanceName);
            } catch (JoranException e) {
                System.out.println(e);
            }
            _isInitialized = true;
        }
        if (_logger == null) {
            _logger = LoggerFactory.getLogger(LogManager.class);
        }
        return _logger;
    }
}

Here my logs are created when the jar file is run as I initialize the logger in my main method.

But my need is to have different log files created with different names based on the tag or id in the log .

rgoers :

SLF4J and Log4j-API both provide Markers to do what you want. In SLF4J you would create your markers with:

Marker apples = MarkerFactory.getMarker("Apples");
Marker oranges = MarkerFactory.getMarker("Oranges");
Marker mangos = MarkerFactory.getMarker("Mangos");

In addition, the markers can have a parent so you could do:

Marker fruit = MarkerFactory.getMarker("Fruit");
Marker apples = MarkerFactory.getMarker("Apples");
apples.add(fruit);
Marker oranges = MarkerFactory.getMarker("Oranges");
apples.add(fruit);
Marker mangos = MarkerFactory.getMarker("Mangos");
apples.add(fruit);

In the configuration you can either check for the specific Marker or if you want to check for all Markers that are a Fruit you would check for that Marker.

You then use the markers in your application as:

logger.info(apples, "They are red in color");
logger.info(oranges, "They are orange in color");
logger.info(mangoes, "They are yellowish in color");

Finally, in your configuration you can use a turbo filter to do:

  <turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
    <Marker>Apples</Marker>
    <OnMatch>NEUTRAL</OnMatch>
    <OnMismatch>DENY</OnMismatch>
  </turboFilter>

to filter at a global level or you can use one of the evaluator filters to filter on the Appender. You could also use the SiftingAppender to create Appenders dynamically if you write a custom Discriminator.

Things are a bit easier with the Log4j API. To create a Marker without a parent:

Marker apples = MarkerManager.getMarker("Apples");
Marker oranges = MarkerManager.getMarker("Oranges");
Marker mangos = MarkerManager.getMarker("Mangos");

or with a parent:

Marker fruit = MarkerManager.getMarker("Fruit");
Marker apples = MarkerManager.getMarker("Apples").setParents(fruit);
Marker oranges = MarkerManager.getMarker("Oranges").setParents(fruit);
Marker mangos = MarkerManager.getMarker("Mangos").setParents(fruit);

The code to use them in Log4j is exactly the same:

logger.info(apples, "They are red in color");
logger.info(oranges, "They are orange in color");
logger.info(mangoes, "They are yellowish in color");

The big difference is in the configuration. Log4j only has one kind of Filter and it can be used at the global level (like a turbo filter) or on a Logger, an Appender reference, or on an Appender. In your case you would want to use the MarkerFilter:

<MarkerFilter marker="Apples" onMatch="NEUTRAL" onMismatch="DENY"/>

and add that either to each Appender reference or each Appender.

Similar to the SiftingAppender Log4j also provides a RoutingAppender. It uses Lookups to determine how to perform the routing. Out of the box Log4j doesn't provide one to retrieve data from the log event but it would be simple to write one or add one to Log4j, or you could use a script to retrieve the marker from the event.

You should be aware that there is some overhead to using the MarkerFilters, although not much. Here is a benchmark from the log4j-perf module when running 4 threads on my MacBook Pro. Even in the worst case where Logback is checking for a parent marker against an event containing a child marker each comparison still only takes 17 nanoseconds on average.

Benchmark                                  Mode  Cnt   Score   Error  Units
MarkerFilterBenchmark.baseline             avgt   10   2.412 ± 0.088  ns/op
MarkerFilterBenchmark.log4jParentMarker    avgt   10   8.337 ± 0.186  ns/op
MarkerFilterBenchmark.log4jSimpleMarker    avgt   10   8.043 ± 0.145  ns/op
MarkerFilterBenchmark.log4jTooFine         avgt   10   2.825 ± 0.281  ns/op
MarkerFilterBenchmark.logbackParentMarker  avgt   10  17.865 ± 0.533  ns/op
MarkerFilterBenchmark.logbackSimpleMarker  avgt   10  10.471 ± 0.089  ns/op
MarkerFilterBenchmark.logbackTooFine       avgt   10   4.255 ± 0.014  ns/op

A final thought. Ceki Gulcu invented the concept of Markers when he created SLF4J and he deserves credit for that as it was a fantastic idea.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=297643&siteId=1