Completed
Push — master ( 00a7e0...6e1d06 )
by Kris
26s queued 10s
created

LogParser::addNamedPattern()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 16
rs 9.6111
cc 5
nc 5
nop 4
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.3.0
16
 * @copyright  2017-2020 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
        // store log format update IP pattern
147
        $this->logFormat = $format ;
148
        $this->updateIpPatterns();
149
150
        // strtr won't work for "complex" header patterns
151
        // $this->pcreFormat = strtr("#^{$format}$#", $this->patterns);
152
        $expr = "#^{$format}$#";
153
154
        foreach ($this->patterns as $pattern => $replace) {
155
            $expr = preg_replace("/{$pattern}/", $replace, $expr);
156
        }
157
158
        $this->pcreFormat = $expr;
159
    }
160
161
    /**
162
     * Parses one single log line.
163
     * 
164
     * @access public
165
     * @param string    $line
166
     * 
167
     * @return LogEntryInterface
168
     * @throws FormatException
169
     */
170
    public function parse(string $line): LogEntryInterface
171
    {
172
        if (!preg_match($this->getPCRE(), $line, $matches)) {
173
            throw new FormatException($line);
174
        }
175
        
176
        $entry = $this->factory->create($matches);
177
178
        if (isset($entry->time)) {
0 ignored issues
show
Bug introduced by
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...
179
            $entry->stamp = $this->getTimestamp($entry->time);
0 ignored issues
show
Bug introduced by
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...
180
        }
181
      
182
        return $entry;
183
    }
184
185
    /**
186
     * Gets the PCRE filter.
187
     * 
188
     * @access public
189
     * @return string
190
     */
191
    public function getPCRE(): string
192
    {
193
        return (string) $this->pcreFormat;
194
    }
195
196
    /**
197
     * Converts time to previously set format.
198
     *
199
     * @access protected
200
     * @param string        $time
201
     *
202
     * @return int|null
203
     */
204
    protected function getTimestamp($time): ?int
205
    {
206
        // try to get stamp from string
207
        if (isset($this->timeFormat)){
208
            $dateTime = DateTime::createFromFormat($this->timeFormat, $time);
209
210
            if (false !== $dateTime) {
211
                return $dateTime->getTimestamp();
212
            }
213
        }
214
215
        // try to get stamp from string
216
        $stamp = strtotime($time);
217
        if (false !== $stamp) {
218
            return $stamp;
219
        }
220
221
        return null;
222
    }
223
224
    /**
225
     * Replaces {{PATTERN_IP_ALL}} with the IPV4/6 patterns.
226
     * 
227
     * @access public
228
     * @return void
229
     */
230
    private function updateIpPatterns(): void
231
    {
232
        // Set IPv4 & IPv6 recognition patterns 
233
        $ipPatterns = implode('|', [
234
            'ipv4'          => self::PATTERN_IP_V4,
235
            'ipv6full'      => self::PATTERN_IP_V6_FULL,        // 1:1:1:1:1:1:1:1
236
            'ipv6null'      => self::PATTERN_IP_V6_NULL,        // ::
237
            'ipv6leading'   => self::PATTERN_IP_V6_LEADING,     // ::1:1:1:1:1:1:1
238
            'ipv6mid'       => self::PATTERN_IP_V6_MID,         // 1:1:1::1:1:1
239
            'ipv6trailing'  => self::PATTERN_IP_V6_TRAILING,    // 1:1:1:1:1:1:1::
240
        ]);
241
242
        foreach ($this->patterns as &$value) {
243
            $value = str_replace('{{PATTERN_IP_ALL}}', $ipPatterns, $value);
244
        }
245
    }
246
}