Alphred
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  • Download

Namespaces

  • Alphred
  • None

Classes

  • Alphred
  1 <?php
  2 /**
  3  * Contains Log class for Alphred, providing basic logging functionality
  4  *
  5  * PHP version 5
  6  *
  7  * @package    Alphred
  8  * @copyright  Shawn Patrick Rice 2014
  9  * @license    http://opensource.org/licenses/MIT  MIT
 10  * @version    1.0.0
 11  * @author     Shawn Patrick Rice <rice@shawnrice.org>
 12  * @link       http://www.github.com/shawnrice/alphred
 13  * @link       http://shawnrice.github.io/alphred
 14  * @since      File available since Release 1.0.0
 15  *
 16  */
 17 
 18 namespace Alphred;
 19 
 20 /**
 21  * Simple static logging functionality that writes to files or STDERR
 22  *
 23  * @package   Alphred
 24  * @since     Class available since 1.0.0
 25  *
 26  */
 27 class Log {
 28 
 29     /**
 30      * An array that contans the valid log levels
 31      * @since 1.0.0
 32      * @var array
 33      */
 34     static $log_levels = [
 35                                 0 => 'DEBUG',
 36                               1 => 'INFO',
 37                               2 => 'WARNING',
 38                               3 => 'ERROR',
 39                               4 => 'CRITICAL',
 40         ];
 41 
 42         /**
 43          * Log a message to a file
 44          *
 45          * @since 1.0.0
 46          *
 47          * @param  string                   $message  the message to log
 48          * @param  integer|string   $level    the log level
 49          * @param  string                   $filename the filename of the log without an extension
 50          * @param  boolean|integer  $trace    how far back to trace
 51          */
 52         public function file( $message, $level = 1, $filename = 'workflow', $trace = false ) {
 53 
 54             // Check if the log level is loggable based on the threshold.
 55             // The threshold is defined as the constant ALPHED_LOG_LEVEL, and defaults to level 2 (WARNING).
 56             // Change this either in the workflow.ini file or by defining the constant ALPHRED_LOG_LEVEL
 57             // before you include Alphred.phar.
 58             if ( ! self::is_loggable( $level ) ) {
 59                 return false;
 60             }
 61 
 62             // Get the full path to the log file, and create the data directory if it doesn't exist
 63             $log_file = self::get_log_filename( $filename );
 64             // Get the trace
 65             $trace = self::trace( $trace );
 66             // Get the formatted date
 67             $date = self::date_file();
 68             // Normalize the log level
 69             $level = self::normalize_log_level( $level );
 70 
 71             // Construct the log entry
 72             $message = "[{$date}][{$trace}][{$level}] {$message}\n";
 73 
 74             // Write to the log file
 75             file_put_contents( $log_file, $message, FILE_APPEND | LOCK_EX );
 76         }
 77 
 78         /**
 79          * Log a message to the console (STDERR)
 80          *
 81          * @since 1.0.0
 82          *
 83          * @param  string                   $message the message to log
 84          * @param  string|integer   $level   the log level
 85          * @param  boolean|integer  $trace   how far back to trace
 86          */
 87         public function console( $message, $level = 1, $trace = false ) {
 88 
 89             // Check if the log level is loggable based on the threshold.
 90             // The threshold is defined as the constant ALPHED_LOG_LEVEL, and defaults to level 2 (WARNING).
 91             // Change this either in the workflow.ini file or by defining the constant ALPHRED_LOG_LEVEL
 92             // before you include Alphred.phar.
 93             if ( ! self::is_loggable( $level ) ) {
 94                 return false;
 95             }
 96 
 97             // Get the trace
 98             $trace = self::trace( $trace );
 99             // Get the formatted date
100             $date  = self::date_console();
101             // Normalize the log level
102             $level = self::normalize_log_level( $level );
103             file_put_contents( 'php://stderr', "[{$date}][{$trace}][{$level}] {$message}\n" );
104         }
105 
106         /**
107          * Log a message to both a file and the console
108          *
109          * @since 1.0.0
110          *
111          * @param  string                   $message  the message to log
112          * @param  integer|string   $level    the log level
113          * @param  string                   $filename the filename of the log without an extension
114          * @param  boolean|integer  $trace    how far back to trace
115          */
116         public function log( $message, $level = 1, $filename = 'workflow', $trace = false ) {
117             // Check if the log level is loggable based on the threshold.
118             // The threshold is defined as the constant ALPHED_LOG_LEVEL, and defaults to level 2 (WARNING).
119             // Change this either in the workflow.ini file or by defining the constant ALPHRED_LOG_LEVEL
120             // before you include Alphred.phar.
121             if ( ! self::is_loggable( $level ) ) {
122                 return false;
123             }
124             // Log message to console
125             self::console( $message, $level, $trace );
126             // Log message to file
127             self::file( $message, $level, $filename, $trace );
128         }
129 
130         /**
131          * Gets the full filepath to the log file
132          *
133          * @since 1.0.0
134          *
135          * @param  string $filename a filename for a log file
136          * @return string           the full filepath for a log file
137          */
138         private function get_log_filename( $filename ) {
139             // Attempt to get the workflow's data directory. If it isn't set (i.e. running outside of a workflow env),
140             // then just use the current directory.
141             if ( ! $dir = \Alphred\Globals::get( 'alfred_workflow_data' ) ) {
142                 $dir = '.';
143             } else {
144                 self::create_log_directory();
145             }
146             return "{$dir}/{$filename}.log";
147         }
148 
149         /**
150          * Creates the workflow's data directory if it does not exist.
151          *
152          * @since 1.0.0
153          */
154         private function create_log_directory() {
155             $directory = \Alphred\Globals::get( 'alfred_workflow_data' );
156             if ( $directory ) {
157                 if ( ! file_exists( $directory ) ) {
158                     mkdir( $directory, 0775, true );
159                 }
160             }
161         }
162 
163         /**
164         * Checks to see if the log needs to be rotated
165         *
166         * @since 1.0.0
167         */
168         private function check_log( $filename ) {
169             // ALPHRED_LOG_SIZE is define in bytes. It defaults to 1048576 and is set in
170             // `Alphred.php`. If you want to change the max size, then either define the
171             // max size in the INI file or define the constant ALPHRED_LOG_SIZE before
172             // you include `Alphred.phar`.
173             if ( filesize( self::get_log_filename( $filename ) ) > ALPHRED_LOG_SIZE ) {
174                 // The log is too big, so rotate it.
175                 self::rotate_log( $filename );
176             }
177         }
178 
179 
180         /**
181         * Rotates the log
182         *
183         * @since 1.0.0
184         */
185         private function rotate_log( $filename ) {
186             // Get the log filename
187             $log_file = self::get_log_filename( $filename );
188 
189             // Set the backup log filename
190             $old = substr( $log_file, -4 ) . '.1.log';
191 
192             // Check if an old filelog exists
193             if ( file_exists( $old ) ) {
194                 // It exists, so delete it
195                 unlink( $old );
196             }
197 
198             // Rename the current log to the old log
199             rename( $log_file, $old );
200 
201             // Create an empty log file
202             file_put_contents( $log_file, '' );
203         }
204 
205         /**
206          * Normalizes the log level, returning 'INFO' or 1 if invalid
207          *
208          * @since 1.0.0
209          *
210          * @param  integer|string $level the level represented as either a string or an integer
211          * @return string           the name of the log level
212          */
213         private function normalize_log_level( $level ) {
214             // Check if the log level is numeric
215             if ( is_numeric( $level ) ) {
216                 // It is numeric, so check if it is valid
217                 if ( isset( self::$log_levels[ $level ] ) ) {
218                     // It is valid, so return the name of the level
219                     return self::$log_levels[ $level ];
220                 } else {
221                     // The level is numeric but not valid, so log a warning to the console, and
222                     // return log level 1.
223                     self::console( "Log level {$level} is not valid. Setting to log level 1." );
224                     return self::$log_levels[1]; // This is an assumption note an error here
225                 }
226             }
227             // It is not numeric, so check if it is in the log levels array
228             if ( in_array( $level, self::$log_levels ) ) {
229                 // It is in the array, so return the value passed
230                 return $level;
231             }
232             // The log level is a string but is not valid, so log an error to the console
233             // and return log level 1.
234             self::console( "Log level {$level} is not valid. Setting to log level 1." );
235             return self::$log_levels[1]; // This is an assumption, note an error here
236         }
237 
238         /**
239          * Fetches information from a stack trace
240          *
241          * @since 1.0.0
242          *
243          * @param  boolean|integer $depth How far to do the trace, default is the last
244          * @return string          the file and line number of the trace
245          */
246         private function trace( $depth = false ) {
247 
248             // Get the relevant information from the backtrace
249             $trace = debug_backtrace();
250             // Check if the dpeth is defined, and if the depth is within the trace
251             if ( $depth && isset( $trace[ $depth ] ) ) {
252                 // The depth is defined, see if it is negative
253                 if ( $depth < 0 ) {
254                     // It's negative, so translate that to a positive number that we can use.
255                     $depth = count( $trace ) + $depth - 1;
256                 }
257                 // Get the explicit trace
258                 $trace = $trace[ $depth ];
259             } else {
260                 // Just get the last trace.
261                 $trace = end( $trace );
262             }
263 
264             // Set the filename
265             $file  = basename( $trace['file'] );
266             // Set the line number
267             $line  = $trace['line'];
268 
269             return "{$file}:{$line}";
270         }
271 
272 
273         /**
274          * Checks if a log level is within a display threshold
275          *
276          * @since 1.0.0
277          *
278          * @param  mixed  $level  Either a string or a
279          * @return boolean        Whether or not a value is above the logging threshold
280          */
281         private function is_loggable( $level ) {
282             // First, check is the level is numeric
283             if ( ! is_numeric( $level ) ) {
284                 // It is not numeric, so let's translate it to a number
285                 $level = array_search( $level, self::$log_levels ); // This needs error checking
286             }
287             // Return a boolean of whether or not the level is less than or equal to the logging threshold
288             return $level >= self::get_threshold();
289         }
290 
291         /**
292          * Gets the threshold for log messages
293          *
294          * @todo Implement exception for bad log level
295          * @since 1.0.0
296          *
297          * @return integer  an integer matching a log level
298          */
299         private function get_threshold() {
300             // Check is the threshold is defined as a number
301             if ( is_numeric( ALPHRED_LOG_LEVEL ) ) {
302                 // It is, so just return that number
303                 return ALPHRED_LOG_LEVEL;
304             } else if ( in_array( ALPHRED_LOG_LEVEL, self::$log_levels ) ) {
305                 // The threshold is not defined as a number, but it is a string defined
306                 // in the log_levels, so return the number
307                 return array_search( ALPHRED_LOG_LEVEL, self::$log_levels );
308             } else {
309                 // The threshold is not a number, and it is not a string that is in the
310                 // log_levels, so throw an exception and return 0.
311                 throw new Exception( "Alphred Log Level is not a valid level" );
312                 return 0;
313             }
314         }
315 
316         /**
317          * Gets the time formatted for a console display log
318          *
319          * @since 1.0.0
320          *
321          * @return string the time as HH:MM:SS
322          */
323         private function date_console() {
324             return date( 'H:i:s', time() );
325         }
326 
327         /**
328          * Gets a datestamp formatted for a file log
329          *
330          * @since 1.0.0
331          *
332          * @return string Formatted as YYYY-MM-DD HH:MM:SS
333          */
334         private function date_file() {
335             return date( 'Y-m-d H:i:s' );
336         }
337 }
Alphred API documentation generated by ApiGen