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); + } + +}