blob: e018dd084d76eb361c8a08938ae2541dd70ca4a5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2016 Cognos Incorporated, IBM Corporation and others
* 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
******************************************************************************/
package org.eclipse.osgi.internal.log;
import java.io.PrintStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.eclipse.equinox.log.LogFilter;
import org.eclipse.equinox.log.SynchronousLogListener;
import org.eclipse.osgi.framework.util.ArrayMap;
import org.osgi.framework.*;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
public class ExtendedLogReaderServiceFactory implements ServiceFactory<ExtendedLogReaderServiceImpl> {
static final int MAX_RECURSIONS = 50;
static final class LogTask implements Runnable {
private final LogEntry logEntry;
private final LogListener listener;
LogTask(LogEntry logEntry, LogListener listener) {
this.logEntry = logEntry;
this.listener = listener;
}
public void run() {
safeLogged(listener, logEntry);
}
}
@SuppressWarnings("unchecked")
private static final Enumeration<?> EMPTY_ENUMERATION = Collections.enumeration(Collections.EMPTY_LIST);
static final LogFilter NULL_LOGGER_FILTER = new LogFilter() {
public boolean isLoggable(Bundle b, String loggerName, int logLevel) {
return true;
}
};
private static final LogFilter[] ALWAYS_LOG = new LogFilter[0];
private static PrintStream errorStream;
private final BasicReadWriteLock listenersLock = new BasicReadWriteLock();
private ArrayMap<LogListener, Object[]> listeners = new ArrayMap<>(5);
private LogFilter[] filters = null;
private final ThreadLocal<int[]> nestedCallCount = new ThreadLocal<>();
private final LinkedList<LogEntry> history;
private final int maxHistory;
static boolean safeIsLoggable(LogFilter filter, Bundle bundle, String name, int level) {
try {
return filter.isLoggable(bundle, name, level);
} catch (RuntimeException e) {
// "listener.logged" calls user code and might throw an unchecked exception
// we catch the error here to gather information on where the problem occurred.
getErrorStream().println("LogFilter.isLoggable threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
e.printStackTrace(getErrorStream());
} catch (LinkageError e) {
// Catch linkage errors as these are generally recoverable but let other Errors propagate (see bug 222001)
getErrorStream().println("LogFilter.isLoggable threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
e.printStackTrace(getErrorStream());
}
return false;
}
private static synchronized PrintStream getErrorStream() {
if (errorStream == null)
return System.err;
return errorStream;
}
public static synchronized void setErrorStream(PrintStream ps) {
errorStream = ps;
}
static void safeLogged(LogListener listener, LogEntry logEntry) {
try {
listener.logged(logEntry);
} catch (RuntimeException e) {
// "listener.logged" calls user code and might throw an unchecked exception
// we catch the error here to gather information on where the problem occurred.
getErrorStream().println("LogListener.logged threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
e.printStackTrace(getErrorStream());
} catch (LinkageError e) {
// Catch linkage errors as these are generally recoverable but let other Errors propagate (see bug 222001)
getErrorStream().println("LogListener.logged threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
e.printStackTrace(getErrorStream());
}
}
public ExtendedLogReaderServiceFactory(int maxHistory) {
this.maxHistory = maxHistory;
if (maxHistory > 0) {
history = new LinkedList<>();
} else {
history = null;
}
}
public ExtendedLogReaderServiceImpl getService(Bundle bundle, ServiceRegistration<ExtendedLogReaderServiceImpl> registration) {
return new ExtendedLogReaderServiceImpl(this);
}
public void ungetService(Bundle bundle, ServiceRegistration<ExtendedLogReaderServiceImpl> registration, ExtendedLogReaderServiceImpl service) {
service.shutdown();
}
boolean isLoggable(final Bundle bundle, final String name, final int level) {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
return isLoggablePrivileged(bundle, name, level);
}
});
}
return isLoggablePrivileged(bundle, name, level);
}
boolean isLoggablePrivileged(Bundle bundle, String name, int level) {
LogFilter[] filtersCopy;
listenersLock.readLock();
try {
filtersCopy = filters;
} finally {
listenersLock.readUnlock();
}
try {
if (incrementNestedCount() == MAX_RECURSIONS)
return false;
if (filtersCopy == null)
return false;
if (filtersCopy == ALWAYS_LOG)
return true;
int filtersLength = filtersCopy.length;
for (int i = 0; i < filtersLength; i++) {
LogFilter filter = filtersCopy[i];
if (safeIsLoggable(filter, bundle, name, level))
return true;
}
} finally {
decrementNestedCount();
}
return false;
}
private int incrementNestedCount() {
int[] count = getCount();
count[0] = count[0] + 1;
return count[0];
}
private void decrementNestedCount() {
int[] count = getCount();
if (count[0] == 0)
return;
count[0] = count[0] - 1;
}
private int[] getCount() {
int[] count = nestedCallCount.get();
if (count == null) {
count = new int[] {0};
nestedCallCount.set(count);
}
return count;
}
void log(final Bundle bundle, final String name, final Object context, final int level, final String message, final Throwable exception) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
logPrivileged(bundle, name, context, level, message, exception);
return null;
}
});
} else {
logPrivileged(bundle, name, context, level, message, exception);
}
}
void logPrivileged(Bundle bundle, String name, Object context, int level, String message, Throwable exception) {
LogEntry logEntry = new ExtendedLogEntryImpl(bundle, name, context, level, message, exception);
storeEntry(logEntry);
ArrayMap<LogListener, Object[]> listenersCopy;
listenersLock.readLock();
try {
listenersCopy = listeners;
} finally {
listenersLock.readUnlock();
}
try {
if (incrementNestedCount() >= MAX_RECURSIONS)
return;
int size = listenersCopy.size();
for (int i = 0; i < size; i++) {
Object[] listenerObjects = listenersCopy.getValue(i);
LogFilter filter = (LogFilter) listenerObjects[0];
if (safeIsLoggable(filter, bundle, name, level)) {
LogListener listener = listenersCopy.getKey(i);
SerializedTaskQueue taskQueue = (SerializedTaskQueue) listenerObjects[1];
if (taskQueue != null) {
taskQueue.put(new LogTask(logEntry, listener));
} else {
// log synchronously
safeLogged(listener, logEntry);
}
}
}
} finally {
decrementNestedCount();
}
}
private void storeEntry(LogEntry logEntry) {
if (history != null) {
synchronized (history) {
if (history.size() == maxHistory) {
history.removeLast();
}
history.addFirst(logEntry);
}
}
}
void addLogListener(LogListener listener, LogFilter filter) {
listenersLock.writeLock();
try {
ArrayMap<LogListener, Object[]> listenersCopy = new ArrayMap<>(listeners.getKeys(), listeners.getValues());
Object[] listenerObjects = listenersCopy.get(listener);
if (listenerObjects == null) {
// Only create a task queue for non-SynchronousLogListeners
SerializedTaskQueue taskQueue = (listener instanceof SynchronousLogListener) ? null : new SerializedTaskQueue(listener.toString());
listenerObjects = new Object[] {filter, taskQueue};
} else if (filter != listenerObjects[0]) {
// update the filter
listenerObjects[0] = filter;
}
listenersCopy.put(listener, listenerObjects);
recalculateFilters(listenersCopy);
listeners = listenersCopy;
} finally {
listenersLock.writeUnlock();
}
}
private void recalculateFilters(ArrayMap<LogListener, Object[]> listenersCopy) {
List<LogFilter> filtersList = new ArrayList<>();
int size = listenersCopy.size();
for (int i = 0; i < size; i++) {
Object[] listenerObjects = listenersCopy.getValue(i);
LogFilter filter = (LogFilter) listenerObjects[0];
if (filter == NULL_LOGGER_FILTER) {
filters = ALWAYS_LOG;
return;
}
filtersList.add(filter);
}
if (filtersList.isEmpty())
filters = null;
filters = filtersList.toArray(new LogFilter[filtersList.size()]);
}
void removeLogListener(LogListener listener) {
listenersLock.writeLock();
try {
ArrayMap<LogListener, Object[]> listenersCopy = new ArrayMap<>(listeners.getKeys(), listeners.getValues());
listenersCopy.remove(listener);
recalculateFilters(listenersCopy);
listeners = listenersCopy;
} finally {
listenersLock.writeUnlock();
}
}
Enumeration<?> getLog() {
if (history == null) {
return EMPTY_ENUMERATION;
}
synchronized (history) {
return Collections.enumeration(new ArrayList<>(history));
}
}
}