I need to add a custom field to log entries, like this:
[KEYWORD] [Date/Time] [LEVEL] [Message]
The KEYWORD can be present or not in the log entry depending on a boolean value I pass to the log methods:
logger.info("message here", true) // Keyword is present
logger.error("message here", false) // keyword is not in log entry
Is this possible using the machinery provided by log4j, without changing its code?
p.s. I know I can add the custom field in the message part. I am wondering how hard it is if I insist on placing the field at the beginning of the log entry.
I think you can write your implementation of logger by extending log4j
Related
For a Spring application I want to add a custom field to the log.
Currently I use the default format but I want to add a custom field (category field) which should be present in all the logs:
W:Action D:2022-01-10 23:21:03.285 L:INFO C:c.h.l.LoggingDemoApplication F:StartupInfoLogger.java(61) Fn:logStarted T:main R: - Hello World
What are the best solution to add a custom field to the logback log?
What I studied until now are the following possible solutions:
Use marker. The disadvantage with this is that it's not scalable: if in future you need another custom field can't add another marker. Further based on some other posts the marker is best suited to mark special logs that need to be handle differently.
Use MDC.
Also using this it seems not the best solution because:
It keeps the context so if there are multiple log statements in the same function, before each logger.info() there should be MDC.put("category", "action")
The code becomes to verbose.
Create a custom convertor (link). Get the arguments from the ILoggingEvent, get argument of 0. If this is the same type as category enum, then use it. The call for this is like logger.info("Message here: {} {} {}", CatEnum.Action.getValue(), msg1, msg2, msg3).
Create some static method in which the final format is generated.
Pattern is similar to: <pattern>%m%n</pattern>
To log, something like this should be used: logger.info(customFormatter.fmtLog(CatEnum.Action.getValue(), msg)). The returned value of fmtLog should be all the info from default logging + the category field.
Are there any built in solutions to add a custom field?
I have created a wrapper for logging using log4j2. This is a customClass creating a object of :
logger = LogManager.getLogger(caller);
The problem is I am not getting correct class name in source when I am printing LogEvent.
For eg. If abc.java is logging log.info, I need to capture abc.java in source instead of customClass name.
You can't create the Logger just once in your wrapper object, because it is created with a specific class name (not the one you want).
So the quick and dirty way is to call LogManager.getLogger(caller).info(message) on every log call, and pass the calling class as the caller. Log4J keeps a hashtable of already created Loggers so it will not create a new Logger in case one already exists for the same caller class.
However, I don't think this is optimized enough for this use case, so I would probably roll my own caching in the wrapper object using a ConcurrentHashMap and computeIfAbsent( ), so that retrievals of existing Logger objects don't block others.
So my pattern would be:
Logger logger = loggersPerClass.get(callerClassName);
if (logger == null) {
logger = loggersPerClass.computeIfAbsent(callerClassName, k -> LogManager.getLogger(callerClass));
}
In my existing application "org.apache.log4j" API's have been used in java code.
Requirement :
I have to log some statement(say xyz) in log file in any case and should not dependent of log levels.For example : if my log level is error then also xyz should print, if my log level is debug then also xyz should print.
I cannot make log statement of xyz is debug because if i do this, other log statements apart from xyz will also start printing.
For this, I believe, I have to add some custom log level.Please help how to do it and how to set its level ordering so that in any case it should print.
Thanks in advance.
Best Regards
What you could do is create a different Logger for those statements (you are not restricted to use classes names when defining a logger)
// Standard logger
private static Logger log = Logger.getLogger(MyClass.class)
// XYZ logger
private static Logger logXYZ = Logger.getLogger("logs.xyz");
You can access the same logger from several class, you just have to pass the same label.
Then, in the configuration file, you can define a different log level for that category, and even output these logs in a different appender (different file, processing, etc.)
You could "hijack" the protected method Logger#forcedLog() to always print to the log.
You must place the hijacker class in the same package as Logger.
package org.apache.log4j;
/**
* #author maba, 2012-08-23
*/
public class LogOverride {
public static void print(Logger logger, String message) {
logger.forcedLog(logger.getName(), Priority.INFO, message, null);
}
}
And from your calling code
log.setLevel(Level.OFF); // Make sure logging is turned off
log.info("Normal logging"); // Will not be seen anywhere
LogOverride.print(log, "Overriding logger"); // Will still make it to your appender
This is what the log4j FAQ says about custom levels:
How do I add a custom level?
It is possible, but rarely appropriate. The request is commonly for a level named something like "audit" that doesn't obviously fit in the progression "trace", "debug", "info", "warn", "error" and "fatal". In that case, the request for a level is really a request for a mechanism to specify a different audience. The appropriate mechanism is to use a distinct logger name (or tree) for "audit" related messages.
So if you want to go with that suggestion then you should look at the answer from SJuan76.
If you do decide to go with the idea of creating a custom, you would need to create a subclass of Level to do this, because the Level constructor is protected.
/**
* Instantiate a Level object.
*/
protected Level(int level, String levelStr, int syslogEquivalent) {
super(level, levelStr, syslogEquivalent);
}
It looks like you should then chain to the Level constructor, passing it a suitable level value. Note that the larger the level number the higher the priority is. So for a Level that won't be blocked at any of the existing named levels, you want a value that is greater than Priority.FATAL_INT which is 50000.
(However, I'm not convinced that this is the right approach. For a start, you probably won't be able to refer to your custom level by name in a logging config file.)
I'm using java.util.logging.Logger as the logging engine for my application. Each class uses it's own logger, i.e., each class has:
private final Logger logger = Logger.getLogger(this.getClass().getName());
I want to set a logging level for all my classes, and be able to change it (i.e., have the setting in one place). Is there a way to do this, other that using a global Level variable and manually set each logger to it?
One easy way is to use a logging properties file, by including this VM argument:
-Djava.util.logging.config.file="logging.properties"
where "logging.properties" is the path to a file containing logging configuration. For relative paths, the working directory of the process is significant.
In that file, include a line like this:
.level= INFO
This sets the global level, which can be overridden for specific handlers and loggers. For example, a specific logger's level can be overridden like this:
com.xyz.foo.level = SEVERE
You can get a template for a logging properties file from jre6\lib\logging.properties.
As Andy answered, in most cases you should use the property file and the VM argument, thus its independent from your code.
But if you want to go programatically for some reason (I myself had a good reason in one case) you can access the Handlers like this too:
Logger rootLogger = LogManager.getLogManager().getLogger("");
rootLogger.setLevel(Level.INFO);
for (Handler h : rootLogger.getHandlers()) {
h.setLevel(Level.INFO);
}
EDIT I added the setLevel to the root logger as searchengine27 pointed out in in his answer.
The Handlers are File or Console Handlers that you setup via the properties or programatically too.
Or change filters like this:
Logger rootLogger = LogManager.getLogManager().getLogger("");
rootLogger.setFilter(new Filter() {
#Override
public boolean isLoggable(LogRecord record) {
return "something".equals(record.getLoggerName());
}
});
So I don't entirely like all of the answers here, so I'm going to chime in.
Config file use
You're seeing a lot of answers in here telling you to use the config file because it is best practice. I want to explain better how to do this programatically, but before I do, I want to say that I can see where they are coming from, and in the mood of being objective, I will enlighten you a bit (especially because nobody says why its bad practice). I actually want to share what somebody said in a separate StackOverflow answer that is in relation to setting the logger level programatically (Why are the Level.FINE logging messages not showing?):
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.
On that note, I think Andy Thomas has a goodish answer related to not doing it non-programatically.
Programatically setting the Level
That being said, I want to go into a bit more detail about doing it programatically, because I think it has its uses.
Imagine a scenario where you are writing something with a command line interface and you have an option to specify the verbosity of your execution, or even where it goes to (as in dynamic log files). I may be mistaken, but you would probably not want to do this statically in a .conf file. Especially so if you don't want to make your userbase responsible for setting these things (for whatever arbitrary reason) in the config file. This comes at the expense of the above quote, however. Here is an example of how you can do it programatically, keeping all of the existing handlers to whatever level they are at already, and only FileHandler's assume the new level:
public static void setDebugLevel(Level newLvl) {
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler[] handlers = rootLogger.getHandlers();
rootLogger.setLevel(newLvl);
for (Handler h : handlers) {
if(h instanceof FileHandler)
h.setLevel(newLvl);
}
}
I wanted to expand on this, over the accepted answer for one reason in particular. When doing it programatically, you just want to make sure that you set the level for the logger and the handler(s). The way it works, is it will check to see if the request is too low for the logger, and if it is it will discard it. Then the handler(s) have the same check, so you will want to make sure both the loggers and handlers are set to the level you want it.
One-liner Java 8 approach to morja's answer:
Arrays.stream(LogManager.getLogManager().getLogger("").getHandlers()).forEach(h -> h.setLevel(Level.INFO));
JUL(java.util.logging) is java default logger within jvm.
Java Logging Technology--see Overview
So you may find that there is a default config file already in C:\Program Files\Java\jre1.8.0_221\lib\logging.properties
I recommend that you create a new config file under your project to override the default setting, do as following:
config file name, logging.properties, all log level can be found in Class Level
handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format= [%1$tF %1$tT] [%4$-7s] %5$s %n
# your specific logger level
com.xyz.foo.level = SEVERE
Then add jvm option to enable the config
java -Djava.util.logging.config.file="logging.properties" -Duser.country=CN -Duser.language=en
user.country and user.language may have an effect on log message localization
I'm using java.util.logging.Logger logging in my program. How do I enable FINE logging for a single class, while setting it to WARNING for every other class?
I'd prefer to do this programatically in my main() method rather than needing to set up additional properties files.
I know the OP has asked to do this programatically but here's an example of how to do it in the properties file too.
Caveat: I thought it was worthy of inclusion as the header doesn't indicate programatically and many developers will want to manage it through the logging.properties. Also there isn't really a lot on-line about this, it can be confusing and is slightly different to, say log4j
The root logging level is indicated by the .level config. This dictates which events are by default to be captured and "distributed for" logging. The root logging level is the level used by the "root logger" in the logging hierarchy. See this onjava article for more info on the logging hierarchy.
Below, the root log level is set to WARNING so will ordinarily capture only WARNING events. This is inherited by all child loggers in the hierarchy, unless you configure otherwise (later):
.level=WARNING
This root-logging level only indicates what is captured, not what is "distributed". How a captured event (message) is distributed is down to the handlers associated with the logger. For instance, a ConsoleHandler will output the event to the console. For instance:
java.util.logging.ConsoleHandler.level = WARNING
This ConsoleHandler.level indicates the level for which this handler should distribute - or print - the message. So, if a FINE message is received with the above config then this handler will not print it. It will print any messages with a WARNING log level or above though.
Setting to ALL will ensure that the ConsoleHandler will print all messages to the console (an we also need to configure the root level to ensure all are captured):
.level=ALL
java.util.logging.ConsoleHandler.level = ALL
However, this would create a lot of noise which we also don't want. So, to reduce the FINE-level events to those classes we're interested in, we change the logging level of those specific loggers only:
com.level = WARNING
com.mypackage.MyClass1.level = FINE
com.mypackage.MyClass2.level = FINE
com.mypackage.mysubpackage.MyClass3.level = FINE
Note that in the above, I've explicitly set the level for the "com" logger to WARNING.
Logger log = Logger.getLogger(this.getClass().getName()).setLevel(Level.FINE);
If you do not want to have a logger defined for every single class in question but rather want to share loggers between classes, you can alternatively implement your own java.util.logging.Handler that has its own way of filtering for class names using the information provided by LogRecord.getSourceClassName().
I believe that you can set your log level for your Handler and your specific class Logger to FINE, while keeping all the other Loggers for the rest of your code base to WARNING should do the trick. Both the Logger and the Handler need to pass the level filter for a message to be logged.
Well, here's a method I added to my mail class that's actually working. I would still welcome any improvements from others.
private static void setupLogging() {
// To enable FINE logging in a single class, apparently this bewildering
// maze of statements is required.
Logger.getLogger("").setLevel(Level.FINE);
for (Handler handler : Logger.getLogger("").getHandlers()) {
handler.setLevel(Level.FINE);
}
MyOtherClass.logger.setLevel(Level.FINE);
Logger.getLogger("").setLevel(Level.WARNING);
}