Passed
Push — master ( d47355...85ec42 )
by Paul
04:15
created

Console::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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