package osgi.logger.provider;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
import osgi.enroute.debug.api.Debug;
import osgi.enroute.logger.api.Level;
import osgi.enroute.logger.api.LoggerAdmin;
import osgi.logger.provider.LoggerDispatcher.Eval;
import aQute.bnd.annotation.component.Activate;
import aQute.bnd.annotation.component.Component;
import aQute.bnd.annotation.component.ConfigurationPolicy;
import aQute.bnd.annotation.component.Deactivate;
import aQute.bnd.annotation.component.Reference;
import aQute.bnd.annotation.metatype.Configurable;
import aQute.libg.glob.Glob;
/**
* This is the Logger Admin component. It registers a {@link LoggerAdmin}
* service.
*/
@Component(
immediate = true,
servicefactory = false,
designate = Configuration.class,
provide = LoggerAdmin.class,
configurationPolicy = ConfigurationPolicy.optional,
properties = {
Debug.COMMAND_SCOPE + "=logger", Debug.COMMAND_FUNCTION + "=add|remove|settings"
})
public class LoggerAdminImpl extends Thread implements LoggerAdmin, Eval {
final static int LOG_TRACE = LogService.LOG_DEBUG + 1;
boolean traces;
PrintStream out = System.err;
final List<ServiceReference<LogService>> logReferences = new CopyOnWriteArrayList<>();
final List<LogService> logs = new CopyOnWriteArrayList<>();
final Map<Pattern,Control> controls = new ConcurrentHashMap<>();
Control control = new Control();
Settings settings = new Settings();
JavaUtilLoggingHandler javaUtilLogging;
final CountDownLatch latch = new CountDownLatch(1);
public LoggerAdminImpl() {
super("OSGi :: Logger Admin");
setDaemon(true);
}
/*
* Activate the component
*/
@Activate
public void activate(Map<String,Object> config) throws Exception {
assert config != null;
Configuration c = Configurable.createConfigurable(Configuration.class, config);
//
// Check if we need java util logging
//
if (c.javaUtilLogging()) {
java.util.logging.Logger.getLogger("").addHandler(javaUtilLogging = new JavaUtilLoggingHandler());
}
control.level = c.level() == null ? Level.WARN : c.level();
control.thread = null;
control.pattern = null;
control.stackTraces = c.traces();
control.where = c.where();
//
// Make us the admin ...
//
assert LoggerDispatcher.dispatcher.admin == null;
LoggerDispatcher.dispatcher.admin = this;
this.setPriority(Thread.MIN_PRIORITY);
start();
}
@Deactivate
public void deactivate() throws Exception {
if (javaUtilLogging != null) {
java.util.logging.Logger.getLogger("").removeHandler(javaUtilLogging);
}
//
// Stop our thread
//
interrupt();
}
/*
* This is the queue flusher. We read from the queue and send it to the OSGi
* Log Services registered. We quit this method when we get an interrupt.
*/
public void run() {
try {
LoggerDispatcher.dispatcher.evaluate(this);
//
// Poll the queue until we get an interrupt
//
while (!isInterrupted())
try {
//
// We wait util we get at least 1 log service. In the mean
// time we buffer the messages anyway. This is one time
// only.
//
latch.await(10, TimeUnit.SECONDS);
Entry take = LoggerDispatcher.dispatcher.queue.take();
//
// If there are no log services we print to the
// console
//
List<LogService> logs = getLogs(take);
if (logs.isEmpty()) {
System.err.println(take);
continue;
}
//
// We push to all log services registered since the deployer
// can always set the x.target property to limit the
// applicable log services
//
for (LogService log : logs) {
try {
int n = take.exception == null ? 0 : 1;
n += take.reference == null ? 0 : 2;
switch (n) {
case 0 :
log.log(take.level, take.message);
break;
case 1 :
log.log(take.level, take.message, take.exception);
break;
case 2 :
log.log(take.reference, take.level, take.message);
break;
case 3 :
log.log(take.reference, take.level, take.message, take.exception);
break;
}
}
catch (Exception e) {
//
// Hmm, not much we can do here ...
// Since we're the logging subsystem
//
e.printStackTrace();
}
}
}
catch (InterruptedException e) {
interrupt();
return;
}
}
catch (Exception e) {
//
// Hmm, not much we can do here ...
//
e.printStackTrace();
}
finally {}
}
private List<LogService> getLogs(Entry take) throws InvalidSyntaxException {
if (take.source == null)
return logs;
List<LogService> logs = new ArrayList<>();
BundleContext ctx = take.source.getBundleContext();
for (ServiceReference<LogService> ref : ctx.getServiceReferences(LogService.class, null)) {
LogService service = ctx.getService(ref);
if (service != null)
logs.add(service);
}
return logs;
}
/*
* We're an Eval, so we can iterate over the registered loggers. The default
* we supply is to reset the loggers so they have to refetch their
* configurations.
*/
public void eval(AbstractLogger msf) {
msf.reset();
}
/*
* Called by an Abstract Logger when it is initing.
*/
Control getControl(String identifier) {
for (java.util.Map.Entry<Pattern,Control> p : controls.entrySet()) {
if (p.getKey().matcher(identifier).matches())
return p.getValue();
}
return control;
}
/*
* List the information about the selected loggers (known to the system).
*/
@Override
public List<Info> list(String glob) throws Exception {
final List<Info> infos = new ArrayList<>();
final Pattern p = glob == null ? null : Glob.toPattern(glob);
;
LoggerDispatcher.dispatcher.evaluate(new Eval() {
@Override
public void eval(AbstractLogger msf) {
if (p == null || p.matcher(msf.name).find()) {
Info info = new Info();
info.bundleId = msf.bundle.getBundleId();
info.level = msf.level;
info.name = msf.name;
infos.add(info);
}
}
});
return infos;
}
/*
* Get the current settings
*/
@Override
public Settings getSettings() throws Exception {
return settings;
}
/*
* Set the current settings and update the current loggers
*/
@Override
public void setSettings(Settings settings) throws Exception {
Map<Pattern,Control> controls = new HashMap<>();
for (Control c : settings.controls) {
try {
Pattern p = Glob.toPattern(c.pattern);
controls.put(p, c);
}
catch (Exception ee) {
error("Invalid filter " + c.pattern, ee);
return;
}
}
synchronized (this.controls) {
this.controls.clear();
this.controls.putAll(controls);
}
LoggerDispatcher.dispatcher.evaluate(this);
if (javaUtilLogging != null) {
javaUtilLogging.update(controls);
}
}
private void error(String string, Exception ee) {
System.err.println(string);
}
/**
* Shell command to add a new setting
*
* @param pattern
* the pattern to add
* @param level
* the level to report on
* @param options
* set of options: stacktrace, where
* @return the Control added
*/
public Control add(Glob pattern, Level level, String... options) throws Exception {
Settings settings = getSettings();
Control control = new Control();
control.level = level;
control.pattern = pattern.toString();
for (String option : options) {
switch (option) {
case "stacktrace" :
control.stackTraces = true;
break;
case "where" :
control.where = true;
break;
default :
System.err.println("Unknown option " + option);
break;
}
}
settings.controls.add(control);
setSettings(settings);
return control;
}
/**
* Shell command to remove a control
*
* @param pattern
* the pattern to remove (will remove all controls with that
* exact pattern)
* @return the controls removed
*/
public List<Control> remove(Glob pattern) throws Exception {
Settings settings = getSettings();
List<Control> deleted = new ArrayList<>();
for (Iterator<Control> i = settings.controls.iterator(); i.hasNext();) {
Control c = i.next();
if (c.pattern.equals(pattern.toString())) {
i.remove();
deleted.add(c);
}
}
setSettings(settings);
return deleted;
}
/*
* Get the log services
*/
@Reference(type = '*', service = LogService.class)
void addLog(ServiceReference<LogService> log) {
latch.countDown();
logReferences.add(log);
}
/*
* Remove the log services
*/
void removeLog(ServiceReference<LogService> log) {
logReferences.remove(log);
}
/*
* Get the log services
*/
@Reference(type = '*')
void addLogService(LogService log) {
latch.countDown();
logs.add(log);
}
/*
* Remove the log services
*/
void removeLogService(LogService log) {
logs.remove(log);
}
}