How can I log to a specific file path with log4j2 in a library?

94 Views Asked by At

I have a Java Library (SDK) that will be consumed as a JAR in other applications. I'm using log4j2 and SLF4J to log right now, but when my library is consumed by the other application (also using log4j2.xml) the logs are not created. I need to be able to log to a specific directory and path with a specific file name (rolling appender).

I've tried searching for information on this, and it seems like using SLF4J as a facade is the way to go, but I can't find anyway to provide my own file path, format and configuration.

Is it bad practice to do this? I do this in C#, TypeScript and every other language, I only seem to have this problem with Java.

As I understand it, the SLF4J provides a facade for the developer who uses my library to use their own logging framework. If I rely on the developer who is implementing my library to include a logging library, how can I guarantee my logs are being generated? I need to be able to troubleshoot issues from the field, if a bug is reported, I need to be able to review the logs to see what went wrong.

Am I using the wrong logging for this use case? Is there some way I can specify a different configuration file for log4j2 that isn't overridden by the consuming application's configuration?

To clarify, I have a "libary" which is really just an API with methods to obscure and simplify making WebSocket calls. This API will be consumable by other applications. I need to log to a specific file path to ensure that if there are issues, I can trace them through the log files to find out the root of the issue. I can't rely on the consumer of the API to implement their own logging implementation and configure it to a specific path. I have a log4j2.xml, but it seems like this is overridden if the consuming library is also using its own log4j2.xml.

Here's a diagram I hope helps

In the image, the blue and orange JAR have different log4j2.xml files. I have no control over the orange, only the blue.

Please help.

1

There are 1 best solutions below

0
Thomas Yamakaitis On

After a few helpful comments, I believe the solution is to use a ConfigurationFactory to specify a separate "private" LoggerContext and a custom log4j2 configuration file as a ConfigurationSource. Unfortunately, there doesn't seem to be a lot of information out there about doing this.

Here's what worked for me, in the constructor for my logger, I first check if the Configuration file exists, if it doesn't I call my static createConfiguration method (below). If the configuration file exists I create a ConfigurationFactory and point it to my specified configuration file, then create a custom Configuration using the Configuration file as the ConfigurationSource and a new "private" LoggerContext:

protected CustomLogger() throws IOException {
   // Check if the Configuration file exists
   if (!configFile.isFile()) {
        createConfiguration();
   }

   // Get instance of configuration factory on Startup
   ConfigurationFactory factory = XmlConfigurationFactory.getInstance();

   // Create a "Private" LoggerContext instance
   LoggerContext context = new LoggerContext("PrivateLoggerContext");

   // Locate the source of your configuration
   ConfigurationSource configurationSource = new ConfigurationSource(new 
   FileInputStream(new File(configFilePath)));

   // Get a reference from configuration
   Configuration configuration = factory.getConfiguration(context, configurationSource);

   // Start logging system
   context.start(configuration);

   // Get a reference for logger
   logger = context.getLogger("com.packagename");

   logger.info("TEST");
}

And in my logger class, I have a static "createConfiguration" method that programmatically generates the configuration file and outputs it to my desired file path (in this case, "C:/ProgramData/PackageName", however I suggest allowing your library users to specify a path and just provide a default):

public static void createConfiguration() throws IOException {
    // Create the configuration directory if it doesn't exist
    new File("C:/ProgramData/PackageName").mkdirs();

    // Create a ConfigurationBuilder to build out the configuration
    ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();

    AppenderComponentBuilder consoleAppender = builder.newAppender("console", "Console");
    LayoutComponentBuilder patternLayout = builder.newLayout("PatternLayout").addAttribute("pattern", "%d{MM/dd/yyyy HH:mm:ss.SSSS} [%t] %-5level %logger - %msg%n");
    consoleAppender.add(patternLayout);

    builder.add(consoleAppender);

    LoggerComponentBuilder logger = builder.newLogger("com.packagename", Level.DEBUG);
    logger.addAttribute("additivity", false);
    logger.add(builder.newAppenderRef("console"));

    builder.add(logger);

    builder.writeXmlConfiguration(new FileOutputStream(configFile));
}

Finally, in the constructor for my library I create my Logger just like usual:

public Library() {
    Logger logger = new CustomLogger();
}

I hope this answer helps someone in the future who may run into this, and if anyone has any recommendations or comments about the code here, please feel free to comment. I will edit if needed.

References for this answer: