The JavaDocs for java.util.logging.Level state:
The levels in descending order are:
SEVERE (highest value)
WARNING
INFO
CONFIG
FINE
FINER
FINEST (lowest value)
Source
import java.util.logging.*;
class LoggingLevelsBlunder {
public static void main(String[] args) {
Logger logger = Logger.getAnonymousLogger();
logger.setLevel(Level.FINER);
System.out.println("Logging level is: " + logger.getLevel());
for (int ii=0; ii<3; ii++) {
logger.log(Level.FINE, ii + " " + (ii*ii));
logger.log(Level.INFO, ii + " " + (ii*ii));
}
}
}
Output
Logging level is: FINER
Jun 11, 2011 9:39:23 PM LoggingLevelsBlunder main
INFO: 0 0
Jun 11, 2011 9:39:24 PM LoggingLevelsBlunder main
INFO: 1 1
Jun 11, 2011 9:39:24 PM LoggingLevelsBlunder main
INFO: 2 4
Press any key to continue . . .
Problem statement
My example sets the Level to FINER, so I was expecting to see 2 messages for each loop. Instead I see a single message for each loop (the Level.FINE messages are missing).
Question
What needs changing in order to see the FINE (, FINER or FINEST) output?
Update (solution)
Thanks to Vineet Reynolds' answer, this version works according to my expectation. It displays 3 x INFO messages, & 3 x FINE messages.
import java.util.logging.*;
class LoggingLevelsBlunder {
public static void main(String[] args) {
Logger logger = Logger.getAnonymousLogger();
// LOG this level to the log
logger.setLevel(Level.FINER);
ConsoleHandler handler = new ConsoleHandler();
// PUBLISH this level
handler.setLevel(Level.FINER);
logger.addHandler(handler);
System.out.println("Logging level is: " + logger.getLevel());
for (int ii=0; ii<3; ii++) {
logger.log(Level.FINE, ii + " " + (ii*ii));
logger.log(Level.INFO, ii + " " + (ii*ii));
}
}
}
Loggers only log the message, i.e. they create the log records (or logging requests). They do not publish the messages to the destinations, which is taken care of by the Handlers. Setting the level of a logger, only causes it to create log records matching that level or higher.
You might be using a ConsoleHandler (I couldn't infer where your output is System.err or a file, but I would assume that it is the former), which defaults to publishing log records of the level Level.INFO. You will have to configure this handler, to publish log records of level Level.FINER and higher, for the desired outcome.
I would recommend reading the Java Logging Overview guide, in order to understand the underlying design. The guide covers the difference between the concept of a Logger and a Handler.
Editing the handler level
1. Using the Configuration file
The java.util.logging properties file (by default, this is the logging.properties file in JRE_HOME/lib) can be modified to change the default level of the ConsoleHandler:
java.util.logging.ConsoleHandler.level = FINER
2. Creating handlers at runtime
This is not recommended, for it would result in overriding the global configuration. Using this throughout your code base will result in a possibly unmanageable logger configuration.
Handler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.FINER);
Logger.getAnonymousLogger().addHandler(consoleHandler);
The Why
java.util.logging has a root logger that defaults to Level.INFO, and a ConsoleHandler attached to it that also defaults to Level.INFO.
FINE is lower than INFO, so fine messages are not displayed by default.
Solution 1
Create a logger for your whole application, e.g. from your package name or use Logger.getGlobal(), and hook your own ConsoleLogger to it.
Then either ask root logger to shut up (to avoid duplicate output of higher level messages), or ask your logger to not forward logs to root.
public static final Logger applog = Logger.getGlobal();
...
// Create and set handler
Handler systemOut = new ConsoleHandler();
systemOut.setLevel( Level.ALL );
applog.addHandler( systemOut );
applog.setLevel( Level.ALL );
// Prevent logs from processed by default Console handler.
applog.setUseParentHandlers( false ); // Solution 1
Logger.getLogger("").setLevel( Level.OFF ); // Solution 2
Solution 2
Alternatively, you may lower the root logger's bar.
You can set them by code:
Logger rootLog = Logger.getLogger("");
rootLog.setLevel( Level.FINE );
rootLog.getHandlers()[0].setLevel( Level.FINE ); // Default console handler
Or with logging configuration file, if you are using it:
.level = FINE
java.util.logging.ConsoleHandler.level = FINE
By lowering the global level, you may start seeing messages from core libraries, such as from some Swing or JavaFX components.
In this case you may set a Filter on the root logger to filter out messages not from your program.
WHY
As mentioned by #Sheepy, the reason why it doesn't work is that java.util.logging.Logger has a root logger that defaults to Level.INFO, and the ConsoleHandler attached to that root logger also defaults to Level.INFO. Therefore, in order to see the FINE (, FINER or FINEST) output, you need to set the default value of the root logger and its ConsoleHandler to Level.FINE as follows:
Logger.getLogger("").setLevel(Level.FINE);
Logger.getLogger("").getHandlers()[0].setLevel(Level.FINE);
The problem of your Update (solution)
As mentioned by #mins, you will have the messages printed twice on the console for INFO and above: first by the anonymous logger, then by its parent, the root logger which also has a ConsoleHandler set to INFO by default. To disable the root logger, you need to add this line of code: logger.setUseParentHandlers(false);
There are other ways to prevent logs from being processed by default Console handler of the root logger mentioned by #Sheepy, e.g.:
Logger.getLogger("").getHandlers()[0].setLevel( Level.OFF );
But Logger.getLogger("").setLevel( Level.OFF ); won't work because it only blocks the message passed directly to the root logger, not the message comes from a child logger. To illustrate how the Logger Hierarchy works, I draw the following diagram:
public void setLevel(Level newLevel) set the log level specifying which message levels will be logged by this logger. Message levels lower than this value will be discarded. The level value Level.OFF can be used to turn off logging. If the new level is null, it means that this node should inherit its level from its nearest ancestor with a specific (non-null) level value.
why is my java logging not working
provides a jar file that will help you work out why your logging in not working as expected.
It gives you a complete dump of what loggers and handlers have been installed and what levels are set and at which level in the logging hierarchy.
Tried other variants, this can be proper
Logger logger = Logger.getLogger(MyClass.class.getName());
Level level = Level.ALL;
for(Handler h : java.util.logging.Logger.getLogger("").getHandlers())
h.setLevel(level);
logger.setLevel(level);
// this must be shown
logger.fine("fine");
logger.info("info");
I found my actual problem and it was not mentioned in any answer: some of my unit-tests were causing logging initialization code to be run multiple times within the same test suite, messing up the logging on the later tests.
This solution appears better to me, regarding maintainability and design for change:
Create the logging property file embedding it in the resource project folder, to be included in the jar file:
# Logging
handlers = java.util.logging.ConsoleHandler
.level = ALL
# Console Logging
java.util.logging.ConsoleHandler.level = ALL
Load the property file from code:
public static java.net.URL retrieveURLOfJarResource(String resourceName) {
return Thread.currentThread().getContextClassLoader().getResource(resourceName);
}
public synchronized void initializeLogger() {
try (InputStream is = retrieveURLOfJarResource("logging.properties").openStream()) {
LogManager.getLogManager().readConfiguration(is);
} catch (IOException e) {
// ...
}
}
To change the logcat level:
adb shell setprop log.tag.<YOUR_LOG_TAG> <LEVEL>
For example:
C:\Users\my_name\AppData\Local\Android\Sdk\platform-tools\adb.exe shell setprop log.tag.com.mycompany.myapp VERBOSE
Here is why this works for Logger:
The root logger, which is the parent of your logger, is using an internal logging handler com.android.internal.logging.AndroidHandler. This handler in its publish method is checking Log.isLoggable and writing to Log.
Note about <YOUR_LOG_TAG>:
Basically this is the name of your Logger. For global Logger it is Logger.GLOBAL_LOGGER_NAME. For anonymous logger it is "null". Tag is limited to 23 characters.
AndroidHandler does a transformation from the logger name to the tag:
private static String loggerNameToTag(String loggerName) {
// Anonymous logger.
if (loggerName == null) {
return "null";
}
int length = loggerName.length();
if (length <= 23) {
return loggerName;
}
int lastPeriod = loggerName.lastIndexOf(".");
return length - (lastPeriod + 1) <= 23
? loggerName.substring(lastPeriod + 1)
: loggerName.substring(loggerName.length() - 23);
}
Related
The issue of this question has already been discussed e.g. for
C++
Python
The OpenCV documentation describes
ErrorCallback cv::redirectError ( ErrorCallback errCallback,
void * userdata = 0,
void ** prevUserdata = 0
)
How can this be made to use to e.g. filter out annoying messages?
An example is
[mjpeg # 0x7fe5a696ea00] unable to decode APP fields: Invalid data found when processing input
from a Logitech USB Webcam mjpeg stream which is created on every single frame and is superflous and not needed.
There is also a loglevel available. Unfortunately the import org.opencv.utils only contains "Converters" but no logging as of OpenCV 3.4.8
How could the loglevel be set from Java?
enum LogLevel {
LOG_LEVEL_SILENT = 0,
LOG_LEVEL_FATAL = 1,
LOG_LEVEL_ERROR = 2,
LOG_LEVEL_WARNING = 3,
LOG_LEVEL_INFO = 4,
LOG_LEVEL_DEBUG = 5,
LOG_LEVEL_VERBOSE = 6
}
Would Redirect System.out and System.err to slf4j help?
How can this be made to use to e.g. filter out annoying messages?
How could the loglevel be set from Java?
At this time (2020-01) it can't. Even if the API would be accessible from Java the bug https://github.com/opencv/opencv/issues/12780 would prevent it.
Would [Redirect System.out and System.err to slf4j][5] help?
No - see Junit test case below. The result is:
11:05:56.407 [main] DEBUG u.o.l.s.c.SysOutOverSLF4JInitialiser - Your logging framework class ch.qos.logback.classic.Logger should not need access to the standard println methods on the console, so you should not need to register a logging system package.
11:05:56.417 [main] INFO u.o.l.s.context.SysOutOverSLF4J - Replaced standard System.out and System.err PrintStreams with SLF4JPrintStreams
11:05:56.420 [main] INFO u.o.l.s.context.SysOutOverSLF4J - Redirected System.out and System.err to SLF4J for this context
11:05:56.421 [main] ERROR org.rcdukes.roi.TestROI - testing stderr via slf4j
[mjpeg # 0x7f958b1b0400] unable to decode APP fields: Invalid data found when processing input
[mjpeg # 0x7f958b027a00] unable to decode APP fields: Invalid data found when processing input
where the decode APP field part is still showing up via some stderr magic.
#Test
public void testLogStderr() throws Exception {
NativeLibrary.logStdErr();
System.err.println("testing stderr via slf4j");
NativeLibrary.load();
VideoCapture capture = new VideoCapture();
// Dorf Appenzell
//String url="http://213.193.89.202/axis-cgi/mjpg/video.cgi";
// Logitech Cam on test car
// url="http://picarford:8080/?action=stream";
File imgRoot = new File(testPath);
File testStream=new File(imgRoot,"logitech_test_stream.mjpg");
assertTrue(testStream.canRead());
capture.open(testStream.getPath());
Mat image=new Mat();
capture.read(image);
assertEquals(640,image.width());
assertEquals(480,image.height());
capture.release();
}
Funny side fact
According to the ffmpeg documentation
Log coloring can be disabled setting the environment variable AV_LOG_FORCE_NOCOLOR or NO_COLOR, or can be forced setting the environment variable AV_LOG_FORCE_COLOR. The use of the environment variable NO_COLOR is deprecated and will be dropped in a future FFmpeg version
But there seems to be no option to change the Logging level from an environment variable ...
I have a code that run concurrently, and for each run it need log something in file. Each execution - new story with unique file.
So, I can't just get the logger for classname, add file appender, write logs, close and remove appender, because when concurrect code run - logger will contains both appenders, and logs will be written to both files.
So, I can create a new logger instance for each execution Logger.getLogger(classname + counter), but how to mark it as garbage after work is done?
P.S. Moreover... I need somethimes print to console from all this loggers.
Maybe I do something wrong, maybe log4j not created for this pattern and I have to implement it. But log4j - priority choose for me, because it already widely used in this big application.
Thank you in advance, Andrei!
You can't destroy / clean up Appender instances. But you can improve the solution you have with Log4j2 toolbox.
Routing things to dynamic files in Log4j is possible, but most of the time a Marker in a file is sufficient, or at least a starting point for more complex routing.
Please do read up on its documentation. Especially the last paragraph is important to your case:
Some important rules about Markers must be considered when using them.
Markers must be unique. They are permanently registered by name so care should be taken to insure that Markers used in your application are distinct from those in the application's dependencies, unless that is what is desired.
Parent Markers can be added or removed dynamically. However, this is fairly expensive to do. Instead, it is recommended that the parents be identified when obtaining the Marker the first time as shown in the examples above. Specifically, the set method replaces all the markers in a single operation while add and remove act on only a single Marker at a time.
Evaluating Markers with multiple ancestors is much more expensive than Markers with no parents. For example, in one set of tests to evaluate whether a Marker matched its grandparent took 3 times longer than evaluating the Marker itself. Even then though, evaluating Markers is inexpensive compared to resolving the callers class name or line number.
Then you can use the marker in the configuration in places where a pattern is expected like this: $${marker:}. I haven't used that in a filename though and doubt that it works, but you can create routing based on the Marker.
I used this simple test script, please note the use of the Marker that is created for each line from the Scanner. In your case it would be created by config or from Servlet input or similar.
package toTest;
import java.util.Scanner;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
public class TestMe {
private final Logger myOneAndOnlyLogger = LogManager.getLogger("MyCentralName");
public static void main(String[] args) {
new TestMe().doMyThing();
}
private void doMyThing() {
Scanner input = new Scanner(System.in);
String line = "";
while(!line.equals("QUIT")) {
System.out.println("Line: ");
line = input.nextLine();
Marker forThisRound = MarkerManager.getMarker(line);
myOneAndOnlyLogger.log(Level.ERROR, forThisRound, "1");
myOneAndOnlyLogger.log(Level.ERROR, forThisRound, "2");
System.out.println("Line done.");
}
}
}
and this log4j2.properties (a rolling file example I had at hand with marker in the pattern):
status = error
name = MarkerExample
#Make sure to change log file path as per your need
property.filename = /tmp/java/marker.log
filters = threshold
filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appenders = rolling
appender.rolling.type = RollingFile
appender.rolling.name = RollingFile
appender.rolling.fileName = ${filename}
appender.rolling.filePattern = /tmp/java/debug-backup-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} $${marker:} %-5p %c{1}:%L - %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 1
appender.rolling.policies.time.modulate = true
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=10MBONE
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 20
loggers = rolling
#Make sure to change the package structure as per your application
logger.rolling.name = MyCentralName
logger.rolling.level = debug
logger.rolling.additivity = false
logger.rolling.appenderRef.rolling.ref = RollingFile
I need to set maxfile size for my application but currently i am using Dropwizard core version 0.8.4 and its file appender does not support this feature.
So I approached like below by writing a custom appender as updating to latest dropwizard(which support my need) version not an option right now.
private void initLogging(Configuration configuration) throws JoranException {
final File logDir = new File("/tmp/enforcer");
final File logFile = new File(logDir, "wallet.log");
final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
RollingFileAppender<ILoggingEvent> rollingFileAppender = new RollingFileAppender<ILoggingEvent>();
rollingFileAppender.setFile(logFile.getAbsolutePath());
rollingFileAppender.setName("com.documents4j.logger.server.file");
rollingFileAppender.setContext(loggerContext);
FixedWindowRollingPolicy fixedWindowRollingPolicy = new FixedWindowRollingPolicy();
fixedWindowRollingPolicy.setFileNamePattern(logFile.getAbsolutePath() +"%i.gz");
fixedWindowRollingPolicy.setMaxIndex(7);
fixedWindowRollingPolicy.setContext(loggerContext);
fixedWindowRollingPolicy.setParent(rollingFileAppender);
fixedWindowRollingPolicy.start();
SizeBasedTriggeringPolicy<ILoggingEvent> sizeBasedTriggeringPolicy = new SizeBasedTriggeringPolicy<ILoggingEvent>();
sizeBasedTriggeringPolicy.setMaxFileSize("2KB");
sizeBasedTriggeringPolicy.setContext(loggerContext);
sizeBasedTriggeringPolicy.start();
rollingFileAppender.setRollingPolicy(fixedWindowRollingPolicy);
rollingFileAppender.setTriggeringPolicy(sizeBasedTriggeringPolicy);
rollingFileAppender.start();
System.out.println("Logging: The log is written to " + logFile);
final ch.qos.logback.classic.Logger log = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
log.setLevel(Level.DEBUG);
log.addAppender(rollingFileAppender);
}
#Override
public void run(Configuration configuration, Environment environment) throws Exception
{
initLogging(configuration);
}
My yaml file config is
logging:
level: INFO
org.springframework.retry.support.RetryTemplate: DEBUG
appenders:
- type: file
currentLogFilename: /tmp/enforcer.log
threshold: ALL
archive: true
archivedLogFilenamePattern: /tmp/enforcer-%d.log
archivedFileCount: 5
timeZone: UTC
logFormat: '%-5level [%date{yyyy-MM-dd HH:mm:ss,SSS}] [%X{realdocRequestId}] %logger{15}: %m%n'
Now when i run my application i noticed, even though custom log file (/tmp/enforcer/wallet.log) is created in the particular directory but actual log is not dumped i.e. wallet.log file size is 0 kb where as the log file configured in yaml is created and size is certain kb and goes on increasing as log event is generated.
I am not able figure out what is wrong im doing, help will be appreciated.
your logger doesn't log anything because you never set an encoder to it. Set a breakpoint in:
OutputStreamAppender#start() and you will see that there is no encoder.
It will work once you add:
LayoutWrappingEncoder<ILoggingEvent> layoutEncoder = new LayoutWrappingEncoder<>();
DropwizardLayout formatter = new DropwizardLayout(loggerContext, TimeZone.getDefault());
formatter.setPattern("[%level] [%h] %d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z', UTC} [%thread] [%logger] %msg%n");
layoutEncoder.setLayout(formatter);
formatter.start();
rollingFileAppender.setEncoder(layoutEncoder);
Of course you can define whichever format you like (and formatter).
But keep in mind that this is a fairly hacky example of what you try to achieve. You now have 2 points in code where you configure logging (DW and your own). You have yaml configured logging as well as your own. I recommend investing a bit of work and adding a proper AppenderFactory that you can use.
Hope that helps,
Artur
At my work, I have inherited of the maintenance of a satandalone application.
The following code configures Log4J but no messages can be seen on the console.
LogManager.resetConfiguration();
PatternLayout layout = new PatternLayout();
layout.setConversionPattern("RECORD-BACKEND / (%-5p) %m (%F:%L)%n");
ConsoleAppender stderr = new ConsoleAppender();
stderr.setTarget(ConsoleAppender.SYSTEM_ERR);
stderr.setLayout(layout);
stderr.addFilter(new CurrentThreadLogFilter());
stderr.setThreshold(Level.INFO);
stderr.activateOptions();
Logger loggerRECORD = getLoggerRECORD();
loggerRECORD.setAdditivity(false);
loggerRECORD.addAppender(stderr);
Logger root = Logger.getRootLogger();
root.setLevel(Level.WARN);
root.addAppender(stderr);
// some lines forward ...
loggerRECORD.info("Process starting...");
What am I missing ?
Unfortunately you have not sent the code that actually prints the message. However I can assume that you try to do something like this:
logger.info("my message");
In this case your message will not be printed because it is filtered out by root logger defined to print warnings.
Loggers in Log4J are stored as hierarchical structure. The root logger is the entry point to this tree. Each logger filters log messages according to configured level. Therefore if upper level logger (root in your case) filters log message it even does not arrive to lower level logger and definitely does not arrive to appender.
The solution for you is to define root logger to allow ALL messages.
And the last note: do you have any special reasons to configure logger programmatically? Log4J can be configured using properties or (better) xml file. Take a look on this document.
And yet another note. Log4J has been deprecated by its creator. If you are starting now go forward to Logback as a logger and SLF4J as a light weight log interface.
Thanks to AlexR, here how I solved my problem:
LogManager.resetConfiguration();
PatternLayout layout = new PatternLayout();
layout.setConversionPattern("RECORD-BACKEND / (%-5p) %m (%F:%L)%n");
ConsoleAppender stderr = new ConsoleAppender();
stderr.setTarget(ConsoleAppender.SYSTEM_ERR);
stderr.setLayout(layout);
stderr.addFilter(new CurrentThreadLogFilter());
stderr.setThreshold(Level.INFO);
stderr.activateOptions();
Logger loggerRECORD = getLoggerRECORD();
loggerRECORD.setLevel( /* get Log Level from env. */ );
loggerRECORD.setAdditivity(false);
loggerRECORD.addAppender(stderr);
Logger root = Logger.getRootLogger();
root.setLevel(Level.WARN);
root.addAppender(stderr);
// some lines forward ...
loggerRECORD.info("Process starting...");
Since additivity is set to false for loggerRECORD, the appender sucessfully print any message from INFO to ERROR.
I'm trying to programmatically configure LogBack's RollingFileAppender (ch.qos.logback.core.rolling.RollingFileAppender) and it doesn't seem to be working. When I'm using FileAppender, everything seems to be working fine with exact same configuration (less policies/trigger) so I'm guessing it's not a permission issue. I tried commenting out all policy configuration and that didn't help either. Below is my sample code, with some hard-coded values. Also, there's no error at all what so ever. When I debug the LogBack source code, I didn't see anything that could have gone wrong.
Any hint is appreciative. I need to get this working without a configuration file since that's a restriction in my organization. I'm testing this out on a MacBook.
Logger logger = (Logger)LoggerFactory.getLogger(applicationName);
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.reset();
RollingFileAppender<ILoggingEvent> fileAppender =
new RollingFileAppender<ILoggingEvent>();
fileAppender.setAppend(true);
fileAppender.setFile("/Users/Jack/Desktop/logs/" + applicationName + ".log");
fileAppender.setContext(lc);
SizeBasedTriggeringPolicy<ILoggingEvent> rPolicy =
new SizeBasedTriggeringPolicy<ILoggingEvent>("20MB");
fileAppender.setTriggeringPolicy(rPolicy);
TimeBasedRollingPolicy<ILoggingEvent> tPolicy =
new TimeBasedRollingPolicy<ILoggingEvent>();
tPolicy.setFileNamePattern("/archive/" + applicationName + ".%d");
tPolicy.setMaxHistory(180);
tPolicy.setParent(fileAppender);
tPolicy.setContext(lc);
PatternLayout pl = new PatternLayout();
pl.setPattern("%d %5p %t [%c:%L] %m%n)");
pl.setContext(lc);
pl.start();
fileAppender.setLayout(pl);
fileAppender.start();
logger.addAppender(fileAppender);
logger.setLevel(Level.DEBUG);
logger.debug("Test message");
The key issues are as follow:
RollingFileAppender must have RollingPolicy
RollingFileAppender requires PatternLayoutEncoder instead of PatternEncoder
RollingPolicy must also be started or certain properties will be null
What made this very difficult to figure out is that I couldn't figure out how to make BasicStatusManager print out error message. I ended up having to use the following code to print everything out.
for(Status status : logger.getLoggerContext().getStatusManager().getCopyOfStatusList()){
System.out.println(status.getOrigin() + " - " + status.getMessage());
}
There is a separate thread going on as mentioned in the comment above on why LogBack log messages are not printing out. I also have an email thread going on with Nabble. Will post the solution in that thread as soon as I or someone can figure this out.