четвртак, 28. мај 2009.

Условна дијагностика у јави

Постоји неколико широко распрострањених библиотека за исписивање дијагностичких порука (енгл. logging). На пример, java.util.logging.

У својим програмима користим ово, али ми недостаје условна компилација. Оно што у Ц/Ц++ могу да урадим са #ifdef  . . . не могу у јави. Тако сам приморан да дијагностику коју користим искључиво за време развоја, и не желим да буде присутна у крајњем производу морам да пишем овако: 



void foo(int bar) {
if (DEBUG)
logger.trace("U foo(" + bar + ")");
}


Међутим, са јавом 1.5, дошла је и кључна реч assert. Ово је добродошло јер омогућава убацивање провера у сам код, а да то не ремети перформансе крајњег производа. Као посебан бонус, који није доступан у Ц/Ц++, асерти се могу укључити по потреби једноставним давањем аргумента -ea јавиној виртуелној машини.

То ме је подстакло да направим једноставан систем за условну дијагностику сличан #ifdef приступу у Ц/Ц++. Користим постојеће пакете (на пример java.util.logging) али унутар асерта. Наравно, ово није могуће урадити директно јер, на жалост, интерфејси дефинисани у овим пакетима не враћају никакву вредност. Мени је било потребно да сваки метод враћа вредност true да бих исти могао да користим унутар assert исказа:



void foo(int bar) {
assert logger.trace("U foo(" + bar + ")");
}


Ово ми даје управо оно што сам желео: условну дијагностику која је у нормалном режиму рада потпуно избачена оптимизацијом. То је чак и боље од Ц/Ц++ условне компилације јер се са јавом коначна компилација врши на рачунару корисника: уколико је јава виртуелна машина стартована са опцијом -ea онда ће дијагностика бити исписивана, у супротном не.

Моја имплементација се састоји у дефинисању интерфејса:



/**
* Copyright (c) 2007 Aleksandar Ristovski.
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Aleksandar Ristovski - initial API and implementation
*/
package org.java3declipse.logger;

/** ILogger interface.
*
* This interface is very similar to existing logger
* interfaces, with one significant difference: the
* implementation should always return true.
*
* <p> <b>Rationale:</b>
*
* <p> Logging is a nice feature but can have performance
* impact. If we simply call logger's log method, even if
* the logging level is lower than stated, arguments get
* evaluated. We typically have string concatenations in our
* log message which can have significant impact on memory
* fragmentation and speed.
*
* <p> Some approaches use conditional which is much better
* performance-wise. I don't like this approach as the
* statement is still there. <p> This approach suggests
* using <code>assert</code>. When program is started with
* '-ea', logging is 'on'. When program is started without
* assertions enabled, the logging statements are completely
* optimized out. <p> Typical example:
*
* <pre>
* import org.java3declipse.logger.ILogger;
* import org.java3declipse.logger.Logger;
*
* public class MyClass {
* private static ILogger logger =
* Logger.getLogger(MyClass.class.getSimpleName());
*
* public MyClass() {
* assert logger.info("MyClass constructor");
* }
* }
* </pre>
*
*
* @author Aleksandar Ristovski
*/
public interface ILogger {
boolean info(String msg);
boolean warning(String msg);
boolean error(String msg);
boolean error(String msg, Throwable t);
boolean debug(String msg);
boolean debug(String msg, Throwable t);
}


И имплементацији истог:



/**
* Copyright (c) 2007 Aleksandar Ristovski.
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Aleksandar Ristovski - initial API and implementation
*/
package org.java3declipse.logger;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

/**
* Logger class - default implementation of ILogger interface.
*
* Wrapper for a generic logger. This class provides default
* implementation for ILogger interface.
*
* @see ILogger
* @author Aleksandar Ristovski
*
*/
public class Logger implements ILogger {
private ILogger logger_;

/** Constructor, using initialized
* <code>java.util.logging.Logger</code>.
*
* @param logger - initialized
* <code>java.util.logging.Logger</code>
*/
public Logger(java.util.logging.Logger logger) {
logger_ = this.new JavaUtilLogger(logger);
}

/** Constructor - using user implementation of ILogger.
*
* This constructor can be used if logger other than
* <code>java.util.logging.Logger</code> is desired.
*
* @param logger - initialized object that
* implements <code>ILogger</code> interface
*/
public Logger(ILogger logger) {
logger_ = logger;
}

public boolean info(String msg) {
return logger_.info(msg);
}

public boolean warning(String msg) {
return logger_.warning(msg);
}

public boolean error(String msg) {
return logger_.error(msg);
}

public boolean error(String msg, Throwable t) {
return logger_.error(msg, t);
}

public boolean debug(String msg) {
return logger_.debug(msg);
}

public boolean debug(String msg, Throwable t) {
return logger_.debug(msg, t);
}

private class JavaUtilLogger implements ILogger {
final java.util.logging.Logger javaUtilLogger;
public JavaUtilLogger(java.util.logging.Logger logger) {
javaUtilLogger = logger;
}
public boolean info(String msg) {
javaUtilLogger.info(msg);
return true;
}
public boolean warning(String msg) {
javaUtilLogger.warning(msg);
return true;
}
public boolean error(String msg) {
javaUtilLogger.severe(msg);
return true;
}
public boolean error(String msg, Throwable t) {
javaUtilLogger.log(Level.SEVERE, msg, t);
return true;
}
public boolean info(String string, Throwable e) {
javaUtilLogger.log(Level.INFO, string, e);
return true;
}
public boolean debug(String msg) {
javaUtilLogger.log(Level.FINE, msg);
return true;
}
public boolean debug(String msg, Throwable t) {
javaUtilLogger.log(Level.FINE, msg, t);
return true;
}

}

private static class SimpleFormatter extends Formatter {
@Override
public String format(LogRecord record) {
StringBuffer sb = new StringBuffer(80);
final String ln = record.getLoggerName();
final String msg = record.getMessage();
int level = record.getLevel().intValue();
if (level == Level.INFO.intValue())
sb.append("[INFO] ");
else if (level == Level.WARNING.intValue())
sb.append("[WARNING] ");
else if (level == Level.SEVERE.intValue())
sb.append("[SEVERE] ");
if (null != ln) {
sb.append(record.getLoggerName());
if (ln.length() + msg.length() > 75)
sb.append("\n");
else
sb.append("\t");
}
sb.append(record.getMessage());
sb.append("\n");
final Throwable thr = record.getThrown();
if (null != thr) {
ByteArrayOutputStream btbuff
= new ByteArrayOutputStream(80);
sb.append("Exception: ");
thr.printStackTrace(new PrintStream(btbuff));
sb.append(btbuff.toString());
sb.append("\n");
}
return sb.toString();
}
}

/** Convenience routine.
*
* Creates logger based on java.util.logging.Logger
* class.
*
* @param ID - logger id. Typically
* <code>Classname.class.getSimpleName()</code>
* @return logger
*/
public static ILogger getLogger(String ID) {
java.util.logging.Logger logger
= java.util.logging.Logger.getLogger(ID);
Handler h = new ConsoleHandler();
h.setFormatter(new SimpleFormatter());
logger.setUseParentHandlers(false);
logger.addHandler(h);
return new Logger(logger);
}
}