How to print customized prefix in log4j - java

By default, log4j will print the class name as the prefix of log. Now my case is that I have multiple instances of class A in one JVM, and there's one log in this class. I'd like the log as following to allow me know which instance is printing this line of log ? How can I achieve this ?
2014-09-09 13:07:08,512 INFO com.myexample.A(id1)
2014-09-09 13:07:08,514 INFO com.myexample.A(id2)

I think you can not do it by settings in log4j.properties file. See possible variants here.
But you can solve the problem like this:
class A {
Logger LOGGER;
A(String id) {
LOGGER = Logger.getLogger(getClass() + "(" + id + ")");
}
void myMethod() {
LOGGER.info("Hello!");
}
}

Easiest way is to add desired ID to your log message:
if (logger.isDebugEnabled()) {
int id = this.hashCode(); // Invoke ID getter method
logger.debug( String.format("[ %d ] My log message with ID", id) );
}

Related

Logging with optional parameters

I have method where I want to add specific logging:
#Slf4j
#Service
public class SomethingService {
public void doSomething(Something data, String comment, Integer limit) {
Long id = saveSomethingToDatabase(data, comment);
boolean sentNotification = doSomething(id);
// ...
// Log what you done.
// Variables that always have important data: data.getName(), id
// Variables that are optional: sentNotification, comment, limit
// (optional means they aren't mandatory, rarely contains essential data, often null, false or empty string).
}
}
I can simply log all:
log.info("Done something '{}' and saved (id {}, sentNotification={}) with comment '{}' and limit {}",
something.getName(), id, sentNotification, comment, limit);
// Done something 'Name of data' and saved (id 23, sentNotification=true) with comment 'Comment about something' and limit 2
But most of the time most of the parameters are irrelevant. With the above I get logs like:
// Done something 'Name of data' and saved (id 23, sentNotification=false) with comment 'null' and limit null
That makes logs hard to read, long and unnecessarily complicated (in most cases other parameters aren't present).
I want to handle all cases with preserving only essential data. Examples:
// Done something 'Name of data' and saved (id 23)
// Done something 'Name of data' and saved (id 23) with comment 'Comment about something'
// Done something 'Name of data' and saved (id 23) with limit 2
// Done something 'Name of data' and saved (id 23) with comment 'Comment about something' and limit 2
// Done something 'Name of data' and saved (id 23, sent notification)
// Done something 'Name of data' and saved (id 23, sent notification) with limit 2
// Done something 'Name of data' and saved (id 23, sent notification) with comment 'Comment about something'
// Done something 'Name of data' and saved (id 23, sent notification) with comment 'Comment about something' and limit 2
I can code it by hand:
String notificationMessage = sentNotification ? ", sent notification" : "";
String commentMessage = comment != null ? String.format(" with comment '%s'", comment) : "";
String limitMessage = "";
if (limit != null) {
limitMessage = String.format("limit %s", limit);
limitMessage = comment != null ? String.format(" and %s", limitMessage) : String.format(" with %s", limitMessage);
}
log.info("Done something '{}' and saved (id {}{}){}{}",
something.getName(), id, notificationMessage, commentMessage, limitMessage);
But it's hard to write, hard to read, complicated and causes errors.
I would like something like specify part of logs.
Example pseudocode:
log.info("Done something '{}' and saved (id {} $notification) $parameters",
something.getName(), id,
$notification: sentNotification ? "sent notification" : "",
$parameters: [comment, limit]);
It should supports optional parameters, replace boolean/condition with given string, supports separating spaces, commas and words with and and.
Maybe are there existing library for this? Or maybe is there at least a simpler way for coding this?
If not, it remains for me nothing else to write my own library for messages to logging. Additionally, this kind of library will provide that all logs would be consistent.
If you don't see a problem with three optional parameters, just imagine there are more (and you can't always pack them into a class - another class layer only for parameter logging cause even more complications).
At the end, I know I can log each action separately. But with this I get many more logs and I won't have the most important information in one place. Other logs are in the debug level, not info.
both of these are possible. You can either:
register a component with the Logger to do the work for you
write a wrapper class for your logger to use
I will demonstrate both and explain why I think the second is the better choice. Let's start with that:
Instead of having the Logger own the knowledge of how to format your specific properties, let your code own this responsibility.
For example, rather than logging each parameter, collect them and define their logging separately. See this code:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingExample {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingExample.class);
public static void main(String[] args) {
LogObject o = new LogObject();
LOGGER.info("{}", o);
o.first = "hello";
LOGGER.info("{}", o);
o.second = "World";
LOGGER.info("{}", o);
o.last = "And finally";
LOGGER.info("{}", o);
}
public static class LogObject {
String first;
String second;
String last;
#Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Log Object: ");
if (first != null) {
buffer.append("First: " + first + " ");
}
if (second != null) {
buffer.append("Second: " + second + " ");
}
if (last != null) {
buffer.append("Second: " + last + " ");
}
return buffer.toString();
}
}
}
We define LogObject as a container and this container implements toString. All Loggers will call toString() on their objects, that is how they figure out what they should print (unless special formatters applied etc).
With this, the log statements print:
11:04:12.465 [main] INFO LoggingExample - Log Object:
11:04:12.467 [main] INFO LoggingExample - Log Object: First: hello
11:04:12.467 [main] INFO LoggingExample - Log Object: First: hello Second: World
11:04:12.467 [main] INFO LoggingExample - Log Object: First: hello Second: World Second: And finally
Advantages:
this works with any Logger. You won't have to implement specifics depending on what you want to use
the knowledge is encapsulated in 1 object that can be easily tested. This should mitigate the error prone formatting problem you stated.
no need for a complex formatter library or implementation
It will make the logging look much nicer and compact in the end. log.info("{}", object);
Disadvantage:
You are required to write the Bean.
Now the same can be achieved using for example a custom Layout. I am using logback, so this is an example for logback.
We may define a Layout that owns the knowledge of what to do with your custom formatting instructions.
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.LayoutBase;
public class LoggingExample2 {
private static final Logger CUSTOM_LOGGER = createLoggerFor("test");
public static void main(String[] args) {
LogObject o = new LogObject();
CUSTOM_LOGGER.info("{}", o);
o.first = "hello";
CUSTOM_LOGGER.info("{}", o);
o.second = "World";
CUSTOM_LOGGER.info("{}", o);
o.last = "And finally";
CUSTOM_LOGGER.info("{}", o);
}
public static class LogObject {
String first;
String second;
String last;
#Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Log Object: ");
if (first != null) {
buffer.append("First: " + first + " ");
}
if (second != null) {
buffer.append("Second: " + second + " ");
}
if (last != null) {
buffer.append("Second: " + last + " ");
}
return buffer.toString();
}
}
public static class ModifyLogLayout extends LayoutBase<ILoggingEvent> {
#Override
public String doLayout(ILoggingEvent event) {
String formattedMessage = event.getFormattedMessage() + "\n";
Object[] args = event.getArgumentArray();
return String.format(formattedMessage, args);
}
}
private static Logger createLoggerFor(String string) {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setPattern("%date %level [%thread] %logger{10} [%file:%line] %msg%n");
ple.setContext(lc);
ple.start();
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<ILoggingEvent>();
consoleAppender.setEncoder(ple);
consoleAppender.setLayout(new ModifyLogLayout());
consoleAppender.setContext(lc);
consoleAppender.start();
Logger logger = (Logger) LoggerFactory.getLogger(string);
logger.addAppender(consoleAppender);
logger.setLevel(Level.DEBUG);
logger.setAdditive(false); /* set to true if root should log too */
return logger;
}
}
I borrowed the Logger instatiation from: Programmatically configure LogBack appender
Note that I have not found a library that can parse the complex expressions that you have listed. I think you may have to write your own implementation.
In my example, i only illustrate how to intercept and modify the message based on the arguments.
Why I would not recommend this unless it is really needed:
the implementation is specific to logback
writing correct formatting is hard ... it will produce more errors than creating a custom object to format
It is harder to test because you literally have unlimited objects that may pass through this (and formatting). Your code must be resilient to this now, and in the future since any developer may add the weirdest things at any time.
The last (unasked) answer:
Why don't you use a json encoder? And then use something like logstash to aggregate (or cloudlwatch, or anything else).
This should solve all your problems.
This is what I have done in the past:
Define 1 bean that you like to log "differently". I call it metadata. This bean can be i.e.
public class MetaHolder {
// map holding key/values
}
This basically just stores all your variables with a key. It allows you to effectively search on these keys, sink them into databases, etc. etc.
In your log, you simply do:
var meta = // create class
meta.put("comment", comment);
// put other properties here
log.info("formatted string", formattedArguments, meta); // meta is always the last arg
In the Layout this can then be converted quite nicely. Because you are no longer logging "human language", there are no "withs" and "in" to replace. Your log will simply be:
{
"time" : "...",
"message" : "...",
"meta" : {
"comment" : "this is a comment"
// no other variables set, so this was it
}
}
And one last (last) one in just pure java, if you wanted that. You could write:
public static void main(String[] args) {
String comment = null;
String limit = "test";
String id = "id";
LOGGER.info(
"{} {} {}",
Optional.ofNullable(comment).map(s -> "The comment " + s).orElse(""),
Optional.ofNullable(limit).map(s -> "The Limit " + s).orElse(""),
Optional.ofNullable(id).map(s -> "The id " + s).orElse(""));
}
Which effectively moves the conditional logic you want in your formatting into Java's Optional.
I find this also is hard to read and test and would still recommend the first solution

log4j => add caller file and line number to log

I use following pattern [%file:%line] %msg%n to output file + number to my log.
I as well use a simple wrapper class, that I call L.java. Now it does not make sense to output [L.java:74] Message... to my log. Instead, I would like to output the calling file name and line number...
Is that somehow possible with log4j?
The PatternLayout are slighty different between log4j 1.x and 2.x, and I do not know which version are you using, but I think you can't achieve this by configuration in neither versions.
You can achieve that programmatically (but this is going to affect your performance), I think in your L.java method you will have to use a method like:
private Logger logger = getYourLoggerAsYouAreCurrentlyDoing();
public enum LogLevel { INFO,DEBUG, ERROR, ETC }
void log(String msg, LogLevel level) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
String callerClass = stackTraceElements[1].getClassName();
String callerLine = "" + stackTraceElements[1].getLineNumber();
String msg = callerClass + ":" + callerLine + "-" + msg;
switch(LogLevel) {
case INFO: logger.info(msg); break;
case DEBUG: logger.debug(msg); break;
//etc.
}
}
And in case another method with the Throwable argument to log stacktraces.

How can I get spock to execute a different method at runtime using an Annotation Extension?

First, in case there is a simpler way to solve this problem, here is an outline of what I am trying to accomplish. I want to Annotate my test methods with a KnownIssue annotation (extending AbstractAnnotationDrivenExtension) that takes a defect ID as a parameter and checks the status of the defect before executing the tests. If the defect is fixed, it will continue execution, if it is not fixed I want it to ignore the test, but if it is closed or deleted, I want to induce a test failure with logging stating that the test should be removed or updated and the annotation removed since the defect is now closed or deleted.
I have everything working up until inducing a test failure. What I have tried that doesn't work:
Throwing an exception in the visitFeatureAnnotation method, which causes a failure which causes all tests thereafter not to execute.
Creating a class that extends Spec and including a test method that logs a message and fails, then tried to use feature.featureMethod.setReflection() to set the method to execute to the other method. In this case, I get a java.lang.IllegalArgumentException : object is not an instance of declaring class
I then tried using ExpandoMetaClass to add a method directly to the declaringClass, and point feature.featureMethod.setReflection to point to it, but I still get the same IllegalArgumentException.
Here is what I have inside of my visitFeatureAnnotation method for my latest attempt:
def myMetaClass = feature.getFeatureMethod().getReflection().declaringClass.metaClass
myMetaClass.KnownIssueMethod = { -> return false }
feature.featureMethod.setReflection(myMetaClass.methods[0].getDoCall().getCachedMethod());
Any other ideas on how I could accomplish this, and either induce a test failure, or replace the method with another that will fail?
Ok... I finally came up with a solution. Here is what I got working. Within the visitFeatureAnnotation method I add a CauseFailureInterceptor that I created.
Here is the full source in case anyone is interested, just requires you to extend the KnownIssueExtension and implement the abstract method getDefectStatus:
public abstract class KnownIssueExtension extends AbstractAnnotationDrivenExtension<KnownIssue> {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(KnownIssueExtension.class)
public void visitFeatureAnnotation(KnownIssue knownIssue, FeatureInfo feature) {
DefectStatus status = null
try{
status = getDefectStatus(knownIssue.value())
} catch(Exception ex){
LOGGER.warn("Unable to determine defect status for defect ID '{}', test case {}", knownIssue.value(), feature.getName())
// If we can't get info from Defect repository, just skip it, it should not cause failures or cause us not to execute tests.
}
if (status != null){
if(!status.open && !status.fixed){
LOGGER.error("Defect with ID '{}' and title '{}' is no longer in an open status and is not fixed, for test case '{}'. Update or remove test case.", knownIssue.value(), status.defectTitle, feature.getName())
feature.addInterceptor(new CauseFailureInterceptor("Defect with ID '" + knownIssue.value() + "' and title '" + status.defectTitle + "' is no longer in an open status and is not fixed, for test case '" + feature.getName() + "'. Update or remove test case."))
}else if (status.open && !status.fixed){
LOGGER.warn("Defect with ID '{}' and title '{}' is still open and has not been fixed. Not executing test '{}'", knownIssue.value(), status.defectTitle, feature.getName())
feature.setSkipped(true)
}else if (!status.open && status.fixed){
LOGGER.error("Defect with ID '{}' and title '{}' has been fixed and closed. Remove KnownIssue annotation from test '{}'.", knownIssue.value(), status.defectTitle, feature.getName())
feature.addInterceptor(new CauseFailureInterceptor("Defect with ID '" + knownIssue.value() + "' and title '" + status.defectTitle + "' has been fixed and closed. Remove KnownIssue annotation from test '" + feature.getName() + "'."))
}else { // status.open && status.fixed
LOGGER.warn("Defect with ID '{}' and title '{}' has recently been fixed. Remove KnownIssue annotation from test '{}'", knownIssue.value(), status.defectTitle, feature.getName())
}
}
}
public abstract DefectStatus getDefectStatus(String defectId)
}
public class CauseFailureInterceptor extends AbstractMethodInterceptor{
public String failureReason
public CauseFailureInterceptor(String failureReason = ""){
this.failureReason = failureReason
}
#Override
public void interceptFeatureExecution(IMethodInvocation invocation) throws Throwable {
throw new Exception(failureReason)
}
}
class DefectStatus{
boolean open
boolean fixed
String defectTitle
}

java logging writing multiple times for each thread in polling system

threading polling system in java. and i have included logging for each thread separately. but the instance of fileappender not getting closed with thread. so in the next poll when thread comes there are two instance of fileappender so it is logging two times and again in next poll its logging three time and so on...
here is my code
public void run()
{
String parent = "Autobrs", name = "Pool";
name = name + poolId;
String loggerName = parent + "." + name;
Logger log = Logger.getLogger(loggerName);
DailyRollingFileAppender fileApp = new DailyRollingFileAppender();
fileApp.setName("Autobrs." + loggerName + "_FileAppender");
fileApp.setFile("c:\\java\\"+ name+".log");
fileApp.setLayout(new PatternLayout("%d{yyyy-MM-dd HH:mm:ss} (%0F:%L) %3x - %m%n"));
fileApp.setThreshold(Level.toLevel("debug"));
fileApp.setAppend(true);
fileApp.activateOptions();
log.addAppender(fileApp);
}
and i don't know how to handle this.
pls help..
Since FileAppender inherits close() from Writeappender as stated here, use
fileApp.close()
String parent = "Autobrs", name = "Pool";
name = name + poolId;
String loggerName = parent + "." + name;
Logger log = Logger.getLogger(loggerName);
You might have memory leak here. Logger.getLogger() will keep a reference to all loggers it created.
Best practice would be to have one file appender configured by a main thread and each run() can log the poolId (or whatever) in its message.
Have multiple threads writing to a common logger & appender.

Remove timestamp from GWT logger

I'd like to remove the timestamp from GWT logging output on the console.
What's the simplest way to do this? Ideally, in the .gwt.xml configuration would be great.
Here is an example output currently with the timestamp:
Wed Mar 21 08:23:57 EDT 2012 Job
FINE: Job: 'updater': end
EDIT: I am only interested in the client side.
This logging capability is not really configurable. You need to write your own formatter:
call this at the beginning of onModuleLoad():
Handler[] handlers = Logger.getLogger("").getHandlers();
for(Handler h : handlers){
h.setFormatter(new MyCustomLogFormatter());
}
And here is an example of a formatter:
public class MyCustomLogFormatter extends TextLogFormatter{
private static DateTimeFormat timeFormat = DateTimeFormat.getFormat("HH:mm:ss.SSS");
public MyCustomLogFormatter() {
super(true);
}
#Override
public String format(LogRecord event) {
StringBuilder message = new StringBuilder();
message.append(getRecordInfo(event, " "));
message.append(event.getMessage());
message.append(getStackTraceAsString(event.getThrown(), "\n", "\t"));
return message.toString();
}
#Override
protected String getRecordInfo(LogRecord event, String newline) {
Date date = new Date(event.getMillis());
StringBuilder s = new StringBuilder();
s.append(timeFormat.format(date));
s.append(" GWT ");
s.append(event.getLevel().getName());
String loggerName = event.getLoggerName();
String[] split = loggerName.split("\\.");
s.append(" ");
s.append(split[split.length-1]);
s.append(newline);
s.append(": ");
return s.toString();
}
}
More: http://code.google.com/webtoolkit/doc/latest/DevGuideLogging.html
The accepted answer shows how to customize GWT log messages. The example is longer than necessary, though. In case anyone wants to just remove the timestamp (the original question), here is a shorter snippet:
Handler[] handlers = Logger.getLogger("").getHandlers();
for (Handler h : handlers) {
h.setFormatter(new TextLogFormatter(false) {
#Override
public String format(LogRecord event) {
return event.getLoggerName() + ": " +
event.getLevel().getName() + ": " +
event.getMessage();
}
});
}
Watch out for the "gotcha" of doing this in the constructor of a singleton dependency-injected class---you want to make sure GWT has had the chance to actually add handlers to the logger first.
It uses the same pattern config as log4j, see here: http://code.google.com/p/gwt-log/wiki/GettingStarted#Control_the_format_of_your_log_messages
In the gwt-log wiki it says:
Server side logging automatically detects Apache log4j, falling back
to JDK 1.4 logging
As there is a "FINE" log level in your post, it should be the second case. Either include Log4J in your classpath or check "logging.properties" from your current jdr/jre-conf directory.

Categories