Bug 454204 - Equinox Built in Log Service has no history

Change-Id: I286c3648239168c09cc374e8890040ae6909bfcb
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/equinox/log/test/LogReaderServiceTest.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/equinox/log/test/LogReaderServiceTest.java
index bcd1b92..cfe80cd 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/equinox/log/test/LogReaderServiceTest.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/equinox/log/test/LogReaderServiceTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2013 IBM Corporation and others All rights reserved. This
+ * Copyright (c) 2007, 2014 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
@@ -8,7 +8,10 @@
  *******************************************************************************/
 package org.eclipse.equinox.log.test;
 
-import java.util.Hashtable;
+import java.io.File;
+import java.util.*;
+import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
+import org.eclipse.osgi.launch.Equinox;
 import org.eclipse.osgi.tests.OSGiTestsActivator;
 import org.eclipse.osgi.tests.bundles.AbstractBundleTests;
 import org.osgi.framework.*;
@@ -147,4 +150,74 @@
 		}
 		assertTrue(listener.getEntryX().getLevel() == LogService.LOG_INFO);
 	}
+
+	public void testLogHistory1() throws BundleException {
+		File config = OSGiTestsActivator.getContext().getDataFile(getName());
+		Map<String, Object> configuration = new HashMap<String, Object>();
+		configuration.put(Constants.FRAMEWORK_STORAGE, config.getAbsolutePath());
+		configuration.put(EquinoxConfiguration.PROP_LOG_HISTORY_MAX, "10");
+		Equinox equinox = new Equinox(configuration);
+		equinox.start();
+
+		try {
+			LogService testLog = equinox.getBundleContext().getService(equinox.getBundleContext().getServiceReference(LogService.class));
+			LogReaderService testReader = equinox.getBundleContext().getService(equinox.getBundleContext().getServiceReference(LogReaderService.class));
+			assertEquals("Expecting no logs.", 0, countLogEntries(testReader.getLog(), 0));
+			// log 9 things
+			for (int i = 0; i < 9; i++) {
+				testLog.log(LogService.LOG_WARNING, String.valueOf(i));
+			}
+			assertEquals("Wrong number of logs.", 9, countLogEntries(testReader.getLog(), 0));
+
+			// log 9 more things
+			for (int i = 9; i < 18; i++) {
+				testLog.log(LogService.LOG_WARNING, String.valueOf(i));
+			}
+
+			// should only be the last 10 logs (8 - 17)
+			assertEquals("Wrong number of logs.", 10, countLogEntries(testReader.getLog(), 8));
+		} finally {
+			try {
+				equinox.stop();
+			} catch (BundleException e) {
+				// ignore
+			}
+		}
+	}
+
+	public void testLogHistory2() throws BundleException {
+		File config = OSGiTestsActivator.getContext().getDataFile(getName());
+		Map<String, Object> configuration = new HashMap<String, Object>();
+		configuration.put(Constants.FRAMEWORK_STORAGE, config.getAbsolutePath());
+		Equinox equinox = new Equinox(configuration);
+		equinox.start();
+
+		try {
+			LogService testLog = equinox.getBundleContext().getService(equinox.getBundleContext().getServiceReference(LogService.class));
+			LogReaderService testReader = equinox.getBundleContext().getService(equinox.getBundleContext().getServiceReference(LogReaderService.class));
+			assertEquals("Expecting no logs.", 0, countLogEntries(testReader.getLog(), 0));
+			// log 9 things
+			for (int i = 0; i < 9; i++) {
+				testLog.log(LogService.LOG_WARNING, String.valueOf(i));
+			}
+			assertEquals("Wrong number of logs.", 0, countLogEntries(testReader.getLog(), 0));
+		} finally {
+			try {
+				equinox.stop();
+			} catch (BundleException e) {
+				// ignore
+			}
+		}
+	}
+
+	private int countLogEntries(Enumeration logEntries, int startingMessage) {
+		int count = 0;
+		while (logEntries.hasMoreElements()) {
+			LogEntry entry = (LogEntry) logEntries.nextElement();
+			assertEquals("Wrong log message.", String.valueOf(startingMessage), entry.getMessage());
+			startingMessage++;
+			count++;
+		}
+		return count;
+	}
 }
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
index 2925071..a8cfe68 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
@@ -190,6 +190,7 @@
 
 	public static final String PROP_MODULE_LOCK_TIMEOUT = "osgi.module.lock.timeout"; //$NON-NLS-1$
 	public static final String PROP_ALLOW_RESTRICTED_PROVIDES = "osgi.equinox.allow.restricted.provides"; //$NON-NLS-1$
+	public static final String PROP_LOG_HISTORY_MAX = "equinox.log.history.max"; //$NON-NLS-1$
 
 	private final static Collection<String> populateInitConfig = Arrays.asList(PROP_OSGI_ARCH, PROP_OSGI_OS, PROP_OSGI_WS, PROP_OSGI_NL, FRAMEWORK_OS_NAME, FRAMEWORK_OS_VERSION, FRAMEWORK_PROCESSOR, FRAMEWORK_LANGUAGE);
 
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java
index 5740650..02afb00 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2006, 2013 IBM Corporation and others.
+ * Copyright (c) 2006, 2014 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
@@ -71,7 +71,16 @@
 
 		if ("true".equals(environmentInfo.getConfiguration(EclipseStarter.PROP_CONSOLE_LOG))) //$NON-NLS-1$
 			logWriter.setConsoleLog(true);
-		logServiceManager = new LogServiceManager(logWriter, perfWriter);
+		String logHistoryMaxProp = environmentInfo.getConfiguration(EquinoxConfiguration.PROP_LOG_HISTORY_MAX);
+		int logHistoryMax = 0;
+		if (logHistoryMaxProp != null) {
+			try {
+				logHistoryMax = Integer.parseInt(logHistoryMaxProp);
+			} catch (NumberFormatException e) {
+				// ignore and use 0
+			}
+		}
+		logServiceManager = new LogServiceManager(logHistoryMax, logWriter, perfWriter);
 		eclipseLogFactory = new EquinoxLogFactory(logWriter, logServiceManager);
 		rootFrameworkLog = eclipseLogFactory.createFrameworkLog(null, logWriter);
 	}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogReaderServiceFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogReaderServiceFactory.java
index 9f39636..5fbf296 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogReaderServiceFactory.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogReaderServiceFactory.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2006, 2013 Cognos Incorporated, IBM Corporation and others
+ * Copyright (c) 2006, 2014 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
@@ -53,6 +53,8 @@
 	private ArrayMap<LogListener, Object[]> listeners = new ArrayMap<LogListener, Object[]>(5);
 	private LogFilter[] filters = null;
 	private final ThreadLocal<int[]> nestedCallCount = new ThreadLocal<int[]>();
+	private final LinkedList<LogEntry> history;
+	private final int maxHistory;
 
 	static boolean safeIsLoggable(LogFilter filter, Bundle bundle, String name, int level) {
 		try {
@@ -96,6 +98,15 @@
 		}
 	}
 
+	public ExtendedLogReaderServiceFactory(int maxHistory) {
+		this.maxHistory = maxHistory;
+		if (maxHistory > 0) {
+			history = new LinkedList<LogEntry>();
+		} else {
+			history = null;
+		}
+	}
+
 	public ExtendedLogReaderServiceImpl getService(Bundle bundle, ServiceRegistration<ExtendedLogReaderServiceImpl> registration) {
 		return new ExtendedLogReaderServiceImpl(this);
 	}
@@ -181,6 +192,7 @@
 
 	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 {
@@ -211,6 +223,17 @@
 		}
 	}
 
+	private void storeEntry(LogEntry logEntry) {
+		if (history != null) {
+			synchronized (history) {
+				if (history.size() == maxHistory) {
+					history.removeFirst();
+				}
+				history.addLast(logEntry);
+			}
+		}
+	}
+
 	void addLogListener(LogListener listener, LogFilter filter) {
 		listenersLock.writeLock();
 		try {
@@ -264,6 +287,11 @@
 	}
 
 	Enumeration<?> getLog() {
-		return EMPTY_ENUMERATION;
+		if (history == null) {
+			return EMPTY_ENUMERATION;
+		}
+		synchronized (history) {
+			return Collections.enumeration(new ArrayList<LogEntry>(history));
+		}
 	}
 }
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java
index e2259b6..788091b 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2006, 2012 Cognos Incorporated, IBM Corporation and others
+ * Copyright (c) 2006, 2014 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
@@ -24,12 +24,15 @@
 
 	private ServiceRegistration<?> logReaderServiceRegistration;
 	private ServiceRegistration<?> logServiceRegistration;
-	private final ExtendedLogReaderServiceFactory logReaderServiceFactory = new ExtendedLogReaderServiceFactory();
-	private final ExtendedLogServiceFactory logServiceFactory = new ExtendedLogServiceFactory(logReaderServiceFactory);
-	private final ExtendedLogServiceImpl systemBundleLog = logServiceFactory.getLogService(new MockSystemBundle());
+	private final ExtendedLogReaderServiceFactory logReaderServiceFactory;
+	private final ExtendedLogServiceFactory logServiceFactory;
+	private final ExtendedLogServiceImpl systemBundleLog;
 	private EventAdminAdapter eventAdminAdapter;
 
-	public LogServiceManager(LogListener... systemListeners) {
+	public LogServiceManager(int maxHistory, LogListener... systemListeners) {
+		logReaderServiceFactory = new ExtendedLogReaderServiceFactory(maxHistory);
+		logServiceFactory = new ExtendedLogServiceFactory(logReaderServiceFactory);
+		systemBundleLog = logServiceFactory.getLogService(new MockSystemBundle());
 		for (LogListener logListener : systemListeners) {
 			if (logListener instanceof LogFilter)
 				logReaderServiceFactory.addLogListener(logListener, (LogFilter) logListener);