I have a set of classes across multiple packages, and I want any logging from within a single "com.name.root.xxx" package and all its child packages, to be logged to a different file. e.g:
//Package Name Log To \\
//-------------------------------------------\\
com.name.root.router C:\com\router.log
com.name.root.router.utils C:\com\router.log
com.name.root.init C:\com\init.log
com.name.root.database C:\com\database.log
com.name.root.web C:\com\web.log
com.name.root.web.rest C:\com\web.log
com.name.root.web.http C:\com\web.log
etc.
I have created a helper class which tracks which log file paths have been set up with file handlers already, and also which packages have already had their loggers set up; and provides accordingly.
I am interested in review for efficiency, and if there's a better (simpler/cleaner/more understandable) way to do this:
package com.name.root.util.log;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LogProvider
{
private static final String rootPackageName = "com.name.root";
private static final int rootPackageNameLength = rootPackageName.length();
private static final HashMap<String,FileHandler> fileHandlersByPath = new HashMap<>();
private static final HashSet<String> alreadyProvidedPackages = new HashSet<>();
public static Logger getConfiguredLogger(Class<?> callingClass, String logPathIfNotAlreadySet)
{
return getConfiguredLogger(callingClass, logPathIfNotAlreadySet, Level.FINEST); // default log level
}
public static Logger getConfiguredLogger(Class<?> callingClass, String logPathIfNotAlreadySet, Level maxLogLevelIfNotAlreadySet)
{
String fqClassName = callingClass.getCanonicalName(); // e.g. com.name.root.router.utils
String packageName = fqClassName; // default
if (fqClassName.startsWith(rootPackageName))
{
// we want to just get as far as the main package after "com.name.root" - e.g. "com.name.root.router"
packageName = fqClassName.substring(0,fqClassName.indexOf(".", rootPackageNameLength+1));
}
return getConfiguredLogger(packageName, logPathIfNotAlreadySet, maxLogLevelIfNotAlreadySet);
}
private static Logger getConfiguredLogger(String packageName, String logPathIfNotAlreadySet, Level maxLogLevelIfNotAlreadySet)
{
Logger logger = Logger.getLogger(packageName); // get the logger for the package
if (alreadyProvidedPackages.contains(packageName))
{
return logger; // we've already configured this logger
}
else
{
alreadyProvidedPackages.add(packageName);
logger.setLevel(maxLogLevelIfNotAlreadySet);
String logPath = (logPathIfNotAlreadySet == null || logPathIfNotAlreadySet.isBlank() ? "C:\\com\\output.log" : logPathIfNotAlreadySet);
try
{
// reuse an existing file handler if possible, so we don't get multiple output files if two packages want to log to the same file
FileHandler fh = null;
if (fileHandlersByPath.containsKey(logPath))
{
fh = (fileHandlersByPath.get(logPath));
}
else
{
fh = new FileHandler(logPath, false);
fh.setFormatter(new customSingleLineLogFormatter()); // The formatter itself is out of scope for review
fileHandlersByPath.put(logPath, fh);
}
logger.addHandler(fh);
}
catch (SecurityException | IOException e)
{
e.printStackTrace();
}
return logger;
}
}
}
Example Usage:
For utility classes (e.g. static database access classes), I am just passing the logger into each method that uses it, because it can be used from multiple packages and should log as if it was a part of the calling class:
package com.name.root.util.database
public class StringUtils
{
// just an example
public static long parseStringToEpoch(String s, Logger logger)
{
logger.entering("parseStringToEpoch"); // should turn up in the calling class's log file
}
}
but for all other classes, each class has its own static final Logger instance which is instantiated along with the class, calling the getConfiguredLogger method:
package com.name.root.router.base
public abstract class BaseRouter
{
private static final Logger logger = LogProvider.getConfiguredLogger(BaseRouter.class, "C:\\com\\Router.log");`
// etc, including static methods that log
}
package com.name.root.router.impl
public class ChildRouter
{
private static final Logger logger = LogProvider.getConfiguredLogger(ChildRouter.class, "C:\\com\\Router.log");`
// etc, including main and static methods that log
// sample usage of utilities methods
private static final long testEpoch = StringUtils.parseStringToEpoch("1234567",logger);
}