package osgi.logger.provider;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.Formatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
import org.slf4j.Marker;
import osgi.enroute.logger.api.Level;
import osgi.enroute.logger.api.LoggerAdmin.Control;
/**
* This is the base class for the class that must log information. It fully
* implements the slf4j Logger class and maps them to an {@link Entry} record
* which is then queued via the static (yuck) dispatcher.
*/
class AbstractLogger implements org.slf4j.Logger {
/*
* A pattern to discard classes in a stacktrace that we do not want to see
*/
// final static Pattern CUSTOM_CLASSES = Pattern
// .compile("(?!com\\.sun|sun|java\\.|osgi\\.enroute\\.logger\\.provider)(.+\\.)+(.*)");
String name;
Bundle bundle;
volatile boolean info;
volatile boolean trace;
volatile boolean debug;
volatile boolean warn;
volatile boolean error;
volatile boolean exceptions;
volatile boolean where;
boolean init;
boolean registered;
Level level;
AbstractLogger(Bundle bundle, String name) {
this.bundle = bundle;
this.name = name;
reset();
}
AbstractLogger() {
reset();
}
/*
* The init method is checked when a flag is set to true (done in reset()).
* The init method will set all flags correctly based on defaults, or, if
* there is a admin, from the settings in the admin. The init method returns
* true if the given level must be logged. I think this is the fastest
* possible way when you're not using a level. I.e. initially trace is set
* to true. At the first trace, init(TRACE) is called but this in general
* will set the trace flag to false. So the second time only the trace flag
* is checked. In general, extra work is only done when we actually are
* going to log a message.
*/
private synchronized boolean init(Level level) {
if (init)
return true;
init = true;
//
// We need to register once with the LoggerDispatcher
// so the admin can reset us when there are new settings
//
if (!registered) {
LoggerDispatcher.dispatcher.register(this);
registered = true;
}
LoggerAdminImpl admin = LoggerDispatcher.dispatcher.admin;
if (admin != null) {
//
// We have an admin. So we actually get our settins
// from this admin.
//
Control control = admin.getControl(this.name);
debug = control.level.ordinal() <= Level.DEBUG.ordinal();
info = control.level.ordinal() <= Level.INFO.ordinal();
trace = control.level.ordinal() <= Level.TRACE.ordinal();
warn = control.level.ordinal() <= Level.WARN.ordinal();
error = control.level.ordinal() <= Level.ERROR.ordinal();
level = control.level;
} else {
//
// Default Defaults if no admin present. There is a bit of a race
// condition since at this admin could have become active and reset
// us
// However, this is pretty rare and should be corrected on the next
// call
//
debug = false;
info = false;
trace = false;
warn = true;
error = true;
}
return isLevel(level);
}
/*
* Next time this logger is used it will try to get its configuration again
*/
synchronized void reset() {
init = false;
debug = true;
info = true;
trace = true;
warn = true;
error = true;
}
/*
* Check if this level is set
*/
private boolean isLevel(Level level) {
switch (level) {
case DEBUG :
return debug;
case ERROR :
return error;
case INFO :
return info;
case TRACE :
return trace;
case WARN :
return warn;
case R1 :
case R2 :
case R3 :
case AUDIT :
default :
return true;
}
}
/*
* Return the name
*/
@Override
public String getName() {
return name;
}
/*
* The core routine. We've committed to logging so now we have to create a
* logging message.
*/
void message(int level, String format, Object... arguments) {
try {
//
// Log4j is using {} as the %s ... :-(
// so we replace it with the %s
// TODO figure out markers
//
if (format.indexOf('{') >= 0) {
format = format.replaceAll("\\{\\}", "%s");
}
//
// We will log an entry to the queue
//
Entry entry = new Entry();
entry.level = level;
//
// Adjust the arguments since arrays print badly and we can do
// better for some other objects as well.
//
for (int i = 0; i < arguments.length; i++)
if (arguments[i] != null) {
if (entry.reference == null && arguments[i] instanceof ServiceReference< ? >) {
entry.reference = (ServiceReference< ? >) arguments[i];
} else if (entry.exception == null && arguments[i] instanceof Throwable) {
entry.exception = (Throwable) arguments[i];
} else if (!(arguments[i] instanceof String))
arguments[i] = toString(arguments[i]);
}
//
// Add a few more places so that errors in the format would refer to
// non-existent args. Logging should not throw exceptions.
//
Object nargs[] = new Object[arguments.length + 10];
System.arraycopy(arguments, 0, nargs, 0, arguments.length);
final StringBuilder sb = new StringBuilder();
try (Formatter formatter = new Formatter(sb)) {
if (name != null) {
sb.append(name).append(" :: ");
}
if (where) {
where(sb, 4);
}
formatter.format(format, nargs);
if (entry.exception != null && exceptions) {
sb.append("\n");
try (PrintWriter sw = getWriter(sb)) {
entry.exception.printStackTrace(sw);
}
}
}
entry.message = sb.toString();
entry.source = bundle;
//
// We will not block on the queue. So we attempt to put it in the
// queue and if we do not succeed, print it to std err.
//
if (!LoggerDispatcher.dispatcher.queue.offer(entry)) {
System.err.println("Overflowing log queue " + entry);
}
}
catch (Exception e) {
System.err.println("Shamefully have to admit the log service failed :-(" + e);
e.printStackTrace();
}
}
private PrintWriter getWriter(final StringBuilder sb) {
return new PrintWriter(new Writer() {
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
for (int i = 0; i < len; i++)
sb.append(cbuf[i + off]);
}
@Override
public void flush() throws IOException {}
@Override
public void close() throws IOException {}
});
}
/*
* Create a more suitable text presentation for array objects
* @param object
* @return
*/
private String toString(Object object) {
if (object == null)
return "null";
if (object.getClass().isArray()) {
StringBuilder sb = new StringBuilder();
String del = "[";
for (int i = 0; i < Array.getLength(object); i++) {
sb.append(del).append(toString(Array.get(object, i)));
del = ", ";
}
sb.append("]");
return sb.toString();
}
return object.toString();
}
/*
* Get the current location of where the error was reported.
*/
protected void where(StringBuilder sb, int max) {
try {
throw new Exception();
}
catch (Exception e) {
StackTraceElement[] stackTrace = e.getStackTrace();
int n = 0;
for (int i = 2; i < sb.length(); i++) {
Matcher matcher = Pattern.compile(".*").matcher(stackTrace[i].getClassName());
if (matcher.matches()) {
String logMethod = stackTrace[i].getMethodName();
String logClass = matcher.group(2);
int line = stackTrace[i].getLineNumber();
sb.append("[").append(logClass).append(".").append(logMethod);
if (line != 0)
sb.append(":").append(line);
sb.append("] ");
n++;
if (n >= max)
return;
}
}
}
}
/**************************************************************************************************************/
// The rest is the SLF4J dump ... what backward compatibility does to you
// ... :-(
@Override
public void info(String format, Object... arguments) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, format, arguments);
}
@Override
public void debug(String format, Object... arguments) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, format, arguments);
}
@Override
public void error(String format, Object... arguments) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, format, arguments);
}
@Override
public void trace(String format, Object... arguments) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, format, arguments);
}
@Override
public void warn(String format, Object... arguments) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, format, arguments);
}
@Override
public boolean isInfoEnabled() {
return info && init(Level.INFO);
}
@Override
public boolean isDebugEnabled() {
return debug && init(Level.DEBUG);
}
@Override
public boolean isErrorEnabled() {
return error && init(Level.ERROR);
}
@Override
public boolean isTraceEnabled() {
return trace && init(Level.TRACE);
}
@Override
public boolean isWarnEnabled() {
return warn && init(Level.WARN);
}
public void close() {
LoggerDispatcher.dispatcher.unregister(this);
init = registered = info = trace = error = warn = debug = false;
}
@Override
public void debug(String string) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, string);
}
@Override
public void debug(String format, Object arguments) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, format, arguments);
}
@Override
public void debug(String string, Throwable t) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, string, t);
}
@Override
public void debug(Marker marker, String string) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, string, marker);
}
@Override
public void debug(String format, Object a, Object b) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, format, a, b);
}
@Override
public void debug(Marker marker, String format, Object a) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, format, a, marker);
}
@Override
public void debug(Marker marker, String format, Object... args) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, format, args);
}
@Override
public void debug(Marker marker, String format, Throwable t) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, format, t);
}
@Override
public void debug(Marker marker, String format, Object a, Object b) {
if (debug && init(Level.DEBUG))
message(LogService.LOG_DEBUG, format, a, b);
}
@Override
public void error(String string) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, string);
}
@Override
public void error(String format, Object arguments) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, format, arguments);
}
@Override
public void error(String string, Throwable t) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, string, t);
}
@Override
public void error(Marker marker, String string) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, string, marker);
}
@Override
public void error(String format, Object a, Object b) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, format, a, b);
}
@Override
public void error(Marker marker, String format, Object a) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, format, a, marker);
}
@Override
public void error(Marker marker, String format, Object... args) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, format, args);
}
@Override
public void error(Marker marker, String format, Throwable t) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, format, t);
}
@Override
public void error(Marker marker, String format, Object a, Object b) {
if (error && init(Level.ERROR))
message(LogService.LOG_ERROR, format, a, b);
}
@Override
public void info(String string) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, string);
}
@Override
public void info(String format, Object arguments) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, format, arguments);
}
@Override
public void info(String string, Throwable t) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, string, t);
}
@Override
public void info(Marker marker, String string) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, string, marker);
}
@Override
public void info(String format, Object a, Object b) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, format, a, b);
}
@Override
public void info(Marker marker, String format, Object a) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, format, a, marker);
}
@Override
public void info(Marker marker, String format, Object... args) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, format, args);
}
@Override
public void info(Marker marker, String format, Throwable t) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, format, t);
}
@Override
public void info(Marker marker, String format, Object a, Object b) {
if (info && init(Level.INFO))
message(LogService.LOG_INFO, format, a, b);
}
@Override
public boolean isDebugEnabled(Marker arg0) {
return debug && init(Level.DEBUG);
}
@Override
public boolean isErrorEnabled(Marker arg0) {
return error && init(Level.ERROR);
}
@Override
public boolean isInfoEnabled(Marker arg0) {
return info && init(Level.INFO);
}
@Override
public boolean isTraceEnabled(Marker arg0) {
return trace && init(Level.TRACE);
}
@Override
public boolean isWarnEnabled(Marker arg0) {
return warn && init(Level.WARN);
}
@Override
public void warn(String string) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, string);
}
@Override
public void warn(String format, Object arguments) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, format, arguments);
}
@Override
public void warn(String string, Throwable t) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, string, t);
}
@Override
public void warn(Marker marker, String string) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, string, marker);
}
@Override
public void warn(String format, Object a, Object b) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, format, a, b);
}
@Override
public void warn(Marker marker, String format, Object a) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, format, a, marker);
}
@Override
public void warn(Marker marker, String format, Object... args) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, format, args);
}
@Override
public void warn(Marker marker, String format, Throwable t) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, format, t);
}
@Override
public void warn(Marker marker, String format, Object a, Object b) {
if (warn && init(Level.WARN))
message(LogService.LOG_WARNING, format, a, b);
}
@Override
public void trace(String string) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, string);
}
@Override
public void trace(String format, Object arguments) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, format, arguments);
}
@Override
public void trace(String string, Throwable t) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, string, t);
}
@Override
public void trace(Marker marker, String string) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, string, marker);
}
@Override
public void trace(String format, Object a, Object b) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, format, a, b);
}
@Override
public void trace(Marker marker, String format, Object a) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, format, a, marker);
}
@Override
public void trace(Marker marker, String format, Object... args) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, format, args);
}
@Override
public void trace(Marker marker, String format, Throwable t) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, format, t);
}
@Override
public void trace(Marker marker, String format, Object a, Object b) {
if (trace && init(Level.TRACE))
message(LoggerAdminImpl.LOG_TRACE, format, a, b);
}
public void setBundle(Bundle bundle) {
this.bundle = bundle;
if (this.name == null) {
String name = this.bundle.getSymbolicName();
if (name == null)
name = bundle.getBundleId() + "";
if (bundle.getVersion() != null)
name += ";" + bundle.getVersion();
setName(name);
}
}
public void setName(String name) {
this.name = name;
}
}