Permalink
Browse files

major refactoring, added tests

  • Loading branch information...
1 parent 522bcc9 commit 93c2b833a51020347f3155f1694fa6a0ceb88447 @leonid-shevtsov committed Apr 6, 2012
View
@@ -25,6 +25,17 @@ It's your responsibility to rotate the logs
05 00 * * * ~/scripts/jenkins.php /var/log/apache2/*.error.log | mail -s "Jenkins report" YOURMAIL@GMAIL.com
+## Installation
+
+The whole script is a self-contained PHP file, so download `jenkins.php` from this repository and put it in a
+convenient place on your system.
+
+## Testing
+
+PHPUnit tests coverage is provided. To run tests:
+
+ phpunit test
+
## Changelog
### 2012-04-06 - 0.3
View
@@ -1,115 +1,230 @@
#!env php
<?php
-$log_files = parseCommandLine();
-
-if (empty($log_files)) {
- echo "Usage: jenkins.php <log_file> <log_file> ...\n";
- die;
+if (__FILE__ == realpath($_SERVER['SCRIPT_FILENAME'])) {
+ $cli = new JenkinsLogAnalyzer_CLI($_SERVER['argc'], $_SERVER['argv']);
+ exit($cli->run());
}
-foreach ($log_files as $log_filename) {
- $log_file = fopen($log_filename,'r');
-
- $error_times = array();
- $error_counts = array();
+
+class JenkinsLogAnalyzer {
+ const VERSION = '0.3';
+
+ public $errors;
+ public $line_count = 0;
+
+ function __construct($log_stream, $error_factory = null, $error_store = null) {
+ $this->log_stream = $log_stream;
+ $this->error_factory = $error_factory ? $error_factory : new JenkinsLogAnalyzer_ErrorFactory();
+ $this->errors = $error_store ? $error_store : new JenkinsLogAnalyzer_ErrorStore();
+ }
+
+ function process() {
+ while ($line = trim(fgets($this->log_stream))) {
+ $this->line_count += 1;
+ $this->processLine($line);
+ }
+ }
- $total_count = 0;
- $php_count = 0;
- $unique_count = 0;
+ private function processLine($line) {
+ if ($error = $this->error_factory->fromLine($line)) {
+ $this->errors->register($error);
+ }
+ }
+}
+
+class JenkinsLogAnalyzer_ErrorStore {
+ public $errors_hash = array();
+
+ function register($error) {
+ if (!array_key_exists($error->key, $this->errors_hash)) {
+ $this->errors_hash[$error->key] = $error;
+ } else {
+ $this->errors_hash[$error->key]->register($error->time);
+ }
+ }
- while ($line = trim(fgets($log_file))) {
- ++$total_count;
- if (preg_match('@^\[([^\]]+)] \[[^\]]+] \[[^\]]+] PHP ([^:]+): (.+) in (.+) on line (\d+)(, referer: (.+)|)$@',$line,$matches)) {
- //it's a php error message
- ++$php_count;
-
- $error_msg = trim($matches[2]) . "\n" . trim($matches[3]) . "\n" . trim($matches[4]) . "\n" . trim($matches[5]);
- $error_times[$error_msg] = date('d.m.y H:i',strtotime( $matches[1] ));
- if (!isset($error_counts[$error_msg])) {
- $error_counts[$error_msg] = 1;
- } else {
- ++$error_counts[$error_msg];
+ function count() {
+ if (!isset($this->_php_error_count)) {
+ $this->_php_error_count=0;
+ foreach($this->errors_hash as $error) {
+ $this->_php_error_count += $error->occurence_count;
}
}
-
+ return $this->_php_error_count;
}
-
- fclose($log_file);
- arsort($error_counts);
-
- $error_messages = array_keys( $error_counts );
-
- $unique_count = count($error_messages);
-
- ?>
- <h1>Jenkins report for <?php echo htmlspecialchars($log_filename);?></h1>
- Total lines in log: <b><?php echo $total_count?></b><br>
- Lines recognized as PHP errors: <b><?php echo $php_count?></b><br>
- Unique PHP error messages: <b><?php echo $unique_count?></b><br>
+ function uniqueCount() {
+ return sizeof($this->errors_hash);
+ }
+}
+
+class JenkinsLogAnalyzer_Error {
+ public $type;
+ public $message;
+ public $filename;
+ public $line;
+ public $occurence_count = 1;
+ public $occurence_times = array();
+ public $key;
+
+ function __construct($options) {
+ $this->type = $options['type'];
+ $this->message = $options['message'];
+ $this->filename = $options['filename'];
+ $this->line = $options['line'];
+ $this->occurence_times[]= $options['time'];
+ $this->key = $this->makeKey();
+ }
+
+ public function register($time) {
+ $this->occurence_times[]= $time;
+ $this->occurence_count += 1;
+ }
+
+ public function firstOccurence() {
+ sort($this->occurence_times);
+ return $this->occurence_times[0];
+ }
-<?php if ($unique_count == 0) { ?>
- Hooray, no errors!<br>
-<?php } else { ?>
- <h2>Error messages</h2>
- <?php foreach ($error_messages as $message) {
- $count = $error_counts[$message];
- $time = $error_times[$message];
-
- $message = explode("\n",$message);
-
- $colors = array(
- 'Notice' => '#4a6d00',
- 'Error' => '#ff9c00',
- 'Warning' => '#dace48',
- 'Fatal error' => '#cb0808'
- );
-
- $message_color = isset($colors[$message[0]]) ? $colors[$message[0]] : '#000000';
-
- ?>
- <span style="color:<?php echo $message_color ?>"><b><?php echo htmlspecialchars($message[0]); ?>:</b></span>
- <?php echo htmlspecialchars($message[1]); ?>
- <code>[<?php echo htmlspecialchars($message[2]); ?>:<?php echo htmlspecialchars($message[3]); ?>]</code>
- <span style="color:#aaaaaa">(<?php echo $count;?>#
- @<?php echo $time; ?>)</span><br>
-<?php }
-}
+ public function lastOccurence() {
+ sort($this->occurence_times);
+ return $this->occurence_times[sizeof($this->occurence_times)-1];
+ }
+
+ private function makeKey() {
+ return md5( $this->type . $this->message . $this->filename . $this->line );
+ }
}
-?>
-<hr>
-Report generated at <?php date_default_timezone_set('UTC'); echo date('r'); ?><br>
-<a href="http://github.com/leonid-shevtsov/jenkins.php">Jenkins</a> 0.3 - an PHP-on-Apache error log analyzer<br>
-&copy; 2012 <a href="http://leonid.shevtsov.me">Leonid Shevtsov</a>
-<?php
-
-function parseCommandLine() {
- $log_files = array();
- for ($i = 1; $i < $_SERVER['argc']; ++$i) {
- if ($_SERVER['argv'][$i][0] == '-') {
- $code = $_SERVER['argv'][$i][1];
- $value = substr($_SERVER['argv'][$i],2);
+
+class JenkinsLogAnalyzer_ErrorFactory {
+ function fromLine($line) {
+ preg_match('@^\[([^\]]+)] \[[^\]]+] \[[^\]]+] PHP ([^:]+): (.+) in (.+) on line (\d+)(, referer: (.+)|)$@',$line,$matches);
+
+ if (sizeof($matches) > 0) {
+ return new JenkinsLogAnalyzer_Error(array(
+ 'time' => strtotime($matches[1]),
+ 'type' => trim($matches[2]),
+ 'message' => trim($matches[3]),
+ 'filename' => trim($matches[4]),
+ 'line' => trim($matches[5])
+ ));
+ } else {
+ return null;
+ }
+ }
+}
+
+class JenkinsLogAnalyzer_HtmlGenerator {
+ private $colors = array(
+ 'Notice' => '#4a6d00',
+ 'Error' => '#ff9c00',
+ 'Warning' => '#dace48',
+ 'Fatal error' => '#cb0808'
+ );
+
+ function __construct($log_filename, $log_analyzer) {
+ $this->log_filename = $log_filename;
+ $this->log_analyzer = $log_analyzer;
+ $this->errors = $log_analyzer->errors;
+ }
+
+ function generateReport() {
+ ob_start();
+ ?>
+ <h1>Jenkins report for <?php echo htmlspecialchars($this->log_filename);?></h1>
+
+ Total lines in log: <b><?php echo $this->log_analyzer->line_count ?></b><br>
+ Lines recognized as PHP errors: <b><?php echo $this->errors->count() ?></b><br>
+ Unique PHP error messages: <b><?php echo $this->errors->uniqueCount() ?></b><br>
- switch($code) {
- case 'm':
- echo "DEPRECATED - use mail command to mail logs";
- die;
- break;
- case 'r':
- echo "DEPRECATED - use logrotate";
- die;
- break;
- default:
- echo "Unrecognized parameter: -$code\n";
- die;
+ <?php if ($this->errors->uniqueCount() == 0) { ?>
+ Hooray, no errors!<br>
+ <?php } else { ?>
+ <h2>Error messages</h2>
+ <?php foreach ($this->errors->errors_hash as $error) { ?>
+ <span style="color:<?php echo $this->errorColor($error) ?>"><b><?php echo htmlspecialchars($error->type); ?>:</b></span>
+ <?php echo htmlspecialchars($error->message); ?>
+ <code>[<?php echo htmlspecialchars($error->filename); ?>:<?php echo htmlspecialchars($error->line); ?>]</code>
+ <span style="color:#aaaaaa">(<?php echo $error->occurence_count;?>#
+ @<?php echo date('d.m.y H:i', $error->lastOccurence()); ?>)</span><br>
+ <?php }
+ } ?>
+ <hr>
+ Report generated at <?php echo date('r'); ?><br>
+ <a href="http://github.com/leonid-shevtsov/jenkins.php">Jenkins</a> <?php echo JenkinsLogAnalyzer::VERSION ?> - an PHP-on-Apache error log analyzer<br>
+ &copy; 2012 <a href="http://leonid.shevtsov.me">Leonid Shevtsov</a>
+ <?php
+ $report = ob_get_contents();
+ ob_end_clean();
+ return $report;
+ }
+
+ private function errorColor($error) {
+ return isset($this->colors[$error->type]) ? $this->colors[$error->type] : '#000000';
+ }
+}
+
+class JenkinsLogAnalyzer_CLI {
+ function __construct($argc, $argv) {
+ $this->argc = $argc;
+ $this->argv = $argv;
+ }
+
+ function run() {
+ $this->parseCommandLine();
+ if ($this->log_files) {
+ foreach ($this->log_files as $log_file) {
+ $this->processLogFile($log_file);
}
+ return 0;
} else {
- $log_files[] = $_SERVER['argv'][$i];
+ $this->printBanner();
+ return -1;
}
- }
- return $log_files;
+ }
+
+ private function parseCommandLine() {
+ $log_files = array();
+ for ($i = 1; $i < $this->argc; ++$i) {
+ if ($this->argv[$i][0] == '-') {
+ $code = $this->argv[$i][1];
+ $value = substr($this->argv[$i],2);
+
+ switch($code) {
+ case 'm':
+ echo "DEPRECATED - use mail command to mail logs";
+ die;
+ break;
+ case 'r':
+ echo "DEPRECATED - use logrotate";
+ die;
+ break;
+ default:
+ echo "Unrecognized parameter: -$code\n";
+ die;
+ }
+ } else {
+ $log_files[] = $this->argv[$i];
+ }
+ }
+ return $this->log_files = $log_files;
+ }
+
+ private function printBanner() {
+ echo "Usage: jenkins.php <log_file> <log_file> ...\n";
+ }
+
+ private function processLogFile($log_filename) {
+ $log_file = fopen($log_filename,'r');
+ $analyzer = new JenkinsLogAnalyzer($log_file);
+ $analyzer->process();
+ fclose($log_file);
+
+ $generator = new JenkinsLogAnalyzer_HtmlGenerator($log_filename, $analyzer);
+ print $generator->generateReport();
+ }
}
?>
@@ -0,0 +1,34 @@
+<?php
+
+require_once 'jenkins.php';
+
+class JenkinsLogAnalyzerTest extends PHPUnit_Framework_TestCase {
+ public function testProcessingLineByLine() {
+ $error_class = $this->getMock('ErrorFactory', array('fromLine'));
+ $error_class->expects($this->exactly(3))->method('fromLine');
+
+ $analyzer = new JenkinsLogAnalyzer(fopen("data://text/plain,".urlencode("1\n2\n3"),'r'), $error_class);
+ $analyzer->process();
+ }
+
+ public function testStoringIntoStore() {
+ $error_factory = $this->getMock('ErrorFactory', array('fromLine'));
+ $error_factory->expects($this->any())->method('fromLine')->will($this->returnValue('TEST ERROR'));
+
+ $error_store = $this->getMock('ErrorStore', array('register'));
+ $error_store->expects($this->once())->method('register')->with($this->equalTo('TEST ERROR'));
+
+ $analyzer = new JenkinsLogAnalyzer(fopen("data://text/plain,".urlencode("1"),'r'), $error_factory, $error_store);
+ $analyzer->process();
+ }
+
+ public function testLineCount() {
+ $error_class = $this->getMock('ErrorFactory', array('fromLine'));
+ $error_class->expects($this->any())->method('fromLine');
+
+ $analyzer = new JenkinsLogAnalyzer(fopen("data://text/plain,".urlencode("1\n2\n3"),'r'), $error_class);
+ $analyzer->process();
+
+ $this->assertEquals(3, $analyzer->line_count);
+ }
+}
Oops, something went wrong.

0 comments on commit 93c2b83

Please sign in to comment.