Suppose I have a program with 20 classes, each with private static Logger logger = LoggerFactory.getLogger(SomeClass.class);. These 20 classes will log warn, info, debug, or error.
Within one of the classes, how can I can access to all the errors logged in the program thus far (across all of the classes)? Note I'm not interested in dumping all these to a .log, I want access to them from within the Java program.
Use a custom appender to add to list which is singleton list using that you can access all the mismatch.
CustomAppender.java
public class CustomAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
#Override
protected void append(ILoggingEvent eventObject) {
if(eventObject.getLevel() == Level.WARN){
{
KPLogHolder.addData(eventObject.getMessage);
}
}
}
A singleton java class to hold messages KPLogHolder.java
public class KPLogHolder {
private static List<String> holder;
public static List<String> getLog(){
if(holder== null){
return new ArrayList<String>();
}
return holder;
}
public static void addData(String item){
getLog().add(item);
}
}
And accessing the mssage.
public class KPTest{
#Test
public void testLog(){
LOG.warn("Warning!!!!");
for(String str: KPLogHolder.getLog()){
System.out.println(str);
}
}
}
Note I've not used synchronization you need to implement those as ArrayList is not synced. Not sure why this requirement but be careful. most cases these goes to wrong implementation, there situation were it will bring down your server. if its not implemented properly.
Do you want to access it when all classes have logged the statement in the .log file or you want to keep accessing as and when the logging is happening.
If you want to access all the logs from those 20 classes after the logging funcation then why not read the .log file later in your other java program to access all the logs.
But i guess you need an indicator in your log file to determine those statementa have been logged by those 20 classes. say for example some string in the consoleappender configuration
Not very difficult -
Either
Read log file
or
Overrider your log4j framework so instead of logging to file, it should log data to java objects (this is going to be very memory intensive).
Related
In continuing to this answer I wrote a unit test to verify that in case of error, the stack will be printed in the log file.
The tested method:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final Logger logger = LoggerFactory.getLogger(getClass());
public long getFq(String fi) {
try {
return calcSomeThing(fi.toLowerCase());
} catch (Exception e) {
logger.error("unable to calculate SomeThing. Error: "
, e);
return -1;
}
}
The unit test:
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.slf4j.LoggerFactory;
#Test
public void getFileQualifier() {
// get Logback Logger
Logger logger = (Logger) LoggerFactory.getLogger(QService.class);
// create and start a ListAppender
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.start();
// add the appender to the logger
// addAppender is outdated now
logger.addAppender(listAppender);
// call method under test
QService.getFq(null);
// JUnit assertions
List<ILoggingEvent> logsList = listAppender.list;
Assert.assertEquals("unable to calculate SomeThing. Error: ", logsList.get(0)
.getFormattedMessage());
Assert.assertEquals(Level.ERROR, logsList.get(0)
.getLevel());
Assert.assertEquals("java.lang.NullPointerException: null", logsList.get(1)
.getMessage());
Assert.assertEquals(Level.ERROR, logsList.get(1)
.getLevel());
Assert.assertThat("(QService.java", containsString(logsList.get(2)
.getMessage()));
Assert.assertEquals(Level.ERROR, logsList.get(2)
.getLevel());
}
Well, although I can see the stack is indeed printed in the log file, the unit test failed because of the
logsList contains only one item (the first printed line only [unable to calculate SomeThing. Error: ]).
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
Why does it happen and how it can be tested?
EDIT
Answers:
the answers (all are quotes from #Gavin's answer and comment, thank you):
On the first question (Why does it happen) the answer is:
It looks to me that exceptions is stored separately from the message in the log event
On the second question (how it can be tested) the answer is:
to find what you are looking for in the list of log events and can be expressed in a manner suitable to your domain, e.g checking the that a Throwable was logged, perhaps looking in org.apache.log4j.spi.LoggingEvent for appropriate methods
Finally, my code to verify it was:
Assert.assertEquals(logsList.get(0).getThrowableProxy().getClassName(), "java.lang.NullPointerException");
This is how I have captured log messages in the past, this is based on an old blog (not written by me) that no longer seems to be available.
It is quite old code written for Java 7/8 and Junit4.
I will try to keep this short :)
First you need an Appender it is probably best to extend the AppenderSkeleton, something like:
public class RuleAppender extends AppenderSkeleton {
private final List<LoggingEvent> loggingEvents = new ArrayList<>();
protected RuleAppender() {
super(true);
this.setThreshold(Level.TRACE);
setName("Rule Appender");
}
#Override
public void close() {
// No op in this case
}
#Override
public boolean requiresLayout() {
return false;
}
#Override
protected void append(final LoggingEvent event) {
loggingEvents.add(event);
}
public boolean hasEventsMeeting(LogExpectation logExpectation) {
// Use the LogExpectation to determine if the list of log events contains what you want.
}
#Override
public String toString() {
return "RuleAppender";
}
}
The LogExpectation is simply somewhere too define an expectation/criteria to match against the stored log events.
This is then wrapped up in a Junit Rule to make adding the the Appender to the Log4J a little easier, I did this by implementing TestRule and extending Statement, ensuring the first thing Statements evaluate method does is:
LogManager.getRootLogger().addAppender(ruleAppender);
LogManager.getRootLogger().setLevel(Level.ALL);
Notes:
This can be done without a JUnit rule, so long as the above two lines are executed before the test in order to ensure the appending is added to Log4J (The custom appender is still required).
I have not gone into the JUnit rule code, as we probably should be moving to JUnit 5 which doesnt support Rules and I have not gone into LogExpecation as this is simply away to find what you are looking for in the list of log events and can be expressed in a manner suitable to your domain, e.g checking the that a Throwable was logged, perhaps looking in org.apache.log4j.spi.LoggingEvent for appropriate methods
I'm searching for a concept to forward an object to subobjects.
Example:
I would like to create log files for several main Objects, that include sub objects (imagine a REST server that would log every single connection by ID).
Creating one big log file is simple ( redirect System.out.println, I already encapsulated that)
Example code:
class SubElementA{
public SubElementA(){
Debugger.debug("I am called, too");
}
}
Application.java
package com.dev4ag;
class Application{
private ElementA elA;
private String prefix;
public Application(String name){
this.elA = new ElementA();
this.prefix = name;
}
public void countUp(){
Debugger.debug(this.prefix+": I will now count up");
this.elA.doSomeStuff();
}
}
ElementA.java
package com.dev4ag;
class ElementA{
private int counter;
private SubElementA subElementA;
public void doSomeStuff(){
counter++;
Debugger.debug("Counter is: "+counter);
}
//Constructor
public ElementA(){
subElementA = new SubElementA();
this.counter = 0;
};
}
SubElementA.java
package com.dev4ag;
class SubElementA{
public SubElementA(){
Debugger.debug("I am called, too");
}
}
Debugger.java
package com.dev4ag;
public class Debugger {
public static void debug(String output){
//Just imagine we would write to a file here ;)
System.out.println(output);
}
}
(it was more easy to write system.out.println than to create a file, just imagine, Debugger.debug would write to a file).
Now I am thinking about a solution to create one Debug output target for each App. I could definitely change debug to not being static and create a debug object within Application.
But is there any way to use this object in the sub classes without forwarding the debug object either through Constructor or setter function, which would mean to have to add an object for the debugger to each class?
What would be the most beautiful solution for that?
Note that this solution might decrease performance a lot and it is pretty dirty way, but some loggers include such data.
But you can use Thread.currentThread().getStackTrace() to get stacktrace like in error and get class and method from where your method was called.
If you are using java9+ then you should probably use StackWalker API instead, especially that it have nice filters and other useful features.
So then you could guess app by class/method names on the stack.
We are using the Ereza CustomActivityOnCrash library to handle unexpected issues with our android app. It's activity offers some debug output which we enable in develop and test builds but disable on production builds. In addition we want to log that information about crash details (log entries, stack trace, error details) in background.
CustomActivityOnCrash offers to call event listeners which sounds convenient. I wanted to implement the interface in our logging service, however I do not understand how I can access the existing information in the crash activity that way. Which puzzles me, cause isn't that a natural expectation?
Basically I need to access public methods of an android activity object from an event listener method that does not get handed over anything. How can I access that activity in the handler method? And how can I get the intent of the activity leading to the crash which is the argument the crash activity expects in those public methods it offers to access the existing information it offers? The examples given in the libraries documentation and those I could find on the internet are trivial, they only dump example strings, not the actual data collected by the library.
This all sounds counter intuitive to me. Which is why I think I generally miss something here. Maybe someone has a short hint for me to bring me on track again. Thanks!
Here is the basics of the LogService implementation I imagine:
...
import cat.ereza.customactivityoncrash.CustomActivityOnCrash;
...
public class LogService
implements CustomActivityOnCrash.EventListener {
private static LogService instance;
...
public void log(LogLevel level, String message) {
....
}
public void logCrashDetails(String activityLog, String stackTrace, String errorDetails) {
String message = String.format(
"--- CRASH REPORT ---\n\n-- Activity log:\n%s\n\n- Stack trace:\n%s\n\nError details:\n%s",
activityLog,
stackTrace,
errorDetails);
log(LogLevel.ERROR, message);
}
....
// CustomActivityOnCrash EventListener interface
#Override
public void onLaunchErrorActivity() {
log(LogLevel.INFO, "COAC: app crashed");
logCrashDetails(
// CustomActivityOnCrash.getActivityLogFromIntent(...some intent...),
// CustomActivityOnCrash.getStackTraceFromIntent(...some intent...),
// CustomActivityOnCrash.getAllErrorDetailsFromIntent(...some intent...)
);
}
#Override
public void onRestartAppFromErrorActivity() {
log(LogLevel.INFO, "COAC: app restarted");
}
#Override
public void onCloseAppFromErrorActivity() {
log(LogLevel.INFO, "COAC: app closed");
}
}
I need to have one single file for the log, but I want to specify two different layouts for this file. I read somewhere that declaring two appenders which write the same file is not advised, so how could do? Thanks in advance.
This seems like a rather odd thing to do, as usually you would want all the lines in a single log file to be the same format, both for ease of eyeballing and if you want to do any automated processing of the log later on. But if you must then you are correct in saying that you shouldn't have two different appenders writing to the same file at the same time.
The solution would probably be to implement a custom Layout which can inspect the log event and then delegate to one of two (or more) other layouts to do the actual formatting
public class MultiLayout extends Layout {
private Layout layout1;
private Layout layout2;
public MultiLayout() {
layout1 = ....;
layout1.activateOptions();
layout2 = ....;
layout2.activateOptions();
}
public boolean ignoresThrowable() {
return layout1.ignoresThrowable();
}
public String format(LoggingEvent e) {
// choose the appropriate layout, e.g. based on logger name
if(e.getLoggerName().startsWith("com.example.")) {
return layout1.format(e);
} else {
return layout2.format(e);
}
}
}
I have a JTextArea always visible in my main app window (a Log if you like), and I want to use it to display activity going on in the system (like mock-debug output you'd do with System.out.println() in if conditions or whatever)
I mean high level things the user does, (like "successfully loaded file " or " written to disk", " completed" etc)
Thing is such messages can be generated anywhere in my system mainly in another package the classes of which deal with the data and computation, and they're unaware of the GUI.
Maybe save the messages to a temp file and the textarea "monitors" that file for changes, how can this be done?
The simplest way is to define a logger interface:
package com.example.logging;
public interface ActivityLogger {
void logAction(String message);
}
Then pass it to your non-GUI components so they don't get tied to a specific implementation:
public class FileLoader {
private ActivityLogger logger;
public FileLoader(ActivityLogger logger){
this.logger = logger;
}
public void loadFile(){
// load stuff from file
logger.logAction("File loaded successfully");
}
}
Now, making an implementation that writes to a text component is simple:
public class TextComponentLogger implements ActivityLogger{
private final JTextComponent target;
public TextComponentLogger(JTextComponent target) {
this.target = target;
}
public void logAction(final String message){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
target.setText(String.format("%s%s%n",
target.getText(),
message));
}
});
}
}
// Usage:
JTextArea logView = new JTextArea();
TextComponentLogger logger = new TextComponentLogger(logView);
FileLoader fileLoader = new FileLoader(logger);
fileLoader.loadFile();
You can of course also use a standard logging framework (java.util.logging, slf4j, log4j, etc) and write an appender that "writes" to a text component.
The design can be rather complicated. Maybe you can have a public access method like updateText() in the class where your TextArea would be. Then you create a kind of 'resource' or 'shared' class (just a plain class) that would be initialized together when your main() runs. When the class containing your TextArea is created, an instance would be placed into the 'shared' class (this shared class should be a singleton) and so all the other classes call this 'shared' class (maybe a method like updateTextArea()) and what it would do is call the class containing the TextArea via that instance and call the TextArea to update text.
The Message Console might be what you are looking for.
Java also has a "Logger" API.
You can use EventBus to decouple your GUI from the other parts of your application. (My blog has another introduction). You could do something as follows:
public class LogArea extends JTextArea {
public static final String LOG_TOPIC = "logarea_topic";
public LogArea() {
super();
// Read in the annotations, register self as a listener to the topic
AnnotationProcessor.process(this);
}
#EventTopicSubscriber(topic=LOG_TOPIC)
public void logEvent(String topic, String text) {
append(text + "\n");
}
}
public class DomainClass {
public void foo() {
// Send out a notification throughout the system to whichever components
// are registered to handle this topic.
EventBus.publish(LogArea.LOG_TOPIC, "some text you want to appear in the log area");
}
}
In a real system you'd probably want to move the topic declarations to another class so that one can use it without being tied to a specific implementation. E.g. you could have a Topics class that just contains the static string constants of the topics. Then you can have multiple classes that listen to those topics and process the messages (e.g. you could have a standard logging framework which writes out to a log file in addition to the jtextarea component).