package osgi.enroute.logging.provider;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.log.LogService;
import osgi.enroute.logging.messages.api.Format;
import osgi.enroute.logging.messages.api.LogBook;
import aQute.bnd.annotation.component.Activate;
import aQute.bnd.annotation.component.Component;
import aQute.bnd.annotation.component.Deactivate;
import aQute.bnd.annotation.component.Reference;
/**
*
*/
@Component
public class LogAdminImpl {
final static int LOG_TRACE = LogService.LOG_DEBUG + 1;
final static Pattern CUSTOM_CLASSES = Pattern
.compile("(?!com\\.sun|sun|java\\.|osgi\\.enroute\\.logging\\.provider)(.+\\.)+(.*)");
ComponentContext ctx;
ServiceRegistration< ? > registration;
ServiceReference<LogService> log;
boolean printStackTraces;
PrintStream out = System.err;
LogService logService;
@Activate
void activate(ComponentContext ctx) throws Exception {
this.ctx = ctx;
logService = ctx.getBundleContext().getService(log);
Hashtable<String,Object> properties = new Hashtable<>();
//
// We want to share a single object (this LogAdminImpl) but still
// want to use a service factory so each bundle gets its own. So we
// need to register it manually :-(
//
registration = ctx.getBundleContext().registerService(LogBook.class.toString(), new ServiceFactory<LogBook>() {
@Override
public LogBook getService(Bundle bundle, ServiceRegistration<LogBook> registration) {
return new LogBookImpl(LogAdminImpl.this, bundle, bundle.getSymbolicName());
}
@Override
public void ungetService(Bundle bundle, ServiceRegistration<LogBook> registration, LogBook service) {
((LogBookImpl) service).close();
}
}, properties);
}
@Deactivate
void deactivate() {
registration.unregister();
}
void message(MessageFormatter msf, int level, String format, Object[] arguments) {
try {
ServiceReference< ? > ref = null;
Throwable throwable = null;
int n = 0;
//
// 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 (ref == null && arguments[i] instanceof ServiceReference< ? >) {
ref = (ServiceReference< ? >) arguments[i];
n++;
}
else if (throwable == null && arguments[i] instanceof Throwable) {
throwable = (Throwable) arguments[i];
n += 2;
} 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();
if (msf.scope != null) {
sb.append(msf.scope).append(":: ");
}
where(sb);
try (Formatter formatter = new Formatter(sb)) {
formatter.format(format, nargs);
}
LogService log = msf.log;
if (log == null)
log = msf.bundle.getBundleContext().getService(this.log);
if (log != null) {
switch (n) {
case 1 :
log.log(ref, level, sb.toString());
break;
case 2 :
log.log(level, sb.toString(), throwable);
break;
case 3 :
log.log(ref, level, sb.toString(), throwable);
break;
case 0 :
default :
log.log(level, sb.toString());
break;
}
}
if (throwable != null && printStackTraces) {
sb.append("\n");
try (PrintWriter sw = getWriter(sb)) {
throwable.printStackTrace(sw);
}
out.println(sb.toString());
}
}
catch (Exception e) {
logService.log(LogService.LOG_ERROR, "Shamefully have to admit the log service failed " + e);
}
}
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 Object toString(Object object) {
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;
}
return object;
}
/**
* Get the current location of where the error was reported.
*/
private void where(StringBuilder sb) {
try {
throw new Exception();
}
catch (Exception e) {
StackTraceElement[] stackTrace = e.getStackTrace();
for (int i = 2; i < stackTrace.length; i++) {
Matcher matcher = CUSTOM_CLASSES.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("] ");
}
}
}
}
Object message(MessageFormatter msf, int level, Method method, Object[] args) {
Format f = method.getAnnotation(Format.class);
String format;
if (f == null)
format = makeup(method.getName(), args.length);
else
format = f.value();
message(msf, level, format, args);
return null;
}
/**
* Use a method name and turn it into a reasonable format
*
* @param name
* @param length
* @return
*/
private String makeup(String id, int length) {
StringBuilder sb = new StringBuilder();
sb.append(Character.toUpperCase(id.charAt(0)));
int i = 1;
char c;
boolean upper = true;
while (i < id.length()) {
c = id.charAt(i);
if (Character.isUpperCase(c)) {
if (upper) {
sb.append(c);
} else {
sb.append(" ").append(c);
}
upper = true;
} else {
upper = false;
if (c == '_') {
if (length > 0) {
sb.append(" %s ");
length--;
}
} else {
sb.append(c);
}
}
}
while (length > 0) {
sb.append(" %s ");
length--;
}
return sb.toString();
}
<T> T scoped(LogBookImpl book, Class<T> type, String prefix) {
LogBookHandler handler = new LogBookHandler(this, prefix, book);
synchronized (book) {
if (book.handlers == null)
book.handlers = new ArrayList<>();
book.handlers.add(handler);
}
return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class< ? >[] {
type
}, handler));
}
@Reference(service = LogService.class)
void setLogService(ServiceReference< LogService > ref) {
this.log = ref;
}
}