I am trying to create a Custom Logger.
My code is given below and the Properties file is also given below.
It works in the sense that I get log messages and details in the log file in the format I want.
However, all logs are logged with same ClassName - MyLogger. I want the 'name' I am passing in the constructor to be logged as ClassName instead of the MyLogger ClassName.
Appreciate any help in that regard.
Java Code:
public class MyLogger {
private static Logger log;
public static MyLogger getLogger(String name) {
return new MyLogger(name);
}
private MyLogger(String name) {
log = Logger.getLogger(name);
try {
LogManager.getLogManager().readConfiguration(new FileInputStream("./logging.properties"));
} catch (FileNotFoundException ex) {
Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException | SecurityException ex) {
Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void log(Level LogLevel, String logMessage, Object param1) {
log.log(LogLevel, logMessage, param1);
}
public void log(Level LogLevel, String logMessage, Throwable e) {
log.log(LogLevel, logMessage, e);
}
public void log(Level LogLevel, String logMessage) {
log.log(LogLevel, logMessage);
}
}
logging.properties File content:
handlers = java.util.logging.FileHandler
java.util.logging.FileHandler.level = ALL
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format = %1$tF %1$tT %4$s %2$s %5$s%6$s%n
java.util.logging.FileHandler.limit = 1048576
java.util.logging.FileHandler.count = 5
java.util.logging.FileHandler.append = true
java.util.logging.FileHandler.pattern = ./logs/log-%u-%g.log
--
The documentation for SimpleFormatter.format explains that the source parameter (%2$s in the format string) is "a string representing the caller, if available; otherwise, the logger's name". This looks at the stack to determine the direct caller, which in your case will always be the MyLogger class.
To use the logger name instead, use %3$s in the java.util.logging.SimpleFormatter.format configuration rather than %2$s.
Per API docs you can use the log precise methods:
There are a set of "logp" methods (for "log precise") that are like the "log" methods, but also take an explicit source class name and method name.
Combine that with the inferCaller method to make your bridge look transparent:
public void log(Level LogLevel, String logMessage) {
StackTraceElement stack = inferCaller();
log.logp(stack.getClassName(), stack.getMethodName(), LogLevel, logMessage);
}
private boolean isPrintImplFrame(String cname) {
return MyLogger.class.getName().equals(cname);
}
private StackTraceElement inferCaller() {
StackTraceElement stack[] = (new Throwable()).getStackTrace();
int ix = 0;
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (isPrintImplFrame(cname)) {
break;
}
ix++;
}
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (!isPrintImplFrame(cname)) {
return frame;
}
ix++;
}
return new StackTraceElement(MyLogger.class.getName(), "log",
MyLogger.class.getName(), -1);
}
Using this method your code will find the stack frame that is calling your bridge logger that is not the bridge logger itself.
Related
I have written a custom logger with its own rolling file appender. I am logging an exception using one of the log messages (e.g., info(), debug(), etc.), I am excepting that it would print the entire stacktrace of the exception, but it is printing only the message.
public class Logger {
private static ConcurrentHashMap<String, Logger> loggerMap = new ConcurrentHashMap<String, Logger>();
private static ConcurrentHashMap<String, FileAppender> appenderMap = new ConcurrentHashMap<String, FileAppender>();
private static LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
public static org.slf4j.Logger getLoggerWithRollingFileAppender(String appenderName,
String loggerName,
Level logLevel) {
RollingFileAppender rollingFileAppender;
// Check if the appender is already created.
if (! appenderMap.containsKey(appenderName)) {
LayoutWrappingEncoder jsonEncoder = new LayoutWrappingEncoder();
JsonFormatter jsonFormatter = new JacksonJsonFormatter();
((JacksonJsonFormatter) jsonFormatter).setPrettyPrint(false);
JsonLayout jsonLayout = new JsonLayout();
jsonLayout.setJsonFormatter(jsonFormatter);
jsonLayout.setTimestampFormat("yyyy-MM-dd HH:mm:ss.SSS");
jsonLayout.setContext(loggerContext);
jsonLayout.setAppendLineSeparator(true);
jsonLayout.setIncludeException(true);
jsonLayout.setIncludeMDC(true);
jsonEncoder.setLayout(jsonLayout);
jsonEncoder.setContext(loggerContext);
jsonEncoder.start();
rollingFileAppender = new RollingFileAppender();
rollingFileAppender.setContext(loggerContext);
rollingFileAppender.setName(appenderName);
rollingFileAppender.setEncoder(jsonEncoder);
rollingFileAppender.setAppend(true);
SizeAndTimeBasedRollingPolicy sizeAndTimeBasedRollingPolicy =
new SizeAndTimeBasedRollingPolicy();
sizeAndTimeBasedRollingPolicy.setContext(loggerContext);
sizeAndTimeBasedRollingPolicy.setParent(rollingFileAppender);
sizeAndTimeBasedRollingPolicy.setFileNamePattern(logBaseDir +
File.separator +
appenderName + "-" +
applnName + "-%d{yyyy-MM-dd}.%i.log");
sizeAndTimeBasedRollingPolicy.setMaxHistory(maxLogHistory);
sizeAndTimeBasedRollingPolicy.setMaxFileSize(FileSize.valueOf(maxLogFileSize));
sizeAndTimeBasedRollingPolicy.setTotalSizeCap(FileSize.valueOf(totalLogSizeCap));
sizeAndTimeBasedRollingPolicy.start();
rollingFileAppender.setRollingPolicy(sizeAndTimeBasedRollingPolicy);
rollingFileAppender.start();
appenderMap.put(appenderName, rollingFileAppender);
} else {
rollingFileAppender = (RollingFileAppender) appenderMap.get(appenderName);
}
// Return the logger
org.slf4j.Logger slf4jLogger = getLogger(loggerName);
Logger logger = (ch.qos.logback.classic.Logger) slf4jLogger;
logger.setAdditive(false);
logger.setLevel(logLevel);
logger.addAppender(rollingFileAppender);
return slf4jLogger;
}
}
#Test
void testExceptionWithLoggerWithRollingFileAppender() {
org.slf4j.Logger slf4jLogger = Logger.getLoggerWithRollingFileAppender
("testAppender",
"com.test7",
Level.DEBUG);
try {
FileInputStream fis=new FileInputStream("dummyfile.txt");
} catch (FileNotFoundException e) {
slf4jLogger.info("Exception is", e);
}
Logger logger = (ch.qos.logback.classic.Logger) slf4jLogger;
logger.debug("This is a log message");
try {
FileInputStream fis=new FileInputStream("dummyfile.txt");
} catch (FileNotFoundException e) {
logger.info("Exception is", e);
}
}
But the log file do not contain the complete stack trace of the exception. This is not useful from debugging point of view. What could be missing here?
{"timestamp":"2021-03-09 18:03:21.443","level":"INFO","thread":"main","logger":"com.test7","message":"Exception is","context":"default","exception":"java.io.FileNotFoundException: linessssss.txt (No such file or directory)\n"}
{"timestamp":"2021-03-09 18:03:21.476","level":"DEBUG","thread":"main","logger":"com.test7","message":"This is a log message","context":"default"}
{"timestamp":"2021-03-09 18:03:21.477","level":"INFO","thread":"main","logger":"com.test7","message":"Exception is","context":"default","exception":"java.io.FileNotFoundException: linessssss.txt (No such file or directory)\n"}
Looking at the source for JsonLayout it uses a ThrowableProxyConverter to control what ends up in the JSON - so even if you call setIncludeException (as recommended by responses to other similar questions) what it does will depend on which converter is selected (didn't spend long enough to see how). You probably want something like the RootCauseFirstThrowableProxyConverter or something like it being involved.
I found the fix for this. Added a CustomThrowableProxyConverter and handled the stack trace processing through it.
public class CustomThrowableProxyConverter extends ThrowableHandlingConverter {
public CustomThrowableProxyConverter() {
}
#Override
public String convert(ILoggingEvent event) {
StringBuilder sb = new StringBuilder();
IThrowableProxy itp = event.getThrowableProxy();
if (itp instanceof ThrowableProxy) {
ThrowableProxy tp = (ThrowableProxy)itp;
sb.append(tp.getClassName() + ": " + tp.getMessage());
for (StackTraceElementProxy element : tp.getStackTraceElementProxyArray()) {
sb.append("\t\n");
sb.append(element.getSTEAsString());
}
return sb.toString();
}
return "";
}
}
Then set this into jsonLayout instance.
JsonLayout jsonLayout = new JsonLayout();
jsonLayout.setThrowableProxyConverter(new CustomThrowableProxyConverter());
jsonEncoder.setLayout(jsonLayout);
I have a program in which I need to set the logger formatting, but since there are several entry points (I don't control all of them) I want to place the System.setProperty() call in the constructor of the class where all of the entry points converge.
Main class
public static void main(String[] args) throws Exception {
String foo = "bar";
String baz = "boo";
static final String FORMATTER_CONFIG = "%1$tb %1$td, %1$tY %1$tr %4$s: %5$s%n";
SomeClass sc = new SomeClass();
sc.method1(foo);
sc.method2(baz);
try {
SomeOtherClass soc = SomeOtherClass.newInstance();
} catch (Exception e) {;
}
// Next line will update the property of the logging formatter for this instance
System.setProperty("java.util.logging.SimpleFormatter.format", FORMATTER_CONFIG);
MyThirdClass mtc = MyThirdClass.getStaticMethod(foo, baz);
Logger logger = Logger.getLogger("myProject.main");
Handler h = new FileHandler("LogHere.txt");
h.setFormatter(new SimpleFormatter());
logger.addHandler(h);
logger.setLevel(Level.INFO);
logger.info("Info level log");
logger.fine("Fine level log");
logger.finer("Finer level log");
logger.finest("Finest level log");
}
Constructor of MyThirdClass:
public MyThirdClass() throws SecurityException {
this.classStaticVar = EntirelyDifferentClass.getVar();
logger = Logger.getLogger(this.getClass().getName());
Handler h = null;
try {
h = new FileHandler("MTC_LogHere.txt");
} catch (IOException ex) {
logger.warning("Failed to initialize custom FileHandler for MTC_LogHere.txt");
}
h.setFormatter(new SimpleFormatter());
logger.addHandler(h);
logger.setLevel(Level.INFO);
}
I want to put the call to System.setProperty as the first line of the constructor in MyThirdClass (before the call to EntirelyDifferentClass) but when I do the logging is not formatted according to the custom rules. If I leave it right where it is, the line before the call to MyThirdClass in the main method, it works as intended. Note that I've also tried putting the code block for System.setProperty in the getStaticMethod() method.
I understand that the system.properties are all static variables, so I have to believe that whatever is happening in the JVM during the method call to MyThirdClass.getStaticMethod is locking in the static properties before I can change it.
Am I understanding what is happening in the JVM correctly?
Since I don't control all entry points in the actual production
version of this program, and I also don't control the compile
settings nor can I issue a -Djava line command to set the properties
that way (which I've also verified will work) I need to be able to
do this programmatically in MyThirdClass. What am I
missing/overlooking, or am I just out of luck?
This answer was originally edited into the question above by the OP Bryan, I have posted this here and requested him to do so himself:
For anyone wanting to know the answer, I added a static block at the
class level of MyThirdClass:
protected static Logger logger = null;
static {
System.setProperty("java.util.logging.SimpleFormatter.format", FORMATTER_CONFIG);
logger = Logger.getLogger("myProject.MyThirdClass");
Handler h = null;
try {
h = new FileHandler("LogHere.txt");
}
catch (Exception ex) {
System.out.println("Exception");
}
h.setFormatter(new SimpleFormatter());
logger.addHandler(h);
logger.setLevel(Constants.LOGGING_LEVEL);
}
When you call a static method, such as getStaticMethod() in your case, an instance of the class is not created, and therefore the constructor is not called.
Try putting System.setProperty() in the static method instead of in the constructor.
For example:
import java.util.logging.*;
public class Main {
public static void main(String[] args) {
SomeClass.staticMethod();
Logger logger = Logger.getLogger(Main.class.getName());
logger.setLevel(Level.INFO);
logger.info("Info level log");
}
}
public class SomeClass {
public static void staticMethod() {
final String FORMATTER_CONFIG = "%1$tb %1$td, %1$tY %1$tr %4$s: %5$s%n";
System.setProperty("java.util.logging.SimpleFormatter.format", FORMATTER_CONFIG);
}
}
What is the best way of writing a unit test for a method, such as my setProperties (see below), that uses a private configuration variable (config). I tried but failed to override it using reflection and Makito, but without success. I realize that changing the design to make the code easier to test is best, but I want to created some unit tests before I refactor the code.
public class MainClass {
private final java.lang.String config = "app.properties";
public TestClass() {
try {
setProperties();
} catch (Exception e) {
e.printStackTrace();
}
}
public void setProperties() throws Exception {
try {
InputStream input = new BufferedInputStream(new FileInputStream(config));
..
..
} catch (Exception exception) {
throw exception;
}
}
}
Do refactor a tiny bit by extracting a method with a parameter that takes an input stream. Call this new method (probably package-protected) from the old one. Write tests against the new method. Then do more refactorings.
This is an indication of a broken design; don't hard-code things like this. Better yet, determine what the appropriate responsibility for this class is, and, in decreasing order of preference:
pass in an object with the configuration properties, strongly typed
pass in a Map with the configuration properties
pass in an InputStream for the properties file
As File objects are never available from a jar, you shouldn't ever make interfaces like this more specific than InputStream or Reader, so that you can always pass in streams from your jar classpath.
So you can use Properties class in Java for this. Please have a look at this code.
public class PropertyUtil {
private static Properties prop;
private static Logger logger = Logger.getLogger(PropertyUtil.class);
private PropertyUtil() {
}
public void setProperty() {
String filePath = System.getenv("JAVA_HOME") + "/lib" + "/my_file.properties";
prop = new Properties();
try (InputStream input = new FileInputStream(filePath)) {
prop.load(input);
} catch (IOException ex) {
logger.error("Error while reading property file " + ex);
}
}
public static String getProperty(String key) {
if (prop.containsKey(key)) {
return prop.getProperty(key);
} else {
return null;
}
}
public static <T> T getProperty(String key, Class<T> claz) {
if (claz.getName().equals(Integer.class.getName())) {
return claz.cast(Integer.parseInt(prop.getProperty(key)));
}
if (claz.getName().equals(Long.class.getName())) {
return claz.cast(Long.parseLong(prop.getProperty(key)));
}
if (claz.getName().equals(Boolean.class.getName())) {
return claz.cast(Boolean.parseBoolean(prop.getProperty(key)));
}
if (claz.getName().equals(Double.class.getName())) {
return claz.cast(Double.parseDouble(prop.getProperty(key)));
}
if (claz.getName().equals(String.class.getName())) {
return claz.cast(prop.getProperty(key));
}
return null;
}
I implemented two customized handler to log information on DB and an additional flat file (DBHandler and MyFileHandler). This new log handlers will be used by a single class on a specific package.
I attached the two new loggers to a specific package only.
The idea is to switch between this two handlers (file and database)for the classes contaiend on a specific package, but currently with the current configuration I could not do that, so either I am logging with both handler either there is no log at all.
I tried to set the log level for DB handler to off but it is still logging normally on DB.
below the configuration file is use logging.properties
############################################################
##### Global properties
############################################################
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler, com.test.logging.DBHandler, com.test.logging.MyFileHandler
.level = INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
java.util.logging.FileHandler.level = ALL
java.util.logging.FileHandler.pattern = %t/CLog%g.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
com.test.logging.MyFileHandler.level = ALL
com.test.logging.MyFileHandler.pattern = %t/custLog%g.log
com.test.logging.MyFileHandler.limit = 50000
com.test.logging.MyFileHandler.count = 1
com.test.logging.MyFileHandler.formatter = java.util.logging.SimpleFormatter
com.test.logging.DBHandler.level=OFF
com.test.ccb.mon.handlers=com.test.logging.DBHandler, com.test.logging.MyFileHandler
The class using the logger to track he inforamtion is below
package com.test.ccb.mon;
public class Utils {
public static final Logger logger = Logger.getLogger(Utils.class.getCanonicalName());
public void logging()
{
//processing
logger.info("message);
}
}
DBHandler class:
public class DBHandler extends Handler {
#Override
public void close() throws SecurityException {
}
#Override
public void flush() {
}
#Override
public void publish(LogRecord logRecord) {
if (isLoggable(logRecord))
{
try {
//SQL call to insert onDB
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
MyFileHandler class:
public class MyFileHandler extends FileHandler{
public MyileHandler() throws IOException, SecurityException {
super();
}
#Override
public void close() throws SecurityException {
super.close();
}
#Override
public void flush() {
super.flush();
}
#Override
public void publish(LogRecord record) {
super.publish(record);
}
}
The Handler class doesn't read any properties from the LogManager by default. You have to code that logic in all of your subclasses.
public class DBHandler extends Handler {
public DBHandler() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String v = m.getProperty(p + ".level");
try {
if (v != null) {
super.setLevel(Level.parse(v));
}
} catch (RuntimeException re) {
reportError(v, re, ErrorManager.OPEN_FAILURE);
}
//#todo create code to parse filter, formatter, encoding, etc.
}
#Override
public void close() throws SecurityException {
}
#Override
public void flush() {
}
#Override
public void publish(LogRecord logRecord) {
if (isLoggable(logRecord)) {
try {
//SQL call to insert onDB
} catch (Exception e) {
reportError("", e, ErrorManager.WRITE_FAILURE);
}
}
}
}
Reproducing your problem is not so easy for me. With handler classes similar to yours, changes to the configuration file have the expected effect. With the DBHandler.level=OFF setting, the database handler output is missing for me:
Aug 11, 2015 1:47:26 PM com.test.ccb.mon.Utils logging
DBHandler.publish - handler level: OFF; log record level: INFO
INFO: message
MyFileHandler - message
Logging handlers:
###java.util.logging.FileHandler-ALL
###java.util.logging.ConsoleHandler-ALL
###com.test.logging.DBHandler-OFF
###com.test.logging.MyFileHandler-ALL
Your debug code to print the logging handlers is now also included in the following main method to your Utils class. You could run this method yourself, to see whether this way of reading the configuration file works better for you:
public static void main(final String[] arguments) throws IOException
{
final String fileName = "logging.properties";
final InputStream propertiesStream = Utils.class.getResourceAsStream(fileName);
//final InputStream propertiesStream = new FileInputStream("path to file");
LogManager.getLogManager().readConfiguration(propertiesStream);
new Utils().logging();
System.out.println();
// No handlers for this logger directly, but four for its parent.
System.out.println("Logging handlers:");
for (final Handler handler : logger.getParent().getHandlers())
System.out.println("###" + handler.getClass().getName()
+ "-" + handler.getLevel());
}
A very simple version of your DBHandler class could look like this (please note the if (isLoggable(record)) check in the publish method):
package com.test.logging;
import java.util.logging.*;
/**
* Logging handler that stores logging in the database.
*/
public class DBHandler extends Handler {
#Override
public void publish(final LogRecord record) {
System.out.println("DBHandler.publish - handler level: " + getLevel()
+ "; log record level: " + record.getLevel());
if (isLoggable(record))
System.out.println(getClass().getSimpleName() + " - " + record.getMessage());
}
#Override
public void flush() {
// Empty.
}
#Override
public void close() throws SecurityException {
// Empty.
}
}
I have this program as shown below , right now its only printng the stacktrace .
my question is that , is it possible to get the stack trace and also a custom field , here in my case i need 1090099
Please tell me if its possible ??
package com;
import org.apache.log4j.Logger;
public class Test {
private static final Logger logger = Logger.getLogger(Test.class);
public static void main(String args[]) {
try {
String accountid = "1090099";
String desc = null;
System.out.println(desc.toUpperCase());
}
catch (Exception t)
{
logger.fatal("Exception inside the Test program ", t);
}
}
}
2013-06-26 21:44:29,723[main] FATAL(Test.java:<main>:16)- Exception inside the Test program
java.lang.NullPointerException
at com.Test.main(Test.java:12)
You have to include it manually in the message you're logging. But it looks to me like what you're really looking for is the MDC (mapped diagnostic context), a way to store values in a thread-local "context" that can then be used to distinguish between log messages relating to different application-level entities.
package com;
import org.apache.log4j.*;
public class Test {
private static final Logger logger = Logger.getLogger(Test.class);
public static void main(String args[]) {
MDC.put("accountid", "1090099");
try {
String desc = null;
System.out.println(desc.toUpperCase());
}
catch (Exception t)
{
logger.fatal("Exception inside the Test program ", t);
} finally {
MDC.remove("accountid");
}
}
}
You would then include %X{accountid} somewhere in your appender's layout pattern and it would include the appropriate MDC entry in every log message, including those logged by third-party code that you call.
I would create my own Exception class, with members to hold the additional information, and a suitable toString() method that displays them. Wrap the original Exception in your custom Exception and add the information you want preserved.
Yes, you can print the value as long as it's in scope.
String accountid = null;
try {
accountid = "1090099";
String desc = null;
System.out.println(desc.toUpperCase());
} catch (Exception t) {
logger.fatal("Exception inside the Test program " + accountid, t);
}
Also, I would suggest using logger.debug instead of system.out.println for your other logging calls...
You are close to achieving that.
In your case you will have to declare the accountid outside the try block and then you can append the accountid along with your Exception inside the Test program message`
String accountid = "";
try {
accountid = "1090099";
String desc = null;
System.out.println(desc.toUpperCase());
}
catch (Exception t)
{
logger.fatal("Exception inside the Test program.\nAccount ID: " + accountid, t);
}