• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lonnieezell / Bonfire2 / 18263307169

05 Oct 2025 07:26PM UTC coverage: 45.14% (+0.05%) from 45.094%
18263307169

Pull #577

github

web-flow
Merge b56fcf3b3 into 9641e0456
Pull Request #577: Rector suggested changes on consistent string handling

4 of 6 new or added lines in 6 files covered. (66.67%)

103 existing lines in 1 file now uncovered.

1593 of 3529 relevant lines covered (45.14%)

10.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.07
/src/Tools/Libraries/Logs.php
1
<?php
2

3
/**
4
 * This file is part of Bonfire.
5
 *
6
 * (c) Lonnie Ezell <lonnieje@gmail.com>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11

12
namespace Bonfire\Tools\Libraries;
13

14
use DateTime;
15

16
/**
17
 * Provides view cells for Users
18
 */
19
class Logs
20
{
21
    public const MAX_LOG_SIZE           = 52428800; // 50MB
22
    public const MAX_STRING_LENGTH      = 300; // 300 chars
23
    public const LOG_LINE_START_PATTERN = '/((DEBUG)|(INFO)|(NOTICE)|(WARNING)|(ERROR)|(CRITICAL)|(ALERT)|(EMERGENCY))[\\s\\-\\d:\\.\\/]+(-->)/';
24
    public const LOG_DATE_PATTERN       = ['/^((DEBUG)|(INFO)|(NOTICE)|(WARNING)|(ERROR)|(CRITICAL)|(ALERT)|(EMERGENCY))\\s\\-\\s/', '/\\s(-->)/'];
25
    public const LOG_LEVEL_PATTERN      = '/^((DEBUG)|(INFO)|(NOTICE)|(WARNING)|(ERROR)|(CRITICAL)|(ALERT)|(EMERGENCY))/';
26

27
    private static array $levelsIcon = [
28
        'DEBUG'     => 'fas fa-bug',
29
        'INFO'      => 'fas fa-info-circle',
30
        'NOTICE'    => 'fas fa-info-circle',
31
        'WARNING'   => 'fas fa-times',
32
        'ERROR'     => 'fas fa-times',
33
        'CRITICAL'  => 'fas fa-exclamation-triangle',
34
        'ALERT'     => 'fas fa-exclamation-triangle',
35
        'EMERGENCY' => 'fas fa-exclamation-triangle',
36
    ];
37
    private static array $levelClasses = [
38
        'DEBUG'     => 'warning',
39
        'INFO'      => 'info',
40
        'NOTICE'    => 'info',
41
        'WARNING'   => 'warning',
42
        'ERROR'     => 'danger',
43
        'CRITICAL'  => 'danger',
44
        'ALERT'     => 'danger',
45
        'EMERGENCY' => 'danger',
46
    ];
47

48
    /**
49
     * This function will process the logs. Extract the log level, icon class and other information
50
     * from each line of log and then arrange them in another array that is returned to the view for processing
51
     *
52
     * @params logs. The raw logs as read from the log file
53
     *
54
     * @param mixed $file
55
     *
56
     * @return array. An [[], [], [] ...] where each element is a processed log line
57
     * */
58
    public function processFileLogs($file)
59
    {
60
        if ($file === null) {
2✔
61
            return [];
×
62
        }
63

64
        $logs = $this->getLogs($file);
2✔
65

66
        $superLog = [];
2✔
67

68
        foreach ($logs as $log) {
2✔
69
            // get the logLine Start
70
            $logLineStart = $this->getLogLineStart($log);
2✔
71

72
            if (! empty($logLineStart)) {
2✔
73
                // this is actually the start of a new log and not just another line from previous log
74
                $level = $this->getLogLevel($logLineStart);
2✔
75
                $data  = [
2✔
76
                    'level' => $level,
2✔
77
                    'date'  => $this->getLogDate($logLineStart),
2✔
78
                    'icon'  => self::$levelsIcon[$level],
2✔
79
                    'class' => self::$levelClasses[$level],
2✔
80
                ];
2✔
81

82
                $logMessage = preg_replace(self::LOG_LINE_START_PATTERN, '', (string) $log);
2✔
83

84
                if (strlen((string) $logMessage) > self::MAX_STRING_LENGTH) {
2✔
85
                    $data['content'] = substr((string) $logMessage, 0, self::MAX_STRING_LENGTH);
×
86
                    $data['extra']   = substr((string) $logMessage, (self::MAX_STRING_LENGTH + 1));
×
87
                } else {
88
                    $data['content'] = $logMessage;
2✔
89
                }
90

91
                $superLog[] = $data;
2✔
92
            } elseif ($superLog !== []) {
×
93
                // this log line is a continuation of previous logline
94
                // so let's add them as extra
95
                $prevLog                        = $superLog[count($superLog) - 1];
×
96
                $extra                          = (array_key_exists('extra', $prevLog)) ? $prevLog['extra'] : '';
×
97
                $prevLog['extra']               = $extra . "\n" . $log;
×
98
                $superLog[count($superLog) - 1] = $prevLog;
×
99
            }
100
        }
101

102
        return $superLog;
2✔
103
    }
104

105
    public function countLogLevels($filePath): string
106
    {
107
        $levels = array_keys(self::$levelClasses);
×
108

109
        // Initialize the counts array
110
        $counts = array_fill_keys($levels, 0);
×
111

112
        // Read the file content
113
        $fileContent = file_get_contents($filePath);
×
114

115
        if ($fileContent === false) {
×
116
            throw new Exception("Unable to read the file: {$filePath}");
×
117
        }
118

119
        // Count occurrences of each level
120
        foreach ($levels as $level) {
×
NEW
121
            $counts[$level] = substr_count($fileContent, (string) $level);
×
122
        }
123

124
        // Remove entries with value 0
125
        $counts = array_filter($counts, static fn ($value) => $value > 0);
×
126

127
        $counts = array_reverse($counts);
×
128

129
        // Transform the array into a string with color codes
130
        $result = [];
×
131

132
        foreach ($counts as $level => $count) {
×
133
            $class    = self::$levelClasses[$level];
×
134
            $result[] = '<span class="text-' . $class . '">' . $level . '</span>: ' . $count;
×
135
        }
136

137
        return strtolower(implode(', ', $result));
×
138
    }
139

140
    /**
141
     * returns an array of the file contents
142
     * each element in the array is a line
143
     * in the underlying log file
144
     *
145
     * @returns array | each line of file contents is an entry in the returned array.
146
     *
147
     * @params complete fileName
148
     *
149
     * @param mixed $fileName
150
     * */
151
    public function getLogs($fileName)
152
    {
153
        $size = filesize($fileName);
2✔
154
        if (! $size || $size > self::MAX_LOG_SIZE) {
2✔
155
            return null;
×
156
        }
157

158
        return file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2✔
159
    }
160

161
    /**
162
     * This function will paginate logs files lines or logs files.
163
     *
164
     * @param array $logs.  The raw logs as read from the log file or log files.
165
     * @param int   $limit. Number of results per page.
166
     *
167
     * @return array with pager object and filtered array.
168
     */
169
    public function paginateLogs(array $logs, int $limit)
170
    {
171
        $pager  = service('pager');
1✔
172
        $page   = $_GET['page'] ?? 1;
1✔
173
        $offset = ($page > 1) ? ($page - 1) * $limit : 0;
1✔
174

175
        $pager->makeLinks($page, $limit, count($logs));
1✔
176

177
        return ['pager' => $pager, 'logs' => array_slice($logs, $offset, $limit)];
1✔
178
    }
179

180
    /**
181
     * Retrieves the adjacent log files (previous and next) relative to the given log file.
182
     *
183
     * @param string $currentFile            The current log file name.
184
     * @param array  $logFiles               An array of all log file names.
185
     * @param mixed  $currentLogFileBasename
186
     * @param mixed  $logsPath
187
     *
188
     * @return array An associative array with 'previous' and 'next' keys containing the respective log file names, or null if not available.
189
     */
190
    public function getAdjacentLogFiles($currentLogFileBasename, $logsPath): array
191
    {
192
        // Extract the date from the current log file name
193
        preg_match('/log-(\d{4}-\d{2}-\d{2})/', (string) $currentLogFileBasename, $matches);
1✔
194
        new DateTime($matches[1]);
1✔
195

196
        // Retrieve the list of log files in the directory
197
        $logFiles = glob($logsPath . '/log-*.log');
1✔
198

199
        // Extract dates from the filtered log file names
200
        $logDates = array_map(static fn ($filePath) => basename($filePath, '.log'), $logFiles);
1✔
201

202
        // Sort the log files by date
203
        usort($logDates, static function ($a, $b) {
1✔
204
            // Extract the date part of the log file names for comparison
205
            $dateA = str_replace('log-', '', $a);
×
206
            $dateB = str_replace('log-', '', $b);
×
207

208
            return strcmp($dateA, $dateB);
×
209
        });
1✔
210

211
        // Find the index of the current log file
212
        $currentLogFileName = basename((string) $currentLogFileBasename, '.log');
1✔
213
        $currentIndex       = array_search($currentLogFileName, $logDates, true);
1✔
214

215
        // Determine the next and previous log files based on the index
216
        $previousLogFileBasename = $currentIndex > 0 ? $logDates[$currentIndex - 1] : null;
1✔
217
        $nextLogFileBasename     = $currentIndex < count($logDates) - 1 ? $logDates[$currentIndex + 1] : null;
1✔
218

219
        return [
1✔
220
            'prev' => [
1✔
221
                'link'  => $previousLogFileBasename,
1✔
222
                'label' => substr($previousLogFileBasename ?? '', 4, 10),
1✔
223
            ],
1✔
224
            'curr' => [
1✔
225
                'label' => substr((string) $currentLogFileBasename, 4, 10),
1✔
226
            ],
1✔
227
            'next' => [
1✔
228
                'link'  => $nextLogFileBasename,
1✔
229
                'label' => substr($nextLogFileBasename ?? '', 4, 10),
1✔
230
            ],
1✔
231
        ];
1✔
232
    }
233

234
    /**
235
     * extract the log level from the logLine
236
     *
237
     * @param string $logLineStart - The single line that is the start of log line.
238
     *                             extracted by getLogLineStart()
239
     *
240
     * @return string Log level e.g. ERROR, DEBUG, INFO
241
     * */
242
    private function getLogLevel($logLineStart)
243
    {
244
        preg_match(self::LOG_LEVEL_PATTERN, $logLineStart, $matches);
2✔
245

246
        return $matches[0];
2✔
247
    }
248

249
    private function getLogDate($logLineStart)
250
    {
251
        return preg_replace(self::LOG_DATE_PATTERN, '', (string) $logLineStart);
2✔
252
    }
253

254
    private function getLogLineStart($logLine)
255
    {
256
        preg_match(self::LOG_LINE_START_PATTERN, (string) $logLine, $matches);
2✔
257
        if ($matches !== []) {
2✔
258
            return $matches[0];
2✔
259
        }
260

261
        return '';
×
262
    }
263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc