Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
MonitorLogsCommand
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 14
1722
0.00% covered (danger)
0.00%
0 / 1
 getAllowedAppEnvs
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handle
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
110
 checkEmergencies
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 checkAlerts
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 checkCritical
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 checkManyErrors
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 checkManyWarnings
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 checkManyNotices
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isEmergencyLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 isAlertLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 isCriticalLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 isErrorLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 isWarningLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 isNoticeLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace Olz\Command;
4
5use Olz\Command\Common\OlzCommand;
6use Symfony\Component\Console\Attribute\AsCommand;
7use Symfony\Component\Console\Command\Command;
8use Symfony\Component\Console\Input\InputInterface;
9use Symfony\Component\Console\Output\OutputInterface;
10
11#[AsCommand(name: 'olz:monitor-logs')]
12class MonitorLogsCommand extends OlzCommand {
13    /** @return array<string> */
14    protected function getAllowedAppEnvs(): array {
15        return ['dev', 'test', 'staging', 'prod'];
16    }
17
18    protected function handle(InputInterface $input, OutputInterface $output): int {
19        $private_path = $this->envUtils()->getPrivatePath();
20        $logs_path = "{$private_path}logs/";
21        if (!is_dir($logs_path)) {
22            throw new \Exception("Expected {$logs_path} to be a directory");
23        }
24
25        $last_two_merged_log_file_contents = "";
26        $merged_log_index = 0;
27        $filenames = scandir($logs_path, SCANDIR_SORT_DESCENDING) ?: [];
28        foreach ($filenames as $filename) {
29            if ($merged_log_index > 1) {
30                break;
31            }
32            if (preg_match('/^merged-.*\.log$/', $filename)) {
33                $last_two_merged_log_file_contents = file_get_contents("{$logs_path}{$filename}").$last_two_merged_log_file_contents;
34                $merged_log_index++;
35            }
36        }
37
38        $now = new \DateTime();
39        $minus_one_hour = \DateInterval::createFromDateString("-1 hours");
40        $one_hour_ago = $now->add($minus_one_hour);
41
42        $logs_in_last_hour = [];
43
44        foreach (explode("\n", $last_two_merged_log_file_contents) as $line) {
45            $res = preg_match('/^\[([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})/', $line, $matches);
46            if ($res) {
47                $line_timestamp = strtotime($matches[1]) ?: 0;
48                if ($line_timestamp > $one_hour_ago->getTimestamp()) {
49                    $logs_in_last_hour[] = $line;
50                }
51            }
52        }
53
54        $output->writeln("Last hour: ".count($logs_in_last_hour)." logs");
55
56        $output->writeln("Last hour: ".count(array_filter($logs_in_last_hour, function ($line) { return $this->isEmergencyLine($line); }))." emergency logs");
57
58        $output->writeln("Last hour: ".count(array_filter($logs_in_last_hour, function ($line) { return $this->isAlertLine($line); }))." alert logs");
59
60        $output->writeln("Last hour: ".count(array_filter($logs_in_last_hour, function ($line) { return $this->isCriticalLine($line); }))." critical logs");
61
62        $output->writeln("Last hour: ".count(array_filter($logs_in_last_hour, function ($line) { return $this->isErrorLine($line); }))." error logs");
63
64        $output->writeln("Last hour: ".count(array_filter($logs_in_last_hour, function ($line) { return $this->isWarningLine($line); }))." warning logs");
65
66        $output->writeln("Last hour: ".count(array_filter($logs_in_last_hour, function ($line) { return $this->isNoticeLine($line); }))." notice logs");
67
68        $this->checkEmergencies($logs_in_last_hour);
69        $this->checkAlerts($logs_in_last_hour);
70        $this->checkCritical($logs_in_last_hour);
71        $this->checkManyErrors($logs_in_last_hour);
72        $this->checkManyWarnings($logs_in_last_hour);
73        $this->checkManyNotices($logs_in_last_hour);
74
75        $output->writeln("OK:");
76        return Command::SUCCESS;
77    }
78
79    /**
80     * @param array<string> $logs_in_last_hour
81     */
82    protected function checkEmergencies(array $logs_in_last_hour): void {
83        if (count(array_filter($logs_in_last_hour, function ($line) { return $this->isEmergencyLine($line); })) > 0) {
84            throw new \Exception("Expected no emergencies");
85        }
86    }
87
88    /**
89     * @param array<string> $logs_in_last_hour
90     */
91    protected function checkAlerts(array $logs_in_last_hour): void {
92        if (count(array_filter($logs_in_last_hour, function ($line) { return $this->isAlertLine($line); })) > 0) {
93            throw new \Exception("Expected no alerts");
94        }
95    }
96
97    /**
98     * @param array<string> $logs_in_last_hour
99     */
100    protected function checkCritical(array $logs_in_last_hour): void {
101        if (count(array_filter($logs_in_last_hour, function ($line) { return $this->isCriticalLine($line); })) > 0) {
102            throw new \Exception("Expected no critical log entries");
103        }
104    }
105
106    /**
107     * @param array<string> $logs_in_last_hour
108     */
109    protected function checkManyErrors(array $logs_in_last_hour): void {
110        $limit_per_hour = 1;
111
112        $errors_per_hour = count(array_filter($logs_in_last_hour, function ($line) { return $this->isErrorLine($line); }));
113        if ($errors_per_hour > $limit_per_hour) {
114            throw new \Exception("Expected fewer error log entries per hour ({$errors_per_hour} > {$limit_per_hour})");
115        }
116    }
117
118    /**
119     * @param array<string> $logs_in_last_hour
120     */
121    protected function checkManyWarnings(array $logs_in_last_hour): void {
122        $limit_per_hour = 10;
123
124        $warnings_per_hour = count(array_filter($logs_in_last_hour, function ($line) { return $this->isWarningLine($line); }));
125        if ($warnings_per_hour > $limit_per_hour) {
126            throw new \Exception("Expected fewer warning log entries per hour ({$warnings_per_hour} > {$limit_per_hour})");
127        }
128    }
129
130    /**
131     * @param array<string> $logs_in_last_hour
132     */
133    protected function checkManyNotices(array $logs_in_last_hour): void {
134        $limit_per_hour = 100;
135
136        $notices_per_hour = count(array_filter($logs_in_last_hour, function ($line) { return $this->isNoticeLine($line); }));
137        if ($notices_per_hour > $limit_per_hour) {
138            throw new \Exception("Expected fewer notice log entries per hour ({$notices_per_hour} > {$limit_per_hour})");
139        }
140    }
141
142    protected function isEmergencyLine(string $line): bool {
143        return preg_match('/\.EMERGENCY\:/', $line) && !preg_match('/Tool\:\w+-monitoring\.EMERGENCY\:/', $line) && !preg_match('/Olz\\\Command\\\Monitor/', $line);
144    }
145
146    protected function isAlertLine(string $line): bool {
147        return preg_match('/\.ALERT\:/', $line) && !preg_match('/Tool\:\w+-monitoring\.ALERT\:/', $line) && !preg_match('/Olz\\\Command\\\Monitor/', $line);
148    }
149
150    protected function isCriticalLine(string $line): bool {
151        return preg_match('/\.CRITICAL\:/', $line) && !preg_match('/Tool\:\w+-monitoring\.CRITICAL\:/', $line) && !preg_match('/Olz\\\Command\\\Monitor/', $line);
152    }
153
154    protected function isErrorLine(string $line): bool {
155        return preg_match('/\.ERROR\:/', $line) && !preg_match('/Tool\:\w+-monitoring\.ERROR\:/', $line) && !preg_match('/Olz\\\Command\\\Monitor/', $line);
156    }
157
158    protected function isWarningLine(string $line): bool {
159        return preg_match('/\.WARNING\:/', $line) && !preg_match('/Tool\:\w+-monitoring\.WARNING\:/', $line) && !preg_match('/Olz\\\Command\\\Monitor/', $line);
160    }
161
162    protected function isNoticeLine(string $line): bool {
163        return preg_match('/\.NOTICE\:/', $line) && !preg_match('/Tool\:\w+-monitoring\.NOTICE\:/', $line) && !preg_match('/Olz\\\Command\\\Monitor/', $line);
164    }
165}