diff --git a/config-templates/config.php b/config-templates/config.php
index 218840251365a4edce695e36cfc4279e176b3f71..9df78a4a22a550b8843758ac6197710b310753b8 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -140,8 +140,29 @@ $config = array (
 	/* Logging: file - Logfilename in the loggingdir from above.
 	 */
 	'logging.logfile'		=> 'simplesamlphp.log',
-	
-	
+
+	/* (New) statistics output configuration.
+	 *
+	 * This is an array of outputs. Each output has at least a 'class' option, which
+	 * selects the output.
+	 */
+	'statistics.out' => array(
+		// Log statistics to the normal log.
+		/*
+		array(
+			'class' => 'core:Log',
+			'level' => 'notice',
+		),
+		*/
+		// Log statistics to files in a directory. One file per day.
+		/*
+		array(
+			'class' => 'core:File',
+			'directory' => '/var/log/stats',
+		),
+		*/
+	),
+
 
 	/*
 	 * Enable
diff --git a/lib/SimpleSAML/Stats.php b/lib/SimpleSAML/Stats.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e3b3af412f2e3ad895e8bd441e158c96fb3d7a0
--- /dev/null
+++ b/lib/SimpleSAML/Stats.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Statistics handler class.
+ *
+ * This class is responsible for taking a statistics event and logging it.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SimpleSAML_Stats {
+
+	/**
+	 * Whether this class is initialized.
+	 * @var boolean
+	 */
+	private static $initialized = FALSE;
+
+
+	/**
+	 * The statistics output callbacks.
+	 * @var array
+	 */
+	private static $outputs = NULL;
+
+
+	/**
+	 * Create an output from a configuration object.
+	 *
+	 * @param SimpleSAML_Configuration $config  The configuration object.
+	 * @return
+	 */
+	private static function createOutput(SimpleSAML_Configuration $config) {
+		$cls = $config->getString('class');
+		$cls = SimpleSAML_Module::resolveClass($cls, 'Stats_Output', 'SimpleSAML_Stats_Output');
+
+		$output = new $cls($config);
+		return $output;
+	}
+
+
+	/**
+	 * Initialize the outputs.
+	 */
+	private static function initOutputs() {
+
+		$config = SimpleSAML_Configuration::getInstance();
+		$outputCfgs = $config->getConfigList('statistics.out', array());
+
+		self::$outputs = array();
+		foreach ($outputCfgs as $cfg) {
+			self::$outputs[] = self::createOutput($cfg);
+		}
+	}
+
+
+	/**
+	 * Notify about an event.
+	 *
+	 * @param string $event  The event.
+	 * @param array $data  Event data. Optional.
+	 */
+	public static function log($event, array $data = array()) {
+		assert('is_string($event)');
+		assert('!isset($data["op"])');
+		assert('!isset($data["time"])');
+		assert('!isset($data["_id"])');
+
+		if (!self::$initialized) {
+			self::initOutputs();
+			self::$initialized = TRUE;
+		}
+
+		if (empty(self::$outputs)) {
+			/* Not enabled. */
+			return;
+		}
+
+		$data['op'] = $event;
+		$data['time'] = microtime(TRUE);
+
+		/* The ID generation is designed to cluster IDs related in time close together. */
+		$int_t = (int)$data['time'];
+		$hd = SimpleSAML_Utilities::generateRandomBytes(16);
+		$data['_id'] = sprintf('%016x%s', $int_t, bin2hex($hd));
+
+		foreach (self::$outputs as $out) {
+			$out->emit($data);
+		}
+	}
+
+}
diff --git a/lib/SimpleSAML/Stats/Output.php b/lib/SimpleSAML/Stats/Output.php
new file mode 100644
index 0000000000000000000000000000000000000000..f1ecc1f2f356aa21cb6999d8c0703ab06f025f77
--- /dev/null
+++ b/lib/SimpleSAML/Stats/Output.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Interface for statistics outputs.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SimpleSAML_Stats_Output {
+
+	/**
+	 * Initialize the output.
+	 *
+	 * @param SimpleSAML_Configuration $config  The configuration for this output.
+	 */
+	public function __construct(SimpleSAML_Configuration $config) {
+		/* Do nothing by default. */
+	}
+
+
+	/**
+	 * Write a stats event.
+	 *
+	 * @param array $data  The event.
+	 */
+	abstract public function emit(array $data);
+
+}
diff --git a/modules/core/lib/Stats/Output/File.php b/modules/core/lib/Stats/Output/File.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ed31424d7dc8dd5400f0347efc12cf99f711029
--- /dev/null
+++ b/modules/core/lib/Stats/Output/File.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * Statistics logger that writes to a set of log files
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_core_Stats_Output_File extends SimpleSAML_Stats_Output {
+
+	/**
+	 * The log directory.
+	 * @var string
+	 */
+	private $logDir;
+
+
+	/**
+	 * The file handle for the current file.
+	 * @var resource
+	 */
+	private $file = NULL;
+
+	/**
+	 * The current file date.
+	 * @var string
+	 */
+	private $fileDate = NULL;
+
+
+	/**
+	 * Initialize the output.
+	 *
+	 * @param SimpleSAML_Configuration $config  The configuration for this output.
+	 */
+	public function __construct(SimpleSAML_Configuration $config) {
+
+		$this->logDir = $config->getPathValue('directory');
+		if ($this->logDir === NULL) {
+			throw new Exception('Missing "directory" option for core:File');
+		}
+		if (!is_dir($this->logDir)) {
+			throw new Exception('Could not find log directory: ' . var_export($this->logDir, TRUE));
+		}
+
+	}
+
+
+	/**
+	 * Open a log file.
+	 *
+	 * @param string $date  The date for the log file.
+	 */
+	private function openLog($date) {
+		assert('is_string($date)');
+
+		if ($this->file !== NULL && $this->file !== FALSE) {
+			fclose($this->file);
+			$this->file = NULL;
+		}
+
+		$fileName = $this->logDir . '/' . $date . '.log';
+		$this->file = @fopen($fileName, 'a');
+		if ($this->file === FALSE) {
+			throw new SimpleSAML_Error_Exception('Error opening log file: ' . var_export($fileName, TRUE));
+		}
+
+		/* Disable output buffering. */
+		stream_set_write_buffer($this->file, 0);
+
+		$this->fileDate = $date;
+	}
+
+
+	/**
+	 * Write a stats event.
+	 *
+	 * @param array $data  The event.
+	 */
+	public function emit(array $data) {
+		assert('isset($data["time"])');
+
+		$time = $data['time'];
+		$frac_time = $time - (int)$time;
+
+		$timestamp = gmdate('Y-m-d\TH:i:s', $time) . sprintf('%.03fZ', $frac_time);
+
+		$outDate = substr($timestamp, 0, 10); /* The date-part of the timstamp. */
+
+		if ($outDate !== $this->fileDate) {
+			$this->openLog($outDate);
+		}
+
+		$line = $timestamp . ' ' . json_encode($data) . "\n";
+		fwrite($this->file, $line);
+	}
+
+}
diff --git a/modules/core/lib/Stats/Output/Log.php b/modules/core/lib/Stats/Output/Log.php
new file mode 100644
index 0000000000000000000000000000000000000000..d83ff33e8e3dc514c0122753735ed1a5fd2efd63
--- /dev/null
+++ b/modules/core/lib/Stats/Output/Log.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Statistics logger that writes to the default logging handler.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_core_Stats_Output_Log extends SimpleSAML_Stats_Output {
+
+	/**
+	 * The logging function we should call.
+	 * @var callback
+	 */
+	private $logger;
+
+
+	/**
+	 * Initialize the output.
+	 *
+	 * @param SimpleSAML_Configuration $config  The configuration for this output.
+	 */
+	public function __construct(SimpleSAML_Configuration $config) {
+
+		$logLevel = $config->getString('level', 'notice');
+		$this->logger = array('SimpleSAML_Logger', $logLevel);
+		if (!is_callable($this->logger)) {
+			throw new Exception('Invalid log level: ' . var_export($logLevel, TRUE));
+		}
+	}
+
+
+	/**
+	 * Write a stats event.
+	 *
+	 * @param string $data  The event (as a JSON string).
+	 */
+	public function emit(array $data) {
+		$str_data = json_encode($data);
+		call_user_func($this->logger, 'EVENT ' . $str_data);
+	}
+
+}