Passed
Push — master ( 498725...4036bc )
by Paul
04:49
created

Console::warning()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
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 ReflectionClass;
8
9
class Console
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 mixed $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 mixed $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 mixed $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 mixed $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 mixed $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
	 * @return string
118
	 */
119
	public function humanSize()
120
	{
121
		$bytes = $this->size();
122
		$exponent = floor( log( max( $bytes, 1 ), 1024 ));
123
		return round( $bytes / pow( 1024, $exponent ), 2 ).['B','KB','MB','GB'][$exponent];
124
	}
125
126
	/**
127
	 * Interesting events
128
	 * Example: User logs in, SQL logs
129
	 * @param mixed $message
130
	 * @param array $context
131
	 * @return static
132
	 */
133
	public function info( $message, array $context = [] )
134
	{
135
		return $this->log( static::INFO, $message, $context );
136
	}
137
138
	/**
139
	 * @param mixed $level
140
	 * @param mixed $message
141
	 * @return static
142
	 */
143
	public function log( $level, $message, array $context = [] )
144
	{
145
		$constants = (new ReflectionClass( __CLASS__ ))->getConstants();
146
		$constants = (array)apply_filters( 'site-reviews/log-levels', $constants );
147
		if( in_array( $level, $constants, true )) {
148
			$entry = $this->buildLogEntry( $level, $message, $context );
149
			file_put_contents( $this->file, $entry, FILE_APPEND|LOCK_EX );
150
			$this->reset();
151
		}
152
		return $this;
153
	}
154
155
	/**
156
	 * Normal but significant events
157
	 * @param mixed $message
158
	 * @param array $context
159
	 * @return static
160
	 */
161
	public function notice( $message, array $context = [] )
162
	{
163
		return $this->log( static::NOTICE, $message, $context );
164
	}
165
166
	/**
167
	 * @return int
168
	 */
169
	public function size()
170
	{
171
		return file_exists( $this->file )
172
			? filesize( $this->file )
173
			: 0;
174
	}
175
176
	/**
177
	 * Exceptional occurrences that are not errors
178
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong
179
	 * @param mixed $message
180
	 * @param array $context
181
	 * @return static
182
	 */
183
	public function warning( $message, array $context = [] )
184
	{
185
		return $this->log( static::WARNING, $message, $context );
186
	}
187
188
	/**
189
	 * @param string $level
190
	 * @param mixed $message
191
	 * @return string
192
	 */
193
	protected function buildLogEntry( $level, $message, array $context = [] )
194
	{
195
		return sprintf( '[%s] %s: %s%s'.PHP_EOL,
196
			current_time( 'mysql' ),
197
			strtoupper( $level ),
198
			$this->getDebugInformation(),
199
			$this->interpolate( $message, $context )
200
		);
201
	}
202
203
	/**
204
	 * @return void|string
205
	 */
206
	protected function getDebugInformation()
207
	{
208
		$caller = debug_backtrace( 0, 6 );
209
		$index = array_search( 'log', array_column( $caller, 'function' ));
210
		if( $index === false
211
			|| !isset( $caller[$index+2]['class'] )
212
			|| !isset( $caller[$index+2]['function'] )
213
		)return;
214
		return sprintf( '[%s()->%s:%s] ',
215
			$caller[$index+2]['class'],
216
			$caller[$index+2]['function'],
217
			$caller[$index+1]['line']
218
		);
219
	}
220
221
	/**
222
	 * Interpolates context values into the message placeholders
223
	 * @param mixed $message
224
	 * @param array $context
225
	 * @return string
226
	 */
227
	protected function interpolate( $message, $context = [] )
228
	{
229
		if( $this->isObjectOrArray( $message ) || !is_array( $context )) {
230
			return print_r( $message, true );
231
		}
232
		$replace = [];
233
		foreach( $context as $key => $value ) {
234
			$replace['{'.$key.'}'] = $this->normalizeValue( $value );
235
		}
236
		return strtr( $message, $replace );
237
	}
238
239
	/**
240
	 * @param mixed $value
241
	 * @return bool
242
	 */
243
	protected function isObjectOrArray( $value )
244
	{
245
		return is_object( $value ) || is_array( $value );
246
	}
247
248
	/**
249
	 * @param mixed $value
250
	 * @return string
251
	 */
252
	protected function normalizeValue( $value )
253
	{
254
		if( $value instanceof DateTime ) {
255
			$value = $value->format( 'Y-m-d H:i:s' );
256
		}
257
		else if( $this->isObjectOrArray( $value )) {
258
			$value = json_encode( $value );
259
		}
260
		return (string)$value;
261
	}
262
263
	/**
264
	 * @return void
265
	 */
266
	protected function reset()
267
	{
268
		if( $this->size() > pow( 1024, 2 ) / 2 ) {
269
			$this->clear();
270
			file_put_contents(
271
				$this->file,
272
				$this->buildLogEntry( 'info', __( 'Log has been automatically reset (512 KB max size)', 'site-reviews' ))
273
			);
274
		}
275
	}
276
}
277