Log files are a necessary evil on a live system. A few rules can transform
your log files from a useless heap of textfiles to a gold mine.
I'll focus mostly on Log4J since it's available on Java, but is ported on many languages
also as it sets some kind of logging standard.
The logger is configured per class with a :
private static Logger logger =
Logger.getLogger(MyClass.class);
The logger is private since each class should have its logger, especially
the derived ones (that way you can very nicely debug the virtual function
calls), and static since it's either thread-safe and that way you have it even
in the <init> and <cinit> of your class (you did put it in the
first line of the class, didn't you ?).
This is very useful since you can configure a per-package or a even
per-class level of logging. It is very useful since all your classes do only
one thing, don't they ?
I usually use only 5 levels of logging : ERROR, WARN, INFO, DEBUG, TRACE
- TRACE is used to help with a dump of many internal
variables (it's a last resort debug, since usually it's very verbose)
- DEBUG is used to debug this class, with keypoints inside
the class, in order to see the inside flow of execution.
- INFO is used to debug other classes with the help of this
one. Usually it emits only one line per public call, with the incoming
parameters and the result displayed in a synthetised form.
- WARN is used when an exceptional situation happens
(usually a catched Exception that is triggered by the caller data) and there is
a known path to recover.
- ERROR is used when an exceptional situation happens, but
there is no known path to recover. Usually this line is send by email to the
administrator. If there is a problem, and an ERROR is logged, there should be
no other ERROR logged for this problem : it will help you to keep a high
signal/noise ratio.
In a live production system, I just log INFO for terminal business
classes (the ones that represent actions), and WARN on technical ones (the one
that actions class uses). I configure it to send an email on every ERROR.
It's also very important to have a reminder of a synthetised form of the
arguments in the INFO, WARN & ERROR log messages (such as an ORDER_ID if
it's an ordering action or the PRODUCT_ID if it's a deleting product action).
It's also a good thing to put the exception that triggered the WARN/ERROR log.
That way you can just grep through your logs to see if, when and what happened
to that famous product that everyone is so excited about
Always use a RollingFileAppender. Always. If you're scared
about loosing some logs, just put an insane number of backup files since
nothing is worse than not enough space on the log filesystem :
you won't have the logs anyway. Note that if you have a different kind of
rolling mecanismed you can use it, the point is that you should
never leave a growing log file without control.
So, here is a exemple of 2 classes (one business, one technical) that
present the logging system I talked about :
public class PublishProductAction extends Action {
private static Logger logger = Logger.getLogger(AddProductAction.class);
private int product_id;
public PublishProductAction(int product_id) { this.product_id = product_id; }
public void execute() {
logger.info("publishing product[product_id:"
+ product_id + "]");
try {
// Do things...
} catch (Exception e) {
logger.error("Cannot publish product["
+ product_id + "] : ", e);
}
logger.debug("done publishing product[product_id:"
+ product_id + "]");
}
}
public class MyPreparedStatement {
private static Logger logger = Logger.getLogger(MyPreparedStatement.class);
private String sql;
public MyPreparedStatement(String sql) { this.sql_id = sql; }
public void execute(Collection params) {
startTimer();
try {
if (sql == null) {
// Log in warning, since it should not happen,
// but we can handle it gracefully
logger.warn("The SQL statement is null, we do nothing");
return;
}
try {
// Do things...
} catch (Exception e) {
// log only in info since we don't catch
// the Exception.
logger.info("Cannot execute SQL[" + sql + "] : ", e);
thow e;
}
} finally {
stopTimer();
// help the outside class to see what happened
if (logger.isInfoEnabled()) {
logger.info("executed sql ["
+ parseSQL(sql, params) + "] in "
+ getTime() + " ms");
}
}
}
}