Issues (2)

src/LogParser.php (2 issues)

Labels
Severity
1
<?php declare(strict_types=1);
2
3
/**
4
 *  ___             _
5
 * | _ \__ _ _ _ __| |___  __ _
6
 * |  _/ _` | '_(_-< / _ \/ _` |
7
 * |_| \__,_|_| /__/_\___/\__, |
8
 *                        |___/
9
 * 
10
 * (c) Kristuff <[email protected]>
11
 *
12
 * For the full copyright and license information, please view the LICENSE
13
 * file that was distributed with this source code.
14
 *
15
 * @version    0.7.2
16
 * @copyright  2017-2021 Kristuff
17
 */
18
19
namespace Kristuff\Parselog;
20
21
use DateTime;
22
use Kristuff\Parselog\Core\LogEntryInterface;
23
use Kristuff\Parselog\Core\LogEntryFactory;
24
use Kristuff\Parselog\Core\LogEntryFactoryInterface;
25
use Kristuff\Parselog\Core\RegexFactory;
26
27
/** 
28
 * LogParser
29
 */
30
class LogParser extends RegexFactory
31
{
32
    /** 
33
     * @access private
34
     * @var string 
35
     */
36
    private $pcreFormat;
37
38
    /** 
39
     * @access private
40
     * @var string
41
     */
42
    protected $logFormat = '';
43
44
    /**
45
     * @access private
46
     * @var LogEntryFactoryInterface 
47
     */
48
    private $factory;
49
50
    /** 
51
     * @access protected
52
     * @var array 
53
     */
54
    protected $patterns = [];
55
   
56
    /** 
57
     * The time format 
58
     * 
59
     * @access protected
60
     * @var string
61
     */
62
    protected $timeFormat = null;
63
64
    /**
65
     * Constructor
66
     * 
67
     * @access public
68
     * @param string                    $format    
69
     * @param LogEntryFactoryInterface  $factory        
70
     * 
71
     * @return void
72
     */
73
    public function __construct(string $format = null, LogEntryFactoryInterface $factory = null)
74
    {
75
        $this->setFormat($format ?? '');
76
        $this->factory = $factory ?: new LogEntryFactory();
77
    }
78
79
    /**
80
     * Sets/Adds named pattern 
81
     * 
82
     * @access public
83
     * @param string    $placeholder
84
     * @param string    $propertyName
85
     * @param string    $pattern            The pattern expression
86
     * @param bool      $required           False if the column way be missing from output. Default is true.
87
     *                                      Note this feature won't work with the first column. 
88
     * 
89
     * @return void
90
     */
91
    public function addNamedPattern(string $placeholder, string $propertyName, string $pattern, bool $required = true): void
92
    {
93
        // First field must be a required field
94
        if ($required === false && count($this->patterns) === 0){
95
            throw new \Kristuff\Parselog\InvalidArgumentException(
96
                "Invalid value 'false' for argument 'required' given: First pattern must be a required pattern.");
97
        }
98
99
        // required or optional column ?
100
        // Adjust pattern for nullable columns and add space before placeholder
101
        // - $format = '%t %l %P %E: %a %M';
102
        // + $format = '%t %l( %P)?( %E:)?(%a)? %M';
103
        $key = $required ? $placeholder :  ' ' . $placeholder ; 
104
        $val = '(?P<'. $propertyName . '>' . ($required ? $pattern : '( ' . $pattern . ')?') . ')'; 
105
106
        $this->addPattern($key, $val);
107
    }
108
109
    /**
110
     * Sets/Adds pattern 
111
     * 
112
     * @access public
113
     * @param string    $placeholder
114
     * @param string    $propertyName
115
     * @param string    $pattern        
116
     * 
117
     * @return void
118
     */
119
    public function addPattern(string $placeholder, string $pattern): void
120
    {
121
        $this->patterns[$placeholder] = $pattern;
122
    }
123
124
    /**
125
     * Gets the current format (defined by user or default)
126
     * 
127
     * @access public
128
     * 
129
     * @return string
130
     */
131
    public function getFormat(): string
132
    {
133
        return $this->logFormat;
134
    }
135
136
    /**
137
     * Sets the log format  
138
     * 
139
     * @access public
140
     * @param string    $format
141
     * 
142
     * @return void
143
     */
144
    public function setFormat(string $format): void
145
    {   
146
147
        // Remove backslashes from format
148
        $format = str_replace("\\", '', $format);
149
150
        // store log format update IP pattern
151
        $this->logFormat = $format ;
152
        $this->updateIpPatterns();
153
154
        // strtr won't work for "complex" header patterns
155
        // $this->pcreFormat = strtr("#^{$format}$#", $this->patterns);
156
        $expr = "#^{$format}$#";
157
158
        foreach ($this->patterns as $pattern => $replace) {
159
            $expr = preg_replace("/{$pattern}/", $replace, $expr);
160
        }
161
162
        $this->pcreFormat = $expr;
163
    }
164
165
    /**
166
     * Parses one single log line.
167
     * 
168
     * @access public
169
     * @param string    $line
170
     * 
171
     * @return LogEntryInterface
172
     * @throws FormatException
173
     */
174
    public function parse(string $line): LogEntryInterface
175
    {
176
        if (!preg_match($this->getPCRE(), $line, $matches)) {
177
            throw new FormatException($line);
178
        }
179
        
180
        $entry = $this->factory->create($matches);
181
182
        if (isset($entry->time)) {
0 ignored issues
show
Accessing time on the interface Kristuff\Parselog\Core\LogEntryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
183
            $entry->stamp = $this->getTimestamp($entry->time);
0 ignored issues
show
Accessing stamp on the interface Kristuff\Parselog\Core\LogEntryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
184
        }
185
      
186
        return $entry;
187
    }
188
189
    /**
190
     * Gets the PCRE filter.
191
     * 
192
     * @access public
193
     * @return string
194
     */
195
    public function getPCRE(): string
196
    {
197
        return (string) $this->pcreFormat;
198
    }
199
200
    /**
201
     * Converts time to previously set format.
202
     *
203
     * @access protected
204
     * @param string        $time
205
     *
206
     * @return int|null
207
     */
208
    protected function getTimestamp($time): ?int
209
    {
210
        // try to get stamp from string
211
        if (isset($this->timeFormat)){
212
            $dateTime = DateTime::createFromFormat($this->timeFormat, $time);
213
214
            if (false !== $dateTime) {
215
                return $dateTime->getTimestamp();
216
            }
217
        }
218
219
        // try to get stamp from string
220
        $stamp = strtotime($time);
221
        if (false !== $stamp) {
222
            return $stamp;
223
        }
224
225
        return null;
226
    }
227
228
    /**
229
     * Replaces {{PATTERN_IP_ALL}} with the IPV4/6 patterns.
230
     * 
231
     * @access public
232
     * @return void
233
     */
234
    private function updateIpPatterns(): void
235
    {
236
        // Set IPv4 & IPv6 recognition patterns 
237
        $ipPatterns = implode('|', [
238
            'ipv4'          => self::PATTERN_IP_V4,
239
            'ipv6full'      => self::PATTERN_IP_V6_FULL,        // 1:1:1:1:1:1:1:1
240
            'ipv6null'      => self::PATTERN_IP_V6_NULL,        // ::
241
            'ipv6leading'   => self::PATTERN_IP_V6_LEADING,     // ::1:1:1:1:1:1:1
242
            'ipv6mid'       => self::PATTERN_IP_V6_MID,         // 1:1:1::1:1:1
243
            'ipv6trailing'  => self::PATTERN_IP_V6_TRAILING,    // 1:1:1:1:1:1:1::
244
        ]);
245
246
        foreach ($this->patterns as &$value) {
247
            $value = str_replace('{{PATTERN_IP_ALL}}', $ipPatterns, $value);
248
        }
249
    }
250
}