LogUtilitiesTrait   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 24
eloc 86
c 1
b 0
f 0
dl 0
loc 223
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A resetStats() 0 12 2
A parseErrorLine() 0 16 2
A parseLine() 0 17 3
A getChannelDetails() 0 5 1
A getFileList() 0 15 3
A getChannelStats() 0 14 2
A parseFile() 0 29 4
A getFileToArray() 0 3 1
A getFileStats() 0 20 4
A getLineLevel() 0 8 2
1
<?php
2
3
namespace App\Traits;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Facades\Storage;
7
8
/**
9
 * Log Utilities Trait holds all reusable Log File variables and methods
10
 */
11
trait LogUtilitiesTrait
12
{
13
    /**
14
     * Regex data for log file
15
     */
16
    protected $logEntryPattern = '/^\[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})\] (?:.*?(\w+)\.)(?:.*?(\w+)\:) (.*?)(\{.*?\})? .?$/i';
17
    protected $logErrorPattern = '/^\[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})\] (?:.*?(\w+)\.)(?:.*?(\w+)\:) (.*)?$/i';
18
    protected $logLevelPattern = '/\.(.*?): /i';
19
20
    /**
21
     * Log Levels
22
     */
23
    protected $logLevels = [
24
        ['name' => 'Emergency', 'icon' => 'fas fa-ambulance',            'color' => ''],
25
        ['name' => 'Alert',     'icon' => 'fas fa-bullhorn',             'color' => ''],
26
        ['name' => 'Critical',  'icon' => 'fas fa-heartbeat',            'color' => ''],
27
        ['name' => 'Error',     'icon' => 'fas fa-times-circle',         'color' => ''],
28
        ['name' => 'Warning',   'icon' => 'fas fa-exclamation-triangle', 'color' => ''],
29
        ['name' => 'Notice',    'icon' => 'fas fa-exclamation-circle',   'color' => ''],
30
        ['name' => 'Info',      'icon' => 'fas fa-info',                 'color' => ''],
31
        ['name' => 'Debug',     'icon' => 'fas fa-bug',                  'color' => ''],
32
    ];
33
34
    /**
35
     * Log Channels
36
     */
37
    protected $logChannels = [
38
        ['name' => 'Emergency',      'folder' => 'Emergency'],
39
        ['name' => 'Application',    'folder' => 'Application'],
40
        ['name' => 'User',           'folder' => 'Users'],
41
        ['name' => 'Authentication', 'folder' => 'Auth'],
42
        ['name' => 'Customer',       'folder' => 'Cust'],
43
        ['name' => 'Tech Tip',       'folder' => 'TechTip'],
44
    ];
45
46
    /**
47
     * Get the full details of a channel based on the name
48
     */
49
    protected function getChannelDetails($channel)
50
    {
51
        return Arr::first($this->logChannels, function($value) use ($channel)
52
        {
53
            return $value['name'] == $channel;
54
        });
55
    }
56
57
    /**
58
     * Get all of the files for a specific channel
59
     */
60
    protected function getFileList($channel)
61
    {
62
        $list   = Storage::disk('logs')->files($channel['folder']);
63
        $sorted = [];
64
65
        foreach($list as $file)
66
        {
67
            $pathInfo = pathinfo($file);
68
            if($pathInfo['extension'] === 'log')
69
            {
70
                $sorted[] = $pathInfo['filename'];
71
            }
72
        }
73
74
        return $sorted;
75
    }
76
77
    /**
78
     * List the files for a specific channel, and get the log level stats for all files combined
79
     */
80
    protected function getChannelStats($channel)
81
    {
82
        $channel  = $this->getChannelDetails($channel);
83
        $fileList = $this->getFileList($channel);
84
        $statList = [];
85
86
        //  Cycle through all log files
87
        foreach($fileList as $file)
88
        {
89
            $fileArr    = $this->getFileToArray($file, $channel);
90
            $statList[] = $this->getFileStats($fileArr, $file);
91
        }
92
93
        return $statList;
94
    }
95
96
    /**
97
     * Get the stats for a specific file
98
     */
99
    protected function getFileStats($fileArr, $filename)
100
    {
101
        $stats   = $this->resetStats($filename);
102
103
        //  Cycle through each line of the file
104
        foreach($fileArr as $line)
105
        {
106
            $level = $this->getLineLevel($line);
107
            if($level)
108
            {
109
                //  We only increment the level data if it is a valid log line and not part of a multiline stack trace
110
                if(isset($stats[strtolower($level)]))
111
                {
112
                    $stats[strtolower($level)]++;
113
                    $stats['total']++;
114
                }
115
            }
116
        }
117
118
        return $stats;
119
    }
120
121
    /**
122
     * Provide a clean array of stats
123
     */
124
    protected function resetStats($filename)
125
    {
126
        $statList = [];
127
        foreach($this->logLevels as $level)
128
        {
129
            $statList[strtolower($level['name'])] = 0;
130
        }
131
132
        $statList['filename'] = $filename;
133
        $statList['total']    = 0;
134
135
        return $statList;
136
    }
137
138
    /**
139
     * Take a log file and convert each line into an array item
140
     */
141
    protected function getFileToArray($file, $channel)
142
    {
143
        return file(Storage::disk('logs')->path($channel['folder'].DIRECTORY_SEPARATOR.$file.'.log'));
144
    }
145
146
    /**
147
     * Get the log level of a line
148
     */
149
    protected function getLineLevel($line)
150
    {
151
        if(preg_match($this->logLevelPattern, $line, $data))
152
        {
153
            return $data[1];
154
        }
155
156
        return false;
157
    }
158
159
    /**
160
     * Parse the log file to identify each section of the message
161
     */
162
    protected function parseFile($fileArr)
163
    {
164
        $parsed   = [];
165
        $errorKey = null;
166
167
        //  Go through each line of the file
168
        foreach($fileArr as $line)
169
        {
170
            $parse = $this->parseLine($line);
171
            if(!$parse)
172
            {
173
                $parse = $this->parseErrorLine($line);
174
                if($parse)
175
                {
176
                    $parsed[] = $parse;
177
                    $errorKey = array_key_last($parsed);
178
                }
179
                else
180
                {
181
                    $parsed[$errorKey]['stack_trace'][] = $line;
182
                }
183
            }
184
            else
185
            {
186
                $parsed[] = $parse;
187
            }
188
        }
189
190
        return $parsed;
191
    }
192
193
    /**
194
     * Use Regex to parse the portions of each log line
195
     */
196
    protected function parseLine($line)
197
    {
198
        //  If it is a standard log line, we return standard data
199
        if(preg_match($this->logEntryPattern, $line, $data))
200
        {
201
            return [
202
                'date'    => $data[1],
203
                'time'    => $data[2],
204
                'env'     => $data[3],
205
                'level'   => $data[4],
206
                'message' => $data[5],
207
                'details' => isset($data[6]) ? [json_decode($data[6])] : null,
208
            ];
209
        }
210
211
        //  If the line is a major error, the line and stack trace will follow
212
        return false;
213
    }
214
215
    /**
216
     * Use Regex to get the error line
217
     */
218
    protected function parseErrorLine($line)
219
    {
220
        if(preg_match($this->logErrorPattern, $line, $data))
221
        {
222
            return [
223
                'date'    => $data[1],
224
                'time'    => $data[2],
225
                'env'     => $data[3],
226
                'level'   => $data[4],
227
                'message' => $data[5],
228
                'details' => [],
229
            ];
230
        }
231
232
        //  If the line is a major error, the line and stack trace will follow
233
        return false;
234
    }
235
}
236