Passed
Push — master ( 6b8ca8...3384db )
by Paul
04:57
created

Logger   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 33
dl 0
loc 245
ccs 0
cts 122
cp 0
rs 9.3999
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A clear() 0 4 1
A error() 0 3 1
A normalizeValue() 0 9 3
A emergency() 0 3 1
A warning() 0 3 1
A __construct() 0 7 2
A __toString() 0 3 1
A isObjectOrArray() 0 3 2
A info() 0 3 1
A interpolate() 0 10 4
A log() 0 10 2
A get() 0 5 2
A notice() 0 3 1
A buildLogEntry() 0 7 1
A critical() 0 3 1
A debug() 0 3 1
A getDebugInformation() 0 12 4
A alert() 0 3 1
A reset() 0 8 3
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use DateTime;
6
use GeminiLabs\SiteReviews\Application;
7
use ReflectionClass;
8
9
class Logger
10
{
11
	const ALERT = 'alert';
12
	const CRITICAL  = 'critical';
13
	const DEBUG = 'debug';
14
	const EMERGENCY = 'emergency';
15
	const ERROR = 'error';
16
	const INFO = 'info';
17
	const NOTICE = 'notice';
18
	const WARNING = 'warning';
19
20
	protected $file;
21
	protected $log;
22
23
	public function __construct( Application $app )
24
	{
25
		$this->file = $app->path( 'debug.log' );
26
		$this->log = file_exists( $this->file )
27
			? file_get_contents( $this->file )
28
			: '';
29
		$this->reset();
30
	}
31
32
	/**
33
	 * @return string
34
	 */
35
	public function __toString()
36
	{
37
		return $this->get();
38
	}
39
40
	/**
41
	 * Action must be taken immediately
42
	 * Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up
43
	 * @param string $message
44
	 * @param array $context
45
	 * @return static
46
	 */
47
	public function alert( $message, array $context = [] )
48
	{
49
		return $this->log( static::ALERT, $message, $context );
50
	}
51
52
	/**
53
	 * @return void
54
	 */
55
	public function clear()
56
	{
57
		$this->log = '';
58
		file_put_contents( $this->file, $this->log );
59
	}
60
61
	/**
62
	 * Critical conditions
63
	 * Example: Application component unavailable, unexpected exception
64
	 * @param string $message
65
	 * @param array $context
66
	 * @return static
67
	 */
68
	public function critical( $message, array $context = [] )
69
	{
70
		return $this->log( static::CRITICAL, $message, $context );
71
	}
72
73
	/**
74
	 * Detailed debug information
75
	 * @param string $message
76
	 * @param array $context
77
	 * @return static
78
	 */
79
	public function debug( $message, array $context = [] )
80
	{
81
		return $this->log( static::DEBUG, $message, $context );
82
	}
83
84
	/**
85
	 * System is unusable
86
	 * @param string $message
87
	 * @param array $context
88
	 * @return static
89
	 */
90
	public function emergency( $message, array $context = [] )
91
	{
92
		return $this->log( static::EMERGENCY, $message, $context );
93
	}
94
95
	/**
96
	 * Runtime errors that do not require immediate action but should typically be logged and monitored
97
	 * @param string $message
98
	 * @param array $context
99
	 * @return static
100
	 */
101
	public function error( $message, array $context = [] )
102
	{
103
		return $this->log( static::ERROR, $message, $context );
104
	}
105
106
	/**
107
	 * @return string
108
	 */
109
	public function get()
110
	{
111
		return empty( $this->log )
112
			? __( 'Log is empty', 'site-reviews' )
113
			: $this->log;
114
	}
115
116
	/**
117
	 * Interesting events
118
	 * Example: User logs in, SQL logs
119
	 * @param string $message
120
	 * @param array $context
121
	 * @return static
122
	 */
123
	public function info( $message, array $context = [] )
124
	{
125
		return $this->log( static::INFO, $message, $context );
126
	}
127
128
	/**
129
	 * @param mixed $level
130
	 * @param string $message
131
	 * @return static
132
	 */
133
	public function log( $level, $message, array $context = [] )
134
	{
135
		$constants = (new ReflectionClass( __CLASS__ ))->getConstants();
136
		$constants = (array)apply_filters( Application::ID.'/log-levels', $constants );
137
		if( in_array( $level, $constants, true )) {
138
			$entry = $this->buildLogEntry( $level, $message, $context );
139
			file_put_contents( $this->file, $entry, FILE_APPEND|LOCK_EX );
140
			$this->reset();
141
		}
142
		return $this;
143
	}
144
145
	/**
146
	 * Normal but significant events
147
	 * @param string $message
148
	 * @param array $context
149
	 * @return static
150
	 */
151
	public function notice( $message, array $context = [] )
152
	{
153
		return $this->log( static::NOTICE, $message, $context );
154
	}
155
156
	/**
157
	 * Exceptional occurrences that are not errors
158
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong
159
	 * @param string $message
160
	 * @param array $context
161
	 * @return static
162
	 */
163
	public function warning( $message, array $context = [] )
164
	{
165
		return $this->log( static::WARNING, $message, $context );
166
	}
167
168
	/**
169
	 * @param string $level
170
	 * @param string $message
171
	 * @return string
172
	 */
173
	protected function buildLogEntry( $level, $message, array $context = [] )
174
	{
175
		return sprintf( '[%s] %s: %s%s'.PHP_EOL,
176
			current_time( 'mysql' ),
177
			strtoupper( $level ),
178
			$this->getDebugInformation(),
179
			$this->interpolate( $message, $context )
0 ignored issues
show
Bug introduced by
It seems like $this->interpolate($message, $context) can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

179
			/** @scrutinizer ignore-type */ $this->interpolate( $message, $context )
Loading history...
180
		);
181
	}
182
183
	/**
184
	 * @return void|string
185
	 */
186
	protected function getDebugInformation()
187
	{
188
		$caller = debug_backtrace( false, 6 );
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $options of debug_backtrace(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

188
		$caller = debug_backtrace( /** @scrutinizer ignore-type */ false, 6 );
Loading history...
189
		$index = array_search( 'log', array_column( $caller, 'function' ));
190
		if( $index === false
191
			|| !isset( $caller[$index+2]['class'] )
192
			|| !isset( $caller[$index+2]['function'] )
193
		)return;
194
		return sprintf( '[%s()->%s:%s] ',
195
			$caller[$index+2]['class'],
196
			$caller[$index+2]['function'],
197
			$caller[$index+1]['line']
198
		);
199
	}
200
201
	/**
202
	 * Interpolates context values into the message placeholders
203
	 * @param mixed $message
204
	 * @param array $context
205
	 * @return array|string
206
	 */
207
	protected function interpolate( $message, $context = [] )
208
	{
209
		if( $this->isObjectOrArray( $message ) || !is_array( $context )) {
210
			return print_r( $message, true );
211
		}
212
		$replace = [];
213
		foreach( $context as $key => $value ) {
214
			$replace['{'.$key.'}'] = $this->normalizeValue( $value );
215
		}
216
		return strtr( $message, $replace );
217
	}
218
219
	/**
220
	 * @param mixed $value
221
	 * @return bool
222
	 */
223
	protected function isObjectOrArray( $value )
224
	{
225
		return is_object( $value ) || is_array( $value );
226
	}
227
228
	/**
229
	 * @param mixed $value
230
	 * @return string
231
	 */
232
	protected function normalizeValue( $value )
233
	{
234
		if( $value instanceof DateTime ) {
235
			$value = $value->format( 'Y-m-d H:i:s' );
236
		}
237
		else if( $this->isObjectOrArray( $value )) {
238
			$value = json_encode( $value );
239
		}
240
		return (string)$value;
241
	}
242
243
	/**
244
	 * @return void
245
	 */
246
	protected function reset()
247
	{
248
		if( file_exists( $this->file )
249
			&& filesize( $this->file ) > pow( 1024, 2 ) / 2 ) {
250
			$this->clear();
251
			file_put_contents(
252
				$this->file,
253
				$this->buildLogEntry( 'info', __( 'Log has been automatically reset (512 KB max size)', 'site-reviews' ))
254
			);
255
		}
256
	}
257
}
258