Avoid discarding old entries in MemoryHandler - java

When logging to a MemoryHandler, the MemoryHandler removes older entries when the numofentries > size.
I want to avoid this behavior, or at least mark down to the log that older entries are suppressed.
Little test case:
import java.util.logging.*;
public class SSCE01 {
public static void main (String [] args) {
Logger rootLogger = Logger.getLogger("");
rootLogger.removeHandler(rootLogger.getHandlers()[0]); //remove default Console Handler
ConsoleHandler ch = new ConsoleHandler();
Logger l = Logger.getLogger("test");
MemoryHandler mh = new MemoryHandler(ch,3,Level.OFF);
l.addHandler(mh);
l.severe("this shouldnt be logged");
l.severe("this shouldnt be logged");
l.severe("this shouldnt be logged");
l.severe("this should be logged");
l.severe("this should be logged");
l.severe("this should be logged");
mh.push();
}
}

This is a bad idea, you'll fill the memory machine with traces. Despite this, MemoryHandle is a circular buffer so when it gets filled other entries will be removed. If you want entries to not be removed just construct it with Integer.MAX_VALUE as size - again this a bad idea. This will collide with your app performance, and people tend to avoid that.
Consider using a handler that dumps traces into secondary storage, add a timestamp; and build whatever logic you need using the traces from there.
Edit
From the code you reported, you could encapsulate your logging functionality in another class that records the number of entries in MemoryHandler. Something like:
class MyMemoryConsoleHandler {
private Logger rootLogger;
private MemoryHandler mh;
private Logger l;
private int size = 3;
private int entries = 0;
public MyMemoryConsoleHandler() {
this.rootLogger = Logger.getLogger("");
this.rootLogger.removeHandler(rootLogger.getHandlers()[0]);
ConsoleHandler ch = new ConsoleHandler();
this.l = Logger.getLogger("test");
this.mh = new MemoryHandler(ch,this.size,Level.OFF);
}
public synchronized void push() {
this.mh.push();
if (this.entries > this.size) {
this.l.severe("Entries in log discarded !!!");
this.mh.push();
}
this.entries = 0;
}
public synchronized void addMessage(String m) {
this.entries++;
this.l.severe(m);
}
}
Instead of using Java API's logging calls directly use your MyMemoryConsoleHandler so that you have control over what is pushed to the console.
Pay attention to synchronized methods, this is needed in case you have a multi-threaded application. Otherwise you could end up with race conditions.

Related

Java Logger usage

I made a custom logger for my project by using java.util.logging:
public class SpotifyLogger {
private static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
public SpotifyLogger(String loggerFilePath) throws IOException {
Logger myLogger = Logger.getLogger("");
// suppress console messaging
Handler[] handlers = myLogger.getHandlers();
if (handlers[0] instanceof ConsoleHandler) { //exception occurs here
myLogger.removeHandler(handlers[0]);
}
// set level
LOGGER.setLevel(Level.SEVERE);
// create a txt handler
FileHandler textFileHandler = new FileHandler(loggerFilePath);
SimpleFormatter simpleFormatter = new SimpleFormatter();
textFileHandler.setFormatter(simpleFormatter);
LOGGER.addHandler(textFileHandler);
}
public void log(String user, Exception e) {
LOGGER.log(Level.SEVERE, user, e);
}
}
For the client and the server parts of my program, I create two separate Logger objects:
// class member initialized as null, because of exception handling
private SpotifyLogger logger = null;
//...
//in constructor:
this.logger = new SpotifyLogger(LOGGER_FILE_NAME); // the LOGGER_FILE_NAME is different for the client and the server
When I test my program manually, the loggers seem to work (the two log files contain exceptions that I have caused). Then, I wrote automatic tests. For each class that I am testing (a total of 5), I create a separate logger object with a different destination path. The tests (for whichever class comes first) work correctly. All other tests fail because I get an ArrayIndexOutOfBoundsException, when I initialize the logger for that particular class. The reason is that I am trying to access handlers[0], when handlers has 0 length. From what I understood after searching the web, this is because the logger is using parent handlers. I tried this:
public SpotifyLogger(String loggerFilePath) throws IOException {
Logger myLogger = Logger.getLogger("");
// suppress console messaging
myLogger.setUseParentHandlers(false);
Handler[] handlers = myLogger.getHandlers();
if (handlers.length > 0) {
if (handlers[0] instanceof ConsoleHandler) {
myLogger.removeHandler(handlers[0]);
}
}
//etc
}
I don't get an exception anymore but the logging doesn't work. What am I doing wrong?
If you want different Loggers, you need to supply different names for each. Hence this line of your code (in SpotifyLogger constructor) always returns the same Logger.
Logger myLogger = Logger.getLogger("");
This actually returns the java.util.logging.LogManager.RootLogger which has a single Handler which is an instance of ConsoleLogger. You subsequently remove that Handler in the first invocation of SpotifyLogger constructor, hence in every subsequent invocation, method getHandlers returns an empty array.
Since you only ever add Handlers to the global Logger, another FileHandler is added to the global logger every time SpotifyLogger constructor is called. I have not verified but I believe that a Logger will use the first, appropriate Handler in the array returned by method getHandlers, hence the behavior you are seeing whereby only the first log file is being written to, i.e. the file that you passed to the first invocation of SpotifyLogger constructor.
Note that you have not provided a reproducible example so I cannot verify any of the above with regard to your context. I only tested the code in your question in order to arrive at the above.
Consider the following rewrite of class SpotifyLogger – including a main method for testing purposes only.
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class SpotifyLogger {
private static final String[] NAMES = {"First", "Second"};
private static int count;
private int index;
public SpotifyLogger(String loggerFilePath) throws IOException {
index = count;
Logger myLogger = Logger.getLogger(NAMES[count++]);
myLogger.setUseParentHandlers(false);
// set level
myLogger.setLevel(Level.SEVERE);
// create a txt handler
FileHandler textFileHandler = new FileHandler(loggerFilePath);
SimpleFormatter simpleFormatter = new SimpleFormatter();
textFileHandler.setFormatter(simpleFormatter);
myLogger.addHandler(textFileHandler);
}
public void log(String user, Exception e) {
Logger myLogger = Logger.getLogger(NAMES[index]);
myLogger.log(Level.SEVERE, user, e);
}
public static void main(String[] args) {
try {
SpotifyLogger x = new SpotifyLogger("spotifyx.log");
SpotifyLogger y = new SpotifyLogger("spotifyy.log");
x.log("George", new Exception());
y.log("Martha", new RuntimeException());
}
catch (IOException x) {
x.printStackTrace();
}
}
}
Note that you are correct regarding parent Handlers, hence the following line in the above code:
myLogger.setUseParentHandlers(false);
After running the above code, the contents of file spotifyx.log is:
Feb 12, 2022 2:12:28 PM javalogp.SpotifyLogger log
SEVERE: George
java.lang.Exception
at javalogp/javalogp.SpotifyLogger.main(SpotifyLogger.java:38)
And the contents of file spotifyy.log is:
Feb 12, 2022 2:12:28 PM javalogp.SpotifyLogger log
SEVERE: Martha
java.lang.RuntimeException
at javalogp/javalogp.SpotifyLogger.main(SpotifyLogger.java:39)
And no log messages are written to the console.
This doesn't make much sense to me
Logger myLogger = Logger.getLogger("")
Reference: Oracle java docs

Simplest possible setting Java logging level, and it still fails. But, System.out.println still works

I am having problems getting logging to print out in some of my constructors. With others, logging is working just as I would expect.
I have attempted to eliminate every possible source of confusion by going with defaults, and by explicitly setting the log levels within the class itself. I've tried instantiating the logger as both a static and instance variable.
But, even setting the logging level to "Level.ALL" doesn't seem to be working in some of the classes.
For instance loggers, I have been setting the logging level within an instance-initializer / anonymous block. For the static instance, I am using the static block to set the logging level.
Any ideas why the log messages aren't printing out in the classes below?
Note: Code has been edited to show the fix, based on comment by #jmehrens
public abstract class ReadFileInformation extends SimpleFileVisitor<Path> {
static public ConsoleHandler globalConsoleHandler = new ConsoleHandler(); // fix, based on comment by #jmehrens
static private final Logger logger = Logger.getLogger(ReadFileInformation.class.getName());
static { // fix: See comment by #jmehrens
globalConsoleHandler.setLevel(Level.ALL);
logger.addHandler(globalConsoleHandler);
}
{
logger.setLevel(Level.ALL);
logger.log(Level.FINEST, String.format("This does NOT print.\n"));
System.out.println("In ReadFileInformation Static Block. This prints out!");
}
}
public class ReadMetadateFileInformation extends ReadFileInformation {
static private final Logger logger = Logger.getLogger(ReadMetadateFileInformation.class.getName());
static {
logger.setLevel(Level.ALL);
logger.log(Level.FINEST, String.format("This does NOT print.\n"));
System.out.println("In ReadMetadateFileInformation Static Block. This prints out!");
}
{
logger.log(Level.FINEST, String.format("This does NOT print.\n"));
System.out.println("In ReadMetadateFileInformation Anonymous Block. This prints out!");
}
public ReadMetadateFileInformation() {
super();
logger.log(Level.FINE, String.format("This does NOT print.\n"));
}
}
Per the java.util.logging.ConsoleHandler docs:
.level specifies the default level for the Handler (defaults to Level.INFO).
You are adjusting the logger to set the level to ALL which will produce the log records you want to see. However, the ConsoleHandler will filter them because the default level of the ConsoleHandler is INFO. Therefore, you have to adjust the level of the ConsoleHandler too. You need to do one of the following:
Add a ConsoleHandler and set the level to ALL.
Modify the root ConsoleHandler to set the level to ALL.
Since you are modifying the logger configuration via code it might be easier to do #1.
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.ALL);
logger.addHandler(ch);
logger.setLevel(Level.ALL);
logger.log(Level.FINEST, String.format("This does NOT print.\n"));
You also have to ensure you don't add the ConsoleHandler multiple times to the logger as each handler add will result in duplicated console output.

Java logging API MemoryHandler dumping log instantly

I want to log my application's messages after certain threshold. Say after 10 messages. I read about memory handler and used it. However I found that it logs the messages instantly instead of buffering them as said in documentation. Here's code
Handler h = new FileHandler('/var/tmp/process.log',Level.INFO);
Handler h2 = new MemoryHandler(h, 10, Level.ALL);
logger.addHandler(h2);
for(int i=0; i<10; i++) {
logger.log(Level.INFO, "Sample message");
Thread.sleep(1000);
}
This code is adding above message instantly. What am I missing? My purpose is to not let too much disk I/O happen. Please help
The third constructor argument in
Handler h2 = new MemoryHandler(h, 10, Level.ALL);
defines the push level, i.e. if a message of the given level or above it is logged, the MemoryHandler will push it to the configured downstream handler (jdk documentation).
I don't think, that the MemoryHandler is suitable for the purpose you'd like to achieve. You could create your own implementation of a MemoryHandler with a fixed size buffer, that flushes whenever the buffer is full. But consider the drawbacks of this approach: log messages can get lost when the application terminates, flushing may involve blocking I/O and you cannot determine which thread will have to execute that I/O.
Alternatively you could think about using another proven logging framework, like logback or log4j2. These generally offer more advanced functionality. I suggest to look for asynchronous logging.
You have to extend the MemoryHandler to provide custom push behavior. You can do this by setting the push level to ALL overriding the push method or by setting the push level to OFF and then manually issuing a push from the publish method.
If you want to only start logging after a number of log records are seen then you want to create something like:
public class PopoffHandler extends MemoryHandler {
private long count;
private final long size;
public PopoffHandler(Handler target, int size) {
super(target, size, Level.ALL);
this.size = size;
}
#Override
public synchronized void push() {
if (count == size) {
super.push();
} else {
++count;
}
}
#Override
public void setPushLevel(Level newLevel) {
if (newLevel == null) {
throw new NullPointerException();
}
super.setPushLevel(Level.ALL);
}
}
If you want to log records in groups then you want to do something like:
public class ChunkedHandler extends MemoryHandler {
private long count;
private final long size;
public ChunkedHandler(Handler target, int size) {
super(target, size, Level.OFF);
this.size = size;
}
#Override
public synchronized void publish(LogRecord record) {
super.publish(record);
if (++count % size == 0L) {
super.push();
}
}
#Override
public void setPushLevel(Level newLevel) {
if (newLevel == null) {
throw new NullPointerException();
}
super.setPushLevel(Level.OFF);
}
}

How to create my own Appender in log4j?

I am new in log4j. Can anyone explain how to create my own Appender? i.e. how to implement the classes and interfaces and how to override it?
Update: the provided solution is valid for Log4J 1.x . If you're looking for 2.x versions, take a look at this article: How to create a custom appender in log4j2
You should extend AppenderSkeleton class, that (quoting javadoc) "provides the code for common functionality, such as support for threshold filtering and support for general filters."
If you read the code of AppenderSkeleton, you'll see that it handles almost all, leaving to you just:
protected void append(LoggingEvent event)
public void close()
public boolean requiresLayout()
The core method is append. Remember that you don't need to implement the filtering logic in it because it is already implemented in doAppend that in turn calls append.
Here I made a (quite useless) class that stores the log entries in an ArrayList, just as a demo.
public /*static*/ class MyAppender extends AppenderSkeleton {
ArrayList<LoggingEvent> eventsList = new ArrayList();
#Override
protected void append(LoggingEvent event) {
eventsList.add(event);
}
public void close() {
}
public boolean requiresLayout() {
return false;
}
}
Ok, let's test it:
public static void main (String [] args) {
Logger l = Logger.getLogger("test");
MyAppender app = new MyAppender();
l.addAppender(app);
l.warn("first");
l.warn("second");
l.warn("third");
l.trace("fourth shouldn't be printed");
for (LoggingEvent le: app.eventsList) {
System.out.println("***" + le.getMessage());
}
}
You should have "first", "second", "third" printed; the fourth message shouldn't be printed since the log level of root logger is debug while the event level is trace. This proves that AbstractSkeleton implements "level management" correctly for us. So that's definitely seems the way to go... now the question: why do you need a custom appender while there are many built in that log to almost any destination? (btw a good place to start with log4j: http://logging.apache.org/log4j/1.2/manual.html)
If you would like to do some manipulations or decisions you can do it like this:
#Override
protected void append(LoggingEvent event) {
String message = null;
if(event.locationInformationExists()){
StringBuilder formatedMessage = new StringBuilder();
formatedMessage.append(event.getLocationInformation().getClassName());
formatedMessage.append(".");
formatedMessage.append(event.getLocationInformation().getMethodName());
formatedMessage.append(":");
formatedMessage.append(event.getLocationInformation().getLineNumber());
formatedMessage.append(" - ");
formatedMessage.append(event.getMessage().toString());
message = formatedMessage.toString();
}else{
message = event.getMessage().toString();
}
switch(event.getLevel().toInt()){
case Level.INFO_INT:
//your decision
break;
case Level.DEBUG_INT:
//your decision
break;
case Level.ERROR_INT:
//your decision
break;
case Level.WARN_INT:
//your decision
break;
case Level.TRACE_INT:
//your decision
break;
default:
//your decision
break;
}
}
I would like to expend #AgostinoX answer to support pro file configuration and the ability to start and stop the logging capture :
public class StringBufferAppender extends org.apache.log4j.AppenderSkeleton {
StringBuffer logs = new StringBuffer();
AtomicBoolean captureMode = new AtomicBoolean(false);
public void close() {
// TODO Auto-generated method stub
}
public boolean requiresLayout() {
// TODO Auto-generated method stub
return false;
}
#Override
protected void append(LoggingEvent event) {
if(captureMode.get())
logs.append(event.getMessage());
}
public void start()
{
//System.out.println("[StringBufferAppender|start] - Start capturing logs");
StringBuffer logs = new StringBuffer();
captureMode.set(true);
}
public StringBuffer stop()
{
//System.out.println("[StringBufferAppender|start] - Stop capturing logs");
captureMode.set(false);
StringBuffer data = new StringBuffer(logs);
logs = null;
return data;
}
}
Now all you have to do is to define in in the log4j.property file
log4j.rootLogger=...., myAppender # here you adding your appendr name
log4j.appender.myAppender=com.roi.log.StringBufferAppender # pointing it to the implementation
than when ever you want to enable it during runtume:
Logger logger = Logger.getRootLogger();
StringBufferAppender appender = (StringBufferAppender)logger.getAppender("myAppender");
appender.start();
and while want to stop it:
StringBuffer sb = appender.stop();
To create a own Appender you just implement the Appender Interface and just override it.
And also study this link start log

Java logger problem

I would like to record the logs of my Java application.
I have created this class:
public class Log {
final Logger logger = Logger.getLogger("DigiScope.log");
public static void main(String[] args) {
}
public Log(String message) {
try {
// Create an appending file handler
boolean append = true;
FileHandler handler = new FileHandler("my.log", append);
// Add to the desired logger
Logger logger = Logger.getLogger("com.mycompany");
logger.addHandler(handler);
logger.info(message);
} catch (IOException e) {
}
}
}
And for each button I have a code like that:
private void btnNewPatient ActionPerformed(java.awt.event.ActionEvent evt) {
Log a = new Log("New Patient created");
}
This code creates a log.txt, but records only the click on the first button, the others clicks on the others buttons are not record.
Can you help me?
Thank you.
It doesn't make much sense to create a proprietary logging wrapper in your app - java.util.logging is already such a wrapper, so I recommend using it directly. You should create logger objects in your classes, then log messages within handler methods something like this:
logger.info("New Patient created");
And you should use the same Logger instance throughout your class, instead of creating new instances all the time. The standard way is to create one static final instance per class.
It is also better to configure logging from a config file, not from code.
I recommend reading through the Java Logging Tutorial.
Péter Török and StriplingWarrior are right with their suggestion to use the Logging framework in the right way:
logger.info("New Patient created");
instead of creating a new Logger for each statement.
But even with your construction, you should have a logfile with all logging information.
For each invocation of the Log constructor, a new my.log.X file is created (X is a number). And from this point in time each log statement is logged in this file.
So if you invoke the constructor three times (with message: "first", "second", "third") then you should have the files: my.log, my.log.1. my.log.2
my.log: "first", "second", "third"
my.log.1: "second", "third"
my.log.2: "third"
I'm guessing that it's probably logging each click, but you're reopening the file with each new log message, rather than appending to it.

Categories

Resources