Passed
Push — master ( 3e60cb...36c2a1 )
by Paul
07:09 queued 03:38
created

Console::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 7
ccs 5
cts 6
cp 0.8333
crap 2.0185
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use DateTime;
6
use GeminiLabs\SiteReviews\Application;
7
use GeminiLabs\SiteReviews\Modules\Session;
8
use ReflectionClass;
9
10
class Console
11
{
12
	const ALERT = 'alert';
13
	const CRITICAL = 'critical';
14
	const DEBUG = 'debug';
15
	const EMERGENCY = 'emergency';
16
	const ERROR = 'error';
17
	const INFO = 'info';
18
	const NOTICE = 'notice';
19
	const RECURRING = 'recurring';
20
	const WARNING = 'warning';
21
22
	protected $file;
23
	protected $log;
24
25 7
	public function __construct( Application $app )
26
	{
27 7
		$this->file = $app->path( 'console.log' );
28 7
		$this->log = file_exists( $this->file )
29 7
			? file_get_contents( $this->file )
30
			: '';
31 7
		$this->reset();
32 7
	}
33
34
	/**
35
	 * @return string
36
	 */
37
	public function __toString()
38
	{
39
		return $this->get();
40
	}
41
42
	/**
43
	 * Action must be taken immediately
44
	 * Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up
45
	 * @param mixed $message
46
	 * @param array $context
47
	 * @return static
48
	 */
49
	public function alert( $message, array $context = [] )
50
	{
51
		return $this->log( static::ALERT, $message, $context );
52
	}
53
54
	/**
55
	 * @return void
56
	 */
57
	public function clear()
58
	{
59
		$this->log = '';
60
		file_put_contents( $this->file, $this->log );
61
	}
62
63
	/**
64
	 * Critical conditions
65
	 * Example: Application component unavailable, unexpected exception
66
	 * @param mixed $message
67
	 * @param array $context
68
	 * @return static
69
	 */
70
	public function critical( $message, array $context = [] )
71
	{
72
		return $this->log( static::CRITICAL, $message, $context );
73
	}
74
75
	/**
76
	 * Detailed debug information
77
	 * @param mixed $message
78
	 * @param array $context
79
	 * @return static
80
	 */
81
	public function debug( $message, array $context = [] )
82
	{
83
		return $this->log( static::DEBUG, $message, $context );
84
	}
85
86
	/**
87
	 * System is unusable
88
	 * @param mixed $message
89
	 * @param array $context
90
	 * @return static
91
	 */
92
	public function emergency( $message, array $context = [] )
93
	{
94
		return $this->log( static::EMERGENCY, $message, $context );
95
	}
96
97
	/**
98
	 * Runtime errors that do not require immediate action but should typically be logged and monitored
99
	 * @param mixed $message
100
	 * @param array $context
101
	 * @return static
102
	 */
103
	public function error( $message, array $context = [] )
104
	{
105
		return $this->log( static::ERROR, $message, $context );
106
	}
107
108
	/**
109
	 * @return string
110
	 */
111
	public function get()
112
	{
113
		return empty( $this->log )
114
			? __( 'Console is empty', 'site-reviews' )
115
			: $this->log;
116
	}
117
118
	/**
119
	 * @param null|string $valueIfEmpty
120
	 * @return string
121
	 */
122
	public function humanSize( $valueIfEmpty = null )
123
	{
124
		$bytes = $this->size();
125
		if( empty( $bytes ) && is_string( $valueIfEmpty )) {
126
			return $valueIfEmpty;
127
		}
128
		$exponent = floor( log( max( $bytes, 1 ), 1024 ));
129
		return round( $bytes / pow( 1024, $exponent ), 2 ).' '.['bytes','KB','MB','GB'][$exponent];
130
	}
131
132
	/**
133
	 * Interesting events
134
	 * Example: User logs in, SQL logs
135
	 * @param mixed $message
136
	 * @param array $context
137
	 * @return static
138
	 */
139 7
	public function info( $message, array $context = [] )
140
	{
141 7
		return $this->log( static::INFO, $message, $context );
142
	}
143
144
	/**
145
	 * @param mixed $level
146
	 * @param mixed $message
147
	 * @return static
148
	 */
149 7
	public function log( $level, $message, array $context = [] )
150
	{
151 7
		$constants = (new ReflectionClass( __CLASS__ ))->getConstants();
152 7
		if( in_array( $level, $constants, true )) {
153 7
			$entry = $this->buildLogEntry( $level, $message, $context );
154 7
			file_put_contents( $this->file, $entry, FILE_APPEND|LOCK_EX );
155 7
			$this->reset();
156
		}
157 7
		return $this;
158
	}
159
160
	/**
161
	 * @return void
162
	 */
163
	public function logOnce()
164
	{
165
		$single = glsr( Session::class )->get( 'glsr_log_once', null, true );
166
		if( !is_array( $single ))return;
167
		foreach( $single as $message => $debug ) {
168
			if( !empty( $debug )) {
169
				$message.= PHP_EOL.print_r( $debug, 1 );
170
			}
171
			$this->log( static::RECURRING, $message );
172
		}
173
	}
174
175
	/**
176
	 * Normal but significant events
177
	 * @param mixed $message
178
	 * @param array $context
179
	 * @return static
180
	 */
181
	public function notice( $message, array $context = [] )
182
	{
183
		return $this->log( static::NOTICE, $message, $context );
184
	}
185
186
	/**
187
	 * @param mixed $message
188
	 * @param array $context
189
	 * @return void
190
	 */
191
	public function once( $message, $debug = '' )
192
	{
193
		$once = glsr( Session::class )->get( 'glsr_log_once', [] );
194
		if( !is_array( $once )) {
195
			$once = [];
196
		}
197
		if( !isset( $once[$message] )) {
198
			$once[$message] = $debug;
199
			glsr( Session::class )->set( 'glsr_log_once', $once );
200
		}
201
	}
202
203
	/**
204
	 * @return int
205
	 */
206 7
	public function size()
207
	{
208 7
		return file_exists( $this->file )
209 7
			? filesize( $this->file )
210 7
			: 0;
211
	}
212
213
	/**
214
	 * Exceptional occurrences that are not errors
215
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong
216
	 * @param mixed $message
217
	 * @param array $context
218
	 * @return static
219
	 */
220
	public function warning( $message, array $context = [] )
221
	{
222
		return $this->log( static::WARNING, $message, $context );
223
	}
224
225
	/**
226
	 * @param string $level
227
	 * @param mixed $message
228
	 * @return string
229
	 */
230 7
	protected function buildLogEntry( $level, $message, array $context = [] )
231
	{
232 7
		return sprintf( '[%s|%s] %s: %s'.PHP_EOL,
233 7
			current_time( 'mysql' ),
234 7
			$this->getBacktrace(),
235 7
			strtoupper( $level ),
236 7
			$this->interpolate( $message, $context )
237
		);
238
	}
239
240
	/**
241
	 * @return void|string
242
	 */
243 7
	protected function getBacktrace()
244
	{
245 7
		$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 4 );
246 7
		$entry = array_pop( $backtrace );
247 7
		$path = str_replace( [glsr()->path( 'plugin/' ), glsr()->path(), ABSPATH], '', $entry['file'] );
248 7
		return $path.':'.$entry['line'];
249
	}
250
251
	/**
252
	 * Interpolates context values into the message placeholders
253
	 * @param mixed $message
254
	 * @param array $context
255
	 * @return string
256
	 */
257 7
	protected function interpolate( $message, $context = [] )
258
	{
259 7
		if( $this->isObjectOrArray( $message ) || !is_array( $context )) {
260
			return print_r( $message, true );
261
		}
262 7
		$replace = [];
263 7
		foreach( $context as $key => $value ) {
264
			$replace['{'.$key.'}'] = $this->normalizeValue( $value );
265
		}
266 7
		return strtr( $message, $replace );
267
	}
268
269
	/**
270
	 * @param mixed $value
271
	 * @return bool
272
	 */
273 7
	protected function isObjectOrArray( $value )
274
	{
275 7
		return is_object( $value ) || is_array( $value );
276
	}
277
278
	/**
279
	 * @param mixed $value
280
	 * @return string
281
	 */
282
	protected function normalizeValue( $value )
283
	{
284
		if( $value instanceof DateTime ) {
285
			$value = $value->format( 'Y-m-d H:i:s' );
286
		}
287
		else if( $this->isObjectOrArray( $value )) {
288
			$value = json_encode( $value );
289
		}
290
		return (string)$value;
291
	}
292
293
	/**
294
	 * @return void
295
	 */
296 7
	protected function reset()
297
	{
298 7
		if( $this->size() > pow( 1024, 2 ) / 8 ) {
299
			$this->clear();
300
			file_put_contents(
301
				$this->file,
302
				$this->buildLogEntry( 'info', __( 'Console was automatically cleared (128 KB maximum size)', 'site-reviews' ))
303
			);
304
		}
305 7
	}
306
}
307