| /******************************************************************************* |
| * 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)); |
| } |
| } |
| } |