How to correctly use RoutingAppender in log4j2 programmatically? - java

I am creating a log4j2 logger programmatically and adding appenders to it.
I want the logger to write the messages in two different locations/files according to some parameter/criteria.
For this I found that RoutingAppender can be good option to route the messages in different locations.
And for the criteria with the help of which I want to route messages, I am using Marker as used in How to create multiple log file programatically in log4j2?
But I am enable to manage it properly.Below is my code snippet for reference :
public class Log4j2TestApplication {
private static final Marker MARKER1 = MarkerManager.getMarker("MARKER1");
private static final Marker MARKER2 = MarkerManager.getMarker("MARKER2");
public static void main(String[] args) {
String loggerName = "demoLogger";
final ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
final LoggerComponentBuilder loggerComp = builder.newLogger(loggerName, Level.ALL).addAttribute("additivity",
false);
builder.add(loggerComp);
Configuration configuration = builder.build();
LoggerContext ctx = Configurator.initialize(builder.build());
ctx.start(configuration);
ctx.updateLoggers(configuration);
Logger logger = ctx.getLogger(loggerName);
Appender csvAppender = createCsvAppender(configuration);
Appender textAppender = createTextAppender(configuration);
csvAppender.start();
textAppender.start();
logger.addAppender(csvAppender);
logger.addAppender(textAppender);
Appender routingAppender = createRoutingAppender(configuration, textAppender, csvAppender);
routingAppender.start();
logger.addAppender(routingAppender);
logger.error(MARKER1, "the text message", "testing parameter");
logger.error(MARKER2, "the csv message", "testing parameter");
csvAppender.stop();
textAppender.stop();
routingAppender.stop();
}
private static Appender createCsvAppender(final Configuration config) {
return RollingFileAppender.newBuilder().setConfiguration(config).setName("csvAppender")
.withFileName("TestFile.csv").withFilePattern("TestFile.csv")
.withPolicy(SizeBasedTriggeringPolicy.createPolicy("100M"))
.withStrategy(DefaultRolloverStrategy.newBuilder().withConfig(config).build()).withImmediateFlush(true)
.setFilter(ThresholdFilter.createFilter(Level.ALL, Result.ACCEPT, Result.DENY)).setLayout(getCsvLayout(config))
.build();
}
private static Layout<String> getCsvLayout(final Configuration config) {
return new CsvParameterLayout(config, StandardCharsets.UTF_8, CSVFormat.DEFAULT.withDelimiter(','),
"column1;coloumn2\n", null);
}
private static Appender createTextAppender(final Configuration config) {
return RollingFileAppender.newBuilder().setConfiguration(config).setName("txtAppender")
.withFileName("TestFile.txt").withFilePattern("TestFile.txt")
.withPolicy(SizeBasedTriggeringPolicy.createPolicy("100M"))
.withStrategy(DefaultRolloverStrategy.newBuilder().withConfig(config).build()).withImmediateFlush(true)
.setFilter(ThresholdFilter.createFilter(Level.ALL, Result.ACCEPT, Result.DENY)).setLayout(getTextLayout(config, "header\n"))
.build();
}
private static Layout<String> getTextLayout(final Configuration config, final String header) {
return PatternLayout.newBuilder().withConfiguration(config).withCharset(StandardCharsets.UTF_8)
.withPattern("[%d][%-5.-5p]").withHeader(header).build();
}
private static Appender createRoutingAppender(final Configuration config, Appender appender1, Appender appender2) {
Route[] routeArray = new Route[2];
routeArray[0] = Route.createRoute(appender1.getName(), "MARKER1", null);
routeArray[1] = Route.createRoute(appender2.getName(), "MARKER2", null);
Routes routes = Routes.newBuilder().withRoutes(routeArray).withPattern("marker").build();
Appender routingAppender = RoutingAppender.newBuilder().setName("routingAppender").setConfiguration(config)
.withRoutes(routes).build();
return routingAppender;
}
}
I have referenced below links also but I could not find the accurate way of RoutingAppender programmatically.
Is there a way to Route logs based on Marker with the RoutingAppender in Log4j2
Wildcard pattern for RoutingAppender of Log4j2
How to create multiple log file programatically in log4j2?

Related

Log4J PropertyConfigurator migration to Log4J2

I have the following legacy class which used PropertyConfigurator under Log4J 1.x
There's no equivalent under Log4J 2.x but I am trying to preserve the functionality at method level.
The existing answers on Stack Overflow are for locating the configuration file, but the legacy code I have in loadConfiguration is setting properties inside the configuration file about where the actual log file will be output.
I wish to know what the equivalent would be under Log4J2. It's not clear to me how the
loadConfiguration method will change due to not having a PropertyConfigurator under log4j 2.x.
Thanks in advance.
Existing code:
public class BasicLogConfigurator {
public static final String LOG_LEVEL_KEY = "log.level.override";
private static final Level DEFAULT_LOG_LEVEL = Level.ERROR;
private static final String LOG4J_PROPS_LOCATION = "/log4j.properties";
private static final String LOG_FILE_PATTERN = "CustomLog_%s.log";
private static final Date TIMESTAMP = new Date();
private static final String METADATA_FOLDER = ".metadata";
public static void loadDefaultConfiguration() {
// 1. Grab the Log file location
File logLocation = getLogFileLocationInMetadataDirectory();
// 2. Grab default Log level
Level logLevel = DEFAULT_LOG_LEVEL;
loadConfiguration(logLocation, logLevel);
}
public static void loadConfiguration(File logLocation, Level logLevel) {
Properties properties = getConfigurationProperties();
// 1. Setup the Log file location
properties.setProperty("log4j.appender.FILE.file", logLocation.getAbsolutePath());
// 2. Setup the default Log level
properties.setProperty("log4j.rootLogger", String.format("%s, CONSOLE, FILE", sanitizeLogLevel(logLevel)));
LogManager.resetConfiguration();
PropertyConfigurator.configure(properties);
}
public static File getLogFileLocationInMetadataDirectory() {
String workspaceDirectory = getWorkspaceDirectory();
File metadataDirectory = new File(workspaceDirectory, METADATA_FOLDER);
File logLocation = getLogFileLocation(metadataDirectory);
return logLocation;
}
public static File getLogFileLocation(File directory) {
return new File(directory, String.format(LOG_FILE_PATTERN, formatTimeStamp(TIMESTAMP)));
}
private BasicLogConfigurator() {
// prevent instantiation
}
private static Level sanitizeLogLevel(Level selectedLevel) {
String overridingLevel = System.getProperty(LOG_LEVEL_KEY);
if (overridingLevel != null) {
return Level.toLevel(overridingLevel, Level.DEBUG);
}
if (selectedLevel != null) {
return selectedLevel;
}
return DEFAULT_LOG_LEVEL;
}
public static String getWorkspaceDirectory() {
return ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString();
}
private static String formatTimeStamp(Date date) {
return new SimpleDateFormat("yyyyMMdd").format(date).toString();
}
public static Properties getConfigurationProperties() {
try {
Properties properties = new Properties();
properties.load(BasicLogConfigurator.class.getResourceAsStream(LOG4J_PROPS_LOCATION));
return properties;
} catch (IOException e) {
throw new RuntimeException(
String.format("Error while loading {%s}", LOG4J_PROPS_LOCATION), e);
}
}
}
So how is this done for Log4J2 given the inputs of the loadConfiguration method? And does getConfigurationProperties need to change to work with Log4J2?

How to read property from application.properties when using ConfigurationFactory (Log4j's Programmatic Configuration)?

I am using programmatic configuration for log4j as shown in the following link: http://logging.apache.org/log4j/2.x/manual/customconfig.html.
#Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY)
#Order(50)
public class CustomConfigurationFactory extends ConfigurationFactory {
static Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder) {
builder.setConfigurationName(name);
builder.setStatusLevel(Level.ERROR);
builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL).
addAttribute("level", Level.DEBUG));
AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").
addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
appenderBuilder.add(builder.newLayout("PatternLayout").
addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY,
Filter.Result.NEUTRAL).addAttribute("marker", "FLOW"));
builder.add(appenderBuilder);
builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG).
add(builder.newAppenderRef("Stdout")).
addAttribute("additivity", false));
builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout")));
return builder.build();
}
#Override
public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
return getConfiguration(loggerContext, source.toString(), null);
}
#Override
public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) {
ConfigurationBuilder<BuiltConfiguration> builder = newConfigurationBuilder();
return createConfiguration(name, builder);
}
#Override
protected String[] getSupportedTypes() {
return new String[] {"*"};
}
}
I wish to choose the appender based on application.properties
I have tried all answers shared on How to access a value defined in the application.properties file in Spring Boot. But none of them work, giving errors like Spring Boot - Environment #Autowired throws NullPointerException. The solutions mentioned in the above link also fail to work.

Why does the Log4j2 Appenders not add the first log in the next line to the header?

I am creating a log4j2 logger programmatically and adding appenders to it.
But one thing I notice is that at the time of first log message to be written, the log message gets appended to the header.
For example
header[2020-01-21 21:16:07,176][ERROR] - the error message
[2020-01-21 21:16:07,176][ERROR] - text message
What was expected was :
header
[2020-01-21 21:16:07,176][ERROR] - the error message
[2020-01-21 21:16:07,176][ERROR] - text message
Below is the code snippet I am trying to achieve this:
#SpringBootApplication
public class Log4j2TestApplication {
public static void main(String[] args) {
SpringApplication.run(Log4j2TestApplication.class, args);
String loggerName = "testLogger";
final ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
final LoggerComponentBuilder loggerComp = builder.newLogger(loggerName, Level.ALL).addAttribute("additivity",
false);
builder.add(loggerComp);
builder.setConfigurationSource(null);
Configuration configuration = builder.build();
LoggerContext ctx = Configurator.initialize(builder.build());
ctx.start(configuration);
ctx.updateLoggers(configuration);
Logger logger = ctx.getLogger(loggerName);
Appender textAppender = createTextAppender(configuration);
textAppender.start();
logger.addAppender(textAppender);
logger.error("the error message", "Test Paramter");
logger.error("text message", "Test Paramter");
textAppender.stop();
}
private static Appender createTextAppender(final Configuration config) {
final Layout<String> layout = getTextLayout(config, "header");
return RollingFileAppender.newBuilder().setConfiguration(config).setName("txtAppender")
.withFileName("TestFile.text").withFilePattern("TestFile.txt")
.withPolicy(SizeBasedTriggeringPolicy.createPolicy("100M"))
.withStrategy(DefaultRolloverStrategy.newBuilder().withConfig(config).build()).withImmediateFlush(true)
.setFilter(ThresholdFilter.createFilter(Level.ALL, Result.ACCEPT, Result.DENY)).setLayout(layout)
.build();
}
private static Layout<String> getTextLayout(final Configuration config, final String header) {
return PatternLayout.newBuilder().withConfiguration(config).withCharset(StandardCharsets.UTF_8)
.withPattern("[%d][%-5.-5p] - %m%n").withHeader(header).build();
}
}
Why is this behaviour and how we can make the first message to second line ?
Your sample output shows "dummyHeader" but your code shows "header". If you want a newline then change that to "header\n".

How to separate a Log4j configuration?

I've got a program that has a log4j configuration written in XML. I am trying to modify the original application, and attempting to improve upon the previous logger config.
Since I cannot modify the xml file itself, I want to be able to generate a new configuration through the ConfigurationBuilderFactory, and use it alongside the other config. The only issue is that, I am unable to accomplish this. It does not seem to want to work with both.
What can I do?
The following is my code, greatly simplified:
/**
* Internally uses {#link org.apache.logging.log4j.Logger}
*/
public final class MyLogger {
private static final LoggerContext context;
static {
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
{
builder.setStatusLevel(WARN);
AppenderComponentBuilder console = builder.newAppender("SysOut", "Console");
console.addAttribute(...);
console.add(builder.newLayout(...).addAttribute(...));
builder.add(console);
// ... more configuration below
}
context = Configurator.initialize(builder.build()); // only works if no previous config exists, but will not replace an old config
}
}
// later on...
context.getLogger("MyLogger"); // uses the xml config, not the one written above
For creating loggers using the ConfigurationBuilder API programmatically you can refer below code.
It creates a logger in log4j2 environment added with some layout and appenders defined :
public class Log4j2Logger {
int counter = 0;
LoggerContext ctx;
Configuration config;
Logger logger;
String loggerName = "testLogger";
String appenderName = "myAppender";
static String testMessage = "This is a Test Message";
public void log() {
final ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
final LoggerComponentBuilder loggerComp = builder.newLogger(loggerName, Level.ALL).addAttribute("additivity",
false);
builder.add(loggerComp);
config = builder.build();
ctx = Configurator.initialize(config);
config = ctx.getConfiguration();
ctx.start(config);
ctx.updateLoggers(config);
// To create/add the logger of the configuration specified above we can use the
// getLogger(..) method
logger = ctx.getLogger(loggerName);
// Now we need to attach an appender to the logger so that our messages could be
// logged
logger.addAppender(addConsoleAppender(ctx.getConfiguration(), appenderName));
while (counter < 10) {
logger.error(testMessage + counter);
counter++;
}
}
private Appender addConsoleAppender(Configuration config, String appenderName) {
Appender consoleAppender = ConsoleAppender.newBuilder().setConfiguration(config).setName(appenderName)
.withImmediateFlush(true).build();
consoleAppender.start();
return consoleAppender;
}
}
And for testing, you can have following in any test class:
Log4j2Logger testLogger = new Log4j2Logger();
testLogger.log();
This API helps you to handle logs in a powerful way.
You can :
Create multiple loggers with your configuration
Add multiple Appenders to it.
Configure them also.
Remove logger when the usage is over.
Create Asynchronous Loggers also.
PS : I have used log4j2 version 2.12.1.
I think you can create your own log4j.xml. You have to ensure that your XML will be loaded in your program. So just define the resource containing your XML in the Java Classpath before the resource containing the other XML.

How to configure log4j fileappender for all classes?

In my application I read in an inputfile (e.g. myFile1.txt) and create an outputfile with the same name (e.g. myFile2log). Point is that the inputfile will be read within the java application and not given as command line parameter. Therefore it is required to set an appender in the application, e.g.
public class Example {
private static final Logger LOG = Logger.getLogger(Example.class);
public Example() throws IOException {
FileAppender appender = new DailyRollingFileAppender(new PatternLayout(
PatternLayout.DEFAULT_CONVERSION_PATTERN), "yourfilename.log",
"'.'yyyy-MM-dd");
LOG.addAppender(appender);
LOG.setLevel((Level) Level.DEBUG);
LOG.debug("blabla");
new RandomClass();
}
public static void main(String[] args) throws IOException {
new Example();
}
}
public class RandomClass {
private static final Logger LOG = Logger.getLogger(RandomClass.class);
public RandomClass() {
LOG.debug("hello Randomclass");
}
}
And here is the problem: if I do the above the custom file appender only works for the specific class where the fileappender was defined, but not on any other class. Therefore the output of the "RandomClass" will not be written into this logfile, but which is what is required. How can I achieve that?
If you would like to set the appender dynamically you should try setting the new appender to the root logger:
Logger logger = Logger.getRootLogger();
FileAppender appender = new DailyRollingFileAppender(new PatternLayout(
PatternLayout.DEFAULT_CONVERSION_PATTERN), "yourfilename.log", "'.'yyyy-MM-dd");
logger.addAppender(appender)
You can use log4j.properties file in your class path then simply you can initilize logger in all the classes.
private static final Logger LOG = Logger.getLogger(Example.class);
and Appender all not required in constructor. your property file should contain
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=yourfilename.log
log4j.appender.R.MaxFileSize=2048KB

Categories

Resources