I have a global logger which is used in a few classes so that I can log everything in a single file. All works good, however I want to be able to disable the Logger from UI. I tried setting the Level to OFF like shown below, which stops the logging but an empty log file is still created ( I am using File Appender with the logger).
Is there any easy way to avoid creating the log file when Level is OFF ?
public class Main {
public static Logger LOGGER = LogManager.getLogger("GLOBAL");
public static void main(String[] args) {
Configurator.setLevel("GLOBAL", Level.OFF);
//Rest of code
}
}
The LogManager.getLogger("GLOBAL") creates the log file while reading the log4j configuration and initializing it. So, there is no way you can stop it from doing it when you are at Configurator.setLevel("GLOBAL", Level.OFF);. IMO, you have 2 options:
1) Elegant way: Initialize the LogManager in your code by passing the configuration at runtime LogManager.getLogManager().readConfiguration. You could refer here for detailed implementation.
2) Ugly way: Delete the log file when you set the LEVEL.OFF
Related
I've found a few examples (even on Stack Overflow) of some programmatic configuration of Logback logging appenders, but as much as I've incorporated into my own setup hasn't worked for me so far. Some examples produce an actual Logger instance, but considering I've already got a Logger being statically instantiated within my class, I want to be able to programmatically enable an Appender that I've defined for unit testing purposes.
Here is my custom appender:
package org.example.logging;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.util.ArrayList;
import java.util.List;
// Credit to https://stackoverflow.com/a/29077499/5476186
public class TestAppender extends AppenderBase<ILoggingEvent> {
private static List<ILoggingEvent> events = new ArrayList<>();
#Override
protected void append(ILoggingEvent e) {
events.add(e);
}
public static List<ILoggingEvent> events() {
return List.copyOf(events);
}
public static void clear() {
events.clear();
}
}
And in my testing code, I'm trying to configure my TestAppender to "kick in" so that, after invoking this method in my test setup, I can capture the logs and validate them:
package org.example.logging;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;
// ...
// Mostly modeled after https://stackoverflow.com/a/7825548/5476186
private static void startAppender() {
LoggerContext logCtx = (LoggerContext) LoggerFactory.getILoggerFactory();
TestAppender appender = new TestAppender();
appender.setContext(logCtx);
appender.setName("TEST");
// I was hoping this would statically allow the appender to kick in,
// but all of the examples then attach this appender to a Logger instance.
appender.start();
}
Obviously, this isn't working for me. So I guess I have two contingent questions.
Is this possible and, if so, how can I make it work?
If this is not possible, what's the cleanest way to accomplish what I'm trying to do? (Enable/disable appenders during testing without having to manually mess with a config file.)
In one of the threads linked above, I found this answer which looks like one possible solution is to modify the text in the configuration file and to force a reload, but that doesn't seem super clean to me. Another option would be to create my own wrapper Logger factory which I could use to provide loggers with my TestAppender during test execution with dependency injection. I'll probably be creating a wrapper anyway, even though I'm using SLF4J.
Side note: I know that my test code as currently written is pretty tightly coupled with Logback instead of SLF4J, so I'm open to criticism/advice on that issue, too.
If you're using slf4j in your production code, then there is already a project that can help in testing: Its called slf4j-test
In a nutshell, it provides an API to retrieve a "test logger" in the test that will keep all the logged messages in memory so that you'll be able to verify them.
So that you:
Execute a method that logs something
Retrieve a test logger
call getLoggingEvents() on the test logger and verify the logged events
The link that I've provided contains an example of the API as well as maven integration example.
If, alternatively you would like to use logback directly for the tests or something, there is already a ListAppender shipped as a part of logback distribution that allows retrieval of events that have passed through the appender. You can add it programmatically to the logger and use inside the test.
Here you can find a comprehensive example of doing that
I have an application which keeps track of multiple students. When processing infromation on that student, I want their log messages to go into that log file.
logs/system.log
logs/abby.log
logs/brett.log
logs/catherine.log
The system can add more students dynamically, so I can't specify each student in my log config file. How can I, at runtime, specify that a logger should write information to catherine.log ?
Which Logging Framework are you using? Here is an example if you are using Log4j:
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.FileAppender;
public class MyTestClass {
/* Logger Instance should always be kept static final */
private static final Logger LOGGER = Logger.getLogger(MyTestClass.class);
public static void main(String args[]) {
/* Create Dynamic FileAppender */
SimpleLayout myLayout = new SimpleLayout();
FileAppender nwAppender = new FileAppender(myLayout,"file_nm",false);
LOGGER.addAppender(nwAppender);
LOGGER.setLevel((Level) Level.INFO);
/* Write Level : Debug */
LOGGER.debug("*** DEBUG ***");
/* Write Level : Info */
LOGGER.info("*** INFO ***");
/* Write Level : Error */
LOGGER.info("*** ERROR ***");
}
}
How can I, at runtime, specify that a logger should write information to catherine.log ?
There is no logging.properties option to enable this behavior. You have to write code to create a logger (strongly referenced) and installed a FileHandler on that logger.
Messages for abby.log are simultaneously written to the system log and to abby.log.
You should create a logger namespace such that the system file handler is installed on the root logger and each student is a child logger with setUseParentHandlers set to false.
If multiple threads are processing abby, it will open up abby-1, abby-2
That is because you are created multiple FileHandlers with the same file name. Create a Map and remember what you have opened.
This is a stupidly simple example, and yet for some reason it's not working. I must be missing something obvious.
I am trying to make a very simple log4j 2.0 example program. I have added these two jars to the classpath:
log4j-api-2.0-beta8.jar
log4j-core-2.0-beta8.jar
And have done the simplest example possible, using the default configuration:
package testlog;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestLog {
static Logger logger = LogManager.getLogger(TestLog.class.getName());
public static void main(String[] args) {
logger.trace("Hello World");
System.out.println("Test over");
}
}
But for some reason, all I get is 'Test over', I never get the Hello World, anywhere I can find anyway. Am I looking in the wrong place? It;s my understanding that with the default configuration it should be printed to the console, with the 'Test Over'. I have changed the log level to info, still the same. I have tried pulling out the Logger functionality into a class, still the same. I am following this tutorial on the log4j documentation page:
http://logging.apache.org/log4j/2.x/manual/configuration.html#AutomaticConfiguration
I can't understand what could possibly be wrong. Could anyone shed any light on what I've done wrong? Thanks in advance.
Update The logger DOES work with logger.error(). This would mean it is a problem with the default filters/level no?
You missed that line in the documentation:
Note that by default Log4j assigns the root logger to Level.ERROR.
Do a
logger.error("Hello World");
and it will be displayed.
If you want to display info and/or trace levels, you have to configure your logger.
So I have this code:
public class LoggingManager {
Logger root = Logger.getRootLogger();
public void setLogger(String fullClassName, String level) {
LogManager.getLogger(fullClassName).setLevel(Level.toLevel(level));
}
public void logLevelAll(String level) {
root.setLevel(Level.toLevel(level));
}
}
My problem is this. The first method takes in values: p1: com.domain.data.Object and p2: DEBUG and will change that classes log level to debug. The second class will change every logger in the project including libraries referenced like spring.
I want to create a method that only changes the logging level of my packages. So changes com.domain.* if you like.
Can this be done?
Simply set the log level for com.domain. By default each logger will inherit the log level from its parent logger.
Just realised that. Here is the full answer I got for others:
Log4j provides a default root logger that all user-defined loggers inherit from. Root logger is at the top of the logger hierarchy; in other words, root logger is either parent or ancestor of all logger objects created.
For example: A class 'MyClass' in com.domain.sampleapp application package can have a logger named com.domain.sampleapp.MyClass you can use my method above to set the class:
LogManager.getLogger("com.domain.sampleapp.MyClass").setLevel(Level.toLevel("DEBUG"));
But you can also set the package log level like so:
LogManager.getLogger("com.domain.sampleapp").setLevel(Level.toLevel("DEBUG"));
And finally you could set a whole group of packages (i.e ur whole project) by using:
LogManager.getLogger("com.domain").setLevel(Level.toLevel("DEBUG"));
I am placing a logging.properties in the WEB-INF/classes dir of tomcat
I would like to log to two different files. For example: org.pkg1 goes to one file and org.pkg2 goes to a separate file.
I can get one file configured, but not two. Is that possible?
I finally figured this out. In tomcat they extend java util logging ("JULI") to enable this functionality. Here's a logging.properties file that I put in the WEB-INF directory that finally accomplished what I was after......:
handlers=1console.java.util.logging.ConsoleHandler, 2jsp.org.apache.juli.FileHandler, 3financials.org.apache.juli.FileHandler
.handlers=1a.java.util.logging.ConsoleHandler
jsp.level=ALL
jsp.handlers=2jsp.org.apache.juli.FileHandler
org.apache.jasper.level = FINE
org.apache.jasper.handlers=2jsp.org.apache.juli.FileHandler
org.apache.jsp.level = FINE
org.apache.jsp.handlers=2jsp.org.apache.juli.FileHandler
com.paypal.level=ALL
com.paypal.handlers=3financials.org.apache.juli.FileHandler
3financials.org.apache.juli.FileHandler.level=ALL
3financials.org.apache.juli.FileHandler.directory=${catalina.base}/logs
3financials.org.apache.juli.FileHandler.prefix=financials.
2jsp.org.apache.juli.FileHandler.level=ALL
2jsp.org.apache.juli.FileHandler.directory=${catalina.base}/logs
2jsp.org.apache.juli.FileHandler.prefix=jsp.
1console.java.util.logging.ConsoleHandler.level=FINE
1console.java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
Speaking of logging.properties configuration, I did not found any mechanism to use more that one appender. I made simple workaround that works for me.
public class CustomAFileHandler extends FileHandler {
public DebugFileHandler() throws IOException, SecurityException {
super();
}
}
public class CustomBFileHandler extends FileHandler {
public DebugFileHandler() throws IOException, SecurityException {
super();
}
}
And my logging.properties
...
handlers=<pkg_name>.CustomAFileHandler, <pkg_name>.CustomBFileHandler, java.util.logging.ConsoleHandler
<pkg_name>.CustomAFileHandler.level=ALL
<pkg_name>.CustomAFileHandler.pattern=%h/A%u.log
<pkg_name>.CustomAFileHandler.limit=50000
<pkg_name>.CustomAFileHandler.count=1
<pkg_name>.CustomAFileHandler.formatter=java.util.logging.SimpleFormatter
<pkg_name>.CustomBFileHandler.level=ALL
<pkg_name>.CustomBFileHandler.pattern=%h/B%u.log
<pkg_name>.CustomBFileHandler.limit=50000
<pkg_name>.CustomBFileHandler.count=1
<pkg_name>.CustomBFileHandler.formatter=java.util.logging.SimpleFormatter
...
There's no easy way to get two handlers of the same type with java.util.logging classes that have different arguments. Probably the simplest way to do this is to create a FileHandler subclass in your logging.properties that passes the appropriate arguments to enable your logging to take place, such as:
org.pkg1.handlers=java.util.logging.FileHandler
org.pkg2.handlers=org.pkg2.FileHandler
java.util.logging.FileHandler.pattern="org_pkg1_%u.%g.log"
org.pkg2.FileHandler.pattern="org_pkg2_%u.%g.log"
org/pkg2/FileHandler.java:
package org.pkg2;
import java.util.logging.*;
public class FileHandler extends java.util.logging.FileHandler {
public FileHandler() {
super(LogManager.getLogManager().getProperty("org.pkg2.FileHandler.pattern"));
}
}
It is possible using pure jdk also (try with jdk 7 or jdk 8).
Just create custom file handler; use that similar to "java.util.logging.FileHandler".
public class JULTestingFileHandler extends FileHandler {
public JULTestingFileHandler() throws IOException, SecurityException
{
super();
}
}
user properties file;
com.xxx.handlers = com.xxx.JULXXXFileHandler
com.xxx.JULXXXFileHandler.pattern = ./logs/test1_test2.%u.%g.log
Having the same problem myself with java.util.logging and not quite satisfied with the given answers, I just found in the documentation:
2.2 Changing the Configuration
Here's a small program that dynamically adjusts the logging
configuration to send output to a specific file and to get lots of
information on wombats. The pattern "%t" means the system temporary
directory.
public static void main(String[] args) {
Handler fh = new FileHandler("%t/wombat.log");
Logger.getLogger("").addHandler(fh);
Logger.getLogger("com.wombat").setLevel(Level.FINEST);
...
}
So, it seems you can't do it just from the .properties file as can't instantiate several appenders, but you can do it programmatically. Also it should be possible using the LoggerManager