|
@@ -0,0 +1,204 @@
|
|
|
|
+<?php
|
|
|
|
+/**
|
|
|
|
+ * @file
|
|
|
|
+ * Contains the Redis\Logger\Reader class
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+namespace Redis\Logger;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * This class reads the logger information stored in Redis:
|
|
|
|
+ *
|
|
|
|
+ * - messages templates
|
|
|
|
+ * - channels used in message templates
|
|
|
|
+ * - messages for a given template
|
|
|
|
+ *
|
|
|
|
+ * @package Redis\Logger
|
|
|
|
+ */
|
|
|
|
+class Reader {
|
|
|
|
+ /**
|
|
|
|
+ * @var \Redis
|
|
|
|
+ */
|
|
|
|
+ protected $redis;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @var \Redis\Logger\Settings
|
|
|
|
+ */
|
|
|
|
+ protected $settings;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @var string[]
|
|
|
|
+ */
|
|
|
|
+ protected $templateCache = NULL;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @param \Redis $redis
|
|
|
|
+ * @param \Redis\Logger\Settings $settings
|
|
|
|
+ * We pass this constant instance in order to allow overriding it, say for
|
|
|
|
+ * tests.
|
|
|
|
+ */
|
|
|
|
+ public function __construct(\Redis $redis, Settings $settings = NULL) {
|
|
|
|
+ $this->redis = $redis;
|
|
|
|
+ if (!isset($settings)) {
|
|
|
|
+ $settings = new Settings();
|
|
|
|
+ }
|
|
|
|
+ $this->settings = $settings;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Load and cache the list of templates in the instance for this page cycle.
|
|
|
|
+ *
|
|
|
|
+ * Note that the templates are in Redis storage format, including the prefix,
|
|
|
|
+ * to link directly to the results.
|
|
|
|
+ */
|
|
|
|
+ protected function ensureTemplateCache() {
|
|
|
|
+ if (!isset($this->templateCache)) {
|
|
|
|
+ $this->templateCache = $this->scan($this->settings->getRedisPattern(), 0, $this->settings->getScanBatchSize());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Return the list of channels. Assumed to be constant for a given page cycle.
|
|
|
|
+ *
|
|
|
|
+ * @return string[int]
|
|
|
|
+ */
|
|
|
|
+ public function getChannels() {
|
|
|
|
+ $this->ensureTemplateCache();
|
|
|
|
+ $channels = array();
|
|
|
|
+ foreach ($this->templateCache as $key) {
|
|
|
|
+ preg_match($this->settings->getChannelRegex(), $key, $matches);
|
|
|
|
+ $channel = $matches[1];
|
|
|
|
+ $channels[$channel] = $channel;
|
|
|
|
+ }
|
|
|
|
+ ksort($channels);
|
|
|
|
+ return $channels;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @param $template
|
|
|
|
+ * The Redis key for a template.
|
|
|
|
+ * @param int $skip
|
|
|
|
+ * The number of pages to skip.
|
|
|
|
+ * @param int $entries_per_page
|
|
|
|
+ * The maximum number of entries in a page.
|
|
|
|
+ */
|
|
|
|
+ public function getEntries($template, $skip = 0, $entries_per_page = 0) {
|
|
|
|
+ $entries_per_page = $this->settings->getEntriesPerPage($entries_per_page);
|
|
|
|
+
|
|
|
|
+ // Redis expect actual number of entries in the RANGE parameters.
|
|
|
|
+ $start = $skip * $entries_per_page;
|
|
|
|
+ $end = $start + $entries_per_page;
|
|
|
|
+ $entries = $this->redis->lRange($template, $start, $end);
|
|
|
|
+ return $entries;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @param mixed $criterium
|
|
|
|
+ * Filter will always pass if criterium is not set, but be checked otherwise.
|
|
|
|
+ * @param string $regex
|
|
|
|
+ * The regex against which to match to pass the filter.
|
|
|
|
+ * @param $string
|
|
|
|
+ * The string to match against the regex.
|
|
|
|
+ *
|
|
|
|
+ * @return bool
|
|
|
|
+ */
|
|
|
|
+ protected function passesFilter($criterium, $regex, $string) {
|
|
|
|
+ if (!isset($criterium)) {
|
|
|
|
+ $ret = TRUE;
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ $sts = preg_match($regex, $string, $matches);
|
|
|
|
+ $ret = $sts && ($matches[1] == $criterium);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @param null $channel
|
|
|
|
+ * @param int $severity
|
|
|
|
+ * WATCHDOG_* : 0 to 7
|
|
|
|
+ * @param int $skip
|
|
|
|
+ * Number of pages to skip.
|
|
|
|
+ * @param int $entries_per_page
|
|
|
|
+ * Maximum number of entries per page.
|
|
|
|
+ */
|
|
|
|
+ public function getTemplates($channel = NULL, $severity = NULL, $skip = 0, $entries_per_page = 0) {
|
|
|
|
+ $this->ensureTemplateCache();
|
|
|
|
+ $matches = array();
|
|
|
|
+ $count = 0;
|
|
|
|
+ $channelRegex = $this->settings->getChannelRegex();
|
|
|
|
+ $severityRegex = $this->settings->getSeverityRegex();
|
|
|
|
+
|
|
|
|
+ $entries_per_page = $this->settings->getEntriesPerPage($entries_per_page);
|
|
|
|
+ $start = $skip * $entries_per_page;
|
|
|
|
+ $end = $start + $entries_per_page;
|
|
|
|
+
|
|
|
|
+ foreach ($this->templateCache as $template) {
|
|
|
|
+ if (!$this->passesFilter($channel, $channelRegex, $template)) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!$this->passesFilter($severity, $severityRegex, $template)) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $count++;
|
|
|
|
+ if ($count >= $skip) {
|
|
|
|
+ if ($count >= $end) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ $matches[] = $template;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $matches;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Perform a complete Redis SCAN series.
|
|
|
|
+ *
|
|
|
|
+ * @param string $pattern
|
|
|
|
+ * The Redis SCAN MATCH optional argument.
|
|
|
|
+ * @param int $max
|
|
|
|
+ * Maximum number of matches to be returned. Cursor is left dangling if $max
|
|
|
|
+ * is lower than the maximum number of available results: only the minimum
|
|
|
|
+ * number of SCAN iterations needed to reach $max results will be performed.
|
|
|
|
+ * @param int $batch_suggestion
|
|
|
|
+ * The Redis SCAN COUNT optional argument.
|
|
|
|
+ *
|
|
|
|
+ * @return string[]
|
|
|
|
+ */
|
|
|
|
+ public function scan($pattern = '*', $max = 0, $batch_suggestion = 10) {
|
|
|
|
+ $redis = $this->redis;
|
|
|
|
+ $saved_scan_option = $redis->getOption(\Redis::OPT_SCAN);
|
|
|
|
+ $redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
|
|
|
|
+
|
|
|
|
+ $ret = array();
|
|
|
|
+ // Initialize our iterator to NULL.
|
|
|
|
+ $scan_it = NULL;
|
|
|
|
+
|
|
|
|
+ $count = 0;
|
|
|
|
+ $request_count = 1;
|
|
|
|
+ // Retry when we get no keys back.
|
|
|
|
+ while ($arr_keys = $redis->scan($scan_it, $pattern, $batch_suggestion)) {
|
|
|
|
+ $request_count++;
|
|
|
|
+ foreach ($arr_keys as $str_key) {
|
|
|
|
+ $ret[] = $str_key;
|
|
|
|
+ if ($max) {
|
|
|
|
+ $count++;
|
|
|
|
+ if ($count >= $max) {
|
|
|
|
+ break 2; // Break out of foreach + while.
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($saved_scan_option != \Redis::SCAN_RETRY) {
|
|
|
|
+ $redis->setOption(\Redis::OPT_SCAN, $saved_scan_option);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $ret;
|
|
|
|
+ }
|
|
|
|
+}
|