├── .gitignore ├── tests ├── PHPUnit_Framework_TestCase.php ├── bootstrap.php ├── PsrLogCompatTest.php └── AnalogTest.php ├── examples ├── ignore.php ├── stderr.php ├── syslog.php ├── default.php ├── slackbot.php ├── variable.php ├── mail.php ├── post.php ├── file.php ├── server.php ├── amon.php ├── psr-0.php ├── buffer.php ├── gelf.php ├── firephp.php ├── chromelogger.php ├── mongo.php ├── threshold.php ├── levelbuffer.php ├── multi.php └── SplClassLoader.php ├── .travis.yml ├── lib ├── Analog │ ├── Handler │ │ ├── Buffer │ │ │ └── Destructor.php │ │ ├── Ignore.php │ │ ├── Null.php │ │ ├── Stderr.php │ │ ├── Variable.php │ │ ├── Mail.php │ │ ├── File.php │ │ ├── Amon.php │ │ ├── Threshold.php │ │ ├── ChromeLogger.php │ │ ├── Slackbot.php │ │ ├── LevelName.php │ │ ├── Post.php │ │ ├── WPMail.php │ │ ├── GELF.php │ │ ├── Multi.php │ │ ├── Buffer.php │ │ ├── Mongo.php │ │ ├── LevelBuffer.php │ │ ├── Syslog.php │ │ └── FirePHP.php │ ├── Logger.php │ └── Analog.php ├── Analog.php └── ChromePhp.php ├── phpunit.xml.dist ├── composer.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | /composer.lock 3 | -------------------------------------------------------------------------------- /tests/PHPUnit_Framework_TestCase.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/stderr.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/syslog.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/default.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/slackbot.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.4 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - hhvm 8 | 9 | before_script: composer install --no-interaction --prefer-source 10 | script: 11 | - vendor/bin/phpunit --coverage-text --verbose 12 | -------------------------------------------------------------------------------- /examples/variable.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/mail.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/post.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/Analog/Handler/Buffer/Destructor.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/amon.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/psr-0.php: -------------------------------------------------------------------------------- 1 | register (); 7 | 8 | use \Analog\Analog; 9 | 10 | $log = ''; 11 | 12 | Analog::handler (\Analog\Handler\Variable::init ($log)); 13 | 14 | Analog::log ('Test one'); 15 | Analog::log ('Test two'); 16 | 17 | echo $log; 18 | 19 | ?> -------------------------------------------------------------------------------- /examples/buffer.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/gelf.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/Analog/Handler/Ignore.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/chromelogger.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/mongo.php: -------------------------------------------------------------------------------- 1 | testing->log->find (); 16 | foreach ($cur as $doc) { 17 | print_r ($doc); 18 | } 19 | $m->testing->log->remove (); 20 | 21 | ?> 22 | -------------------------------------------------------------------------------- /lib/Analog/Handler/Null.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/levelbuffer.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/Analog/Handler/Variable.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/Analog.php: -------------------------------------------------------------------------------- 1 | Analog\Handler\Variable::init ($errors), 11 | Analog::WARNING => Analog\Handler\Variable::init ($warnings), 12 | Analog::DEBUG => Analog\Handler\Variable::init ($debug) 13 | ))); 14 | 15 | Analog::log ('First error'); 16 | Analog::log ('Emergency!', Analog::URGENT); 17 | Analog::log ('A warning...', Analog::WARNING); 18 | Analog::log ('Some info', Analog::INFO); 19 | Analog::log ('Debugging output', Analog::DEBUG); 20 | 21 | echo $errors; 22 | echo "-----\n"; 23 | echo $warnings; 24 | echo "-----\n"; 25 | echo $debug; 26 | 27 | ?> -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "analog/analog", 3 | "type": "library", 4 | "description": "PHP logging class that can be extended via closures. Includes several pre-built handlers including file, mail, syslog, HTTP post, and MongoDB.", 5 | "keywords": ["log", "logging", "logger", "syslog", "error", "debug", "debugging", "alerts"], 6 | "homepage": "https://github.com/jbroadway/analog", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Johnny Broadway", 11 | "email": "johnny@johnnybroadway.com", 12 | "homepage": "http://www.johnnybroadway.com/" 13 | } 14 | ], 15 | "require": { 16 | "psr/log": "1.*", 17 | "php": ">=5.3.2" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5" 21 | }, 22 | "autoload": { 23 | "psr-0": { 24 | "Analog": "lib/" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/Analog/Handler/Mail.php: -------------------------------------------------------------------------------- 1 | handler (Variable::init ($this->log)); 21 | $logger->format ("%3\$d %4\$s\n"); 22 | return $logger; 23 | } 24 | 25 | public function getLogs () { 26 | $logger = $this->getLogger (); 27 | 28 | $logs = explode ("\n", trim ($this->log)); 29 | 30 | foreach ($logs as $key => $line) { 31 | list ($level, $msg) = explode (' ', $line, 2); 32 | $logs[$key] = $logger->convert_log_level ((int) $level, true) . ' ' . $msg; 33 | } 34 | 35 | return $logs; 36 | } 37 | } -------------------------------------------------------------------------------- /lib/Analog/Handler/Amon.php: -------------------------------------------------------------------------------- 1 | $host, 24 | 'port' => $port, 25 | 'application_key' => $key 26 | )); 27 | 28 | $tags = array ( 29 | 0 => 'urgent', 30 | 1 => 'alert', 31 | 2 => 'critical', 32 | 3 => 'error', 33 | 4 => 'warning', 34 | 5 => 'notice', 35 | 6 => 'info', 36 | 7 => 'debug' 37 | ); 38 | 39 | return function ($info) use ($tags) { 40 | \Amon::log ($info, array ($tags[$info['level']])); 41 | }; 42 | } 43 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 Johnny Broadway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/Analog/Handler/Threshold.php: -------------------------------------------------------------------------------- 1 | 'DEBUG', 24 | \Analog\Analog::INFO => 'INFO', 25 | \Analog\Analog::NOTICE => 'NOTICE', 26 | \Analog\Analog::WARNING => 'WARNING', 27 | \Analog\Analog::ERROR => 'ERROR', 28 | \Analog\Analog::CRITICAL => 'CRITICAL', 29 | \Analog\Analog::ALERT => 'ALERT', 30 | \Analog\Analog::URGENT => 'URGENT' 31 | ); 32 | 33 | /** 34 | * This contains the handler to send to 35 | */ 36 | public static $handler; 37 | 38 | public static function init ($handler) { 39 | self::$handler = $handler; 40 | 41 | return function ($info) { 42 | if (isset(self::$log_levels[$info['level']])) { 43 | $info['level'] = self::$log_levels[$info['level']]; 44 | } 45 | $handler = LevelName::$handler; 46 | $handler ($info); 47 | }; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /lib/Analog/Handler/Post.php: -------------------------------------------------------------------------------- 1 | setHost ($info['machine']); 33 | $message->setLevel ($info['level']); 34 | 35 | if (is_array ($info['message'])) { 36 | $message->setShortMessage ($info['message'][0]); 37 | $message->setFullMessage ($info['message'][0]); 38 | $message->setFile ($info['message'][1]); 39 | $message->setLine ($info['message'][2]); 40 | } else { 41 | $message->setShortMessage ($info['message']); 42 | $message->setFullMessage ($info['message']); 43 | } 44 | 45 | $publisher->publish ($message); 46 | }; 47 | } 48 | } -------------------------------------------------------------------------------- /lib/Analog/Handler/Multi.php: -------------------------------------------------------------------------------- 1 | array( 14 | * Analog\Handler\Mail::init( $to, $subject, $from ), 15 | * Analog\Handler\Stderr::init() 16 | * ), 17 | * 18 | * // Warnings are sent here 19 | * Analog::WARNING => Analog\Handler\File::init( 'logs/warnings.log' ), 20 | * 21 | * // Debug and info messages sent here 22 | * Analog::DEBUG => Analog\Handler\Ignore::init() // do nothing 23 | * ) ) ); 24 | * 25 | * // will be ignored 26 | * Analog::log ('Ignore me', Analog::DEBUG); 27 | * 28 | * // will be written to logs/warnings.log 29 | * Analog::log ('Log me', Analog::WARNING); 30 | * 31 | * // will trigger an email notice 32 | * Analog::log ('Uh oh...', Analog::ERROR); 33 | */ 34 | class Multi { 35 | public static function init ($handlers) { 36 | return function ($info) use ($handlers) { 37 | $level = is_numeric ($info['level']) ? $info['level'] : 3; 38 | while ($level <= 7) { 39 | if ( isset ( $handlers[ $level ] ) ) { 40 | 41 | if ( ! is_array( $handlers[ $level ] ) ) { 42 | $handlers[ $level ] = array( $handlers[ $level ] ); 43 | } 44 | 45 | foreach ( $handlers[ $level ] as $handler ) { 46 | $handler( $info ); 47 | } 48 | 49 | return; 50 | } 51 | $level++; 52 | } 53 | }; 54 | } 55 | } -------------------------------------------------------------------------------- /lib/Analog/Handler/Buffer.php: -------------------------------------------------------------------------------- 1 | {$database}; 35 | } else { 36 | if (class_exists('\MongoDB\Driver\Manager')) { 37 | $driver = 'mongodb'; 38 | $manager = new \MongoDB\Driver\Manager("mongodb://$server"); 39 | } else { 40 | $conn = new \MongoClient ("mongodb://$server"); 41 | $db = $conn->{$database}; 42 | } 43 | } 44 | if ($driver == 'mongodb') { 45 | return function ($info) use ($manager, $database, $collection) { 46 | $bulk = new \MongoDB\Driver\BulkWrite; 47 | $bulk->insert($info); 48 | $dbAndColl = $database.'.'.$collection; 49 | $manager->executeBulkWrite($dbAndColl, $bulk); 50 | }; 51 | } else { 52 | return function ($info) use ($db, $collection) { 53 | $db->{$collection}->insert ($info); 54 | }; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/Analog/Handler/LevelBuffer.php: -------------------------------------------------------------------------------- 1 | LOG_DEBUG, 16 | \Analog\Analog::INFO => LOG_INFO, 17 | \Analog\Analog::NOTICE => LOG_NOTICE, 18 | \Analog\Analog::WARNING => LOG_WARNING, 19 | \Analog\Analog::ERROR => LOG_ERR, 20 | \Analog\Analog::CRITICAL => LOG_CRIT, 21 | \Analog\Analog::ALERT => LOG_ALERT, 22 | \Analog\Analog::URGENT => LOG_EMERG 23 | ); 24 | 25 | public static $facilities = array ( 26 | 'auth' => LOG_AUTH, 27 | 'authpriv' => LOG_AUTHPRIV, 28 | 'cron' => LOG_CRON, 29 | 'daemon' => LOG_DAEMON, 30 | 'kern' => LOG_KERN, 31 | 'lpr' => LOG_LPR, 32 | 'mail' => LOG_MAIL, 33 | 'news' => LOG_NEWS, 34 | 'syslog' => LOG_SYSLOG, 35 | 'user' => LOG_USER, 36 | 'uucp' => LOG_UUCP 37 | ); 38 | 39 | public static function init ($ident, $facility) { 40 | if (! defined ('PHP_WINDOWS_VERSION_BUILD')) { 41 | self::$facilities['local0'] = LOG_LOCAL0; 42 | self::$facilities['local1'] = LOG_LOCAL1; 43 | self::$facilities['local2'] = LOG_LOCAL2; 44 | self::$facilities['local3'] = LOG_LOCAL3; 45 | self::$facilities['local4'] = LOG_LOCAL4; 46 | self::$facilities['local5'] = LOG_LOCAL5; 47 | self::$facilities['local6'] = LOG_LOCAL6; 48 | self::$facilities['local7'] = LOG_LOCAL7; 49 | } 50 | 51 | if (array_key_exists (strtolower ($facility), self::$facilities)) { 52 | $facility = self::$facilities[strtolower ($facility)]; 53 | } elseif (! in_array ($facility, array_values (self::$facilities), true)) { 54 | throw new \UnexpectedValueException ('Unknown facility value "' . $facility . '"'); 55 | } 56 | 57 | return function ($info) use ($ident, $facility) { 58 | if (! openlog ($ident, LOG_PID, $facility)) { 59 | throw new \LogicException ('Can\'t open syslog for ident "' . $ident . '" and facility "' . $facility . '"'); 60 | } 61 | 62 | syslog (Syslog::$levels[$info['level']], vsprintf ('%1$s: %4$s', $info)); 63 | 64 | closelog (); 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/Analog/Handler/FirePHP.php: -------------------------------------------------------------------------------- 1 | 'LOG', 25 | \Analog\Analog::INFO => 'INFO', 26 | \Analog\Analog::NOTICE => 'INFO', 27 | \Analog\Analog::WARNING => 'WARN', 28 | \Analog\Analog::ERROR => 'ERROR', 29 | \Analog\Analog::CRITICAL => 'ERROR', 30 | \Analog\Analog::ALERT => 'ERROR', 31 | \Analog\Analog::URGENT => 'ERROR' 32 | ); 33 | 34 | /** 35 | * Message index increases by 1 each time a message is sent. 36 | */ 37 | private static $message_index = 1; 38 | 39 | /** 40 | * Formats a log header to be sent. 41 | */ 42 | public static function format_header ($info) { 43 | if (is_array ($info['message'])) { 44 | $extra = array ( 45 | 'Type' => self::$log_levels[$info['level']], 46 | 'File' => $info['message'][1], 47 | 'Line' => $info['message'][2] 48 | ); 49 | $info['message'] = $info['message'][0]; 50 | } else { 51 | $extra = array ('Type' => self::$log_levels[$info['level']]); 52 | } 53 | 54 | $json = json_encode (array ($extra, $info['message'])); 55 | 56 | return sprintf ('X-Wf-1-1-1-%d: %s|%s|', self::$message_index++, strlen ($json), $json); 57 | } 58 | 59 | /** 60 | * Sends the initial headers if FirePHP is available then returns a 61 | * closure that handles sending log messages. 62 | */ 63 | public static function init () { 64 | if (! isset ($_SERVER['HTTP_USER_AGENT']) 65 | || preg_match ('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT']) 66 | || isset ($_SERVER['HTTP_X_FIREPHP_VERSION'])) { 67 | 68 | header ('X-Wf-Protocol-1: http://meta.wildfirehq.org/Protocol/JsonStream/0.2'); 69 | header ('X-Wf-1-Plugin-1: http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'); 70 | header ('X-Wf-1-Structure-1: http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'); 71 | } 72 | 73 | return function ($info) { 74 | header (FirePHP::format_header ($info)); 75 | }; 76 | } 77 | } -------------------------------------------------------------------------------- /tests/AnalogTest.php: -------------------------------------------------------------------------------- 1 | assertStringMatchesFormat ( 21 | "localhost - %d-%d-%d %d:%d:%d - 3 - Foo\n", 22 | file_get_contents (Analog::handler ()) 23 | ); 24 | unlink (Analog::handler ()); 25 | } 26 | 27 | /** 28 | * @depends test_default 29 | */ 30 | function test_format () { 31 | // Test changing the format string and write again 32 | Analog::$format = "%s, %s, %d, %s\n"; 33 | Analog::log ('Foo'); 34 | $this->assertStringMatchesFormat ( 35 | "localhost, %d-%d-%d %d:%d:%d, 3, Foo\n", 36 | file_get_contents (Analog::handler ()) 37 | ); 38 | unlink (Analog::handler ()); 39 | } 40 | 41 | /** 42 | * @depends test_format 43 | */ 44 | function test_tz_and_dates () { 45 | // Test changing the date_format 46 | Analog::$date_format = 'r'; // RFC2822 format 47 | Analog::log ('Foo'); 48 | $this->assertStringMatchesFormat ( 49 | "localhost, %s, %d %s %d %d:%d:%d +0000, 3, Foo\n", 50 | file_get_contents (Analog::handler ()) 51 | ); 52 | unlink (Analog::handler ()); 53 | 54 | // Test changing the timezone 55 | Analog::$timezone = 'CST'; 56 | Analog::log ('Foo'); 57 | 58 | $dt = new \DateTime ('now', new \DateTimeZone (Analog::$timezone)); 59 | $zone_offset = $dt->format ('O'); 60 | 61 | $this->assertStringMatchesFormat ( 62 | "localhost, %s, %d %s %d %d:%d:%d $zone_offset, 3, Foo\n", 63 | file_get_contents (Analog::handler ()) 64 | ); 65 | unlink (Analog::handler ()); 66 | 67 | Analog::$date_format = 'Y-m-d H:i:s'; 68 | Analog::$timezone = 'GMT'; 69 | } 70 | 71 | /** 72 | * @depends test_tz_and_dates 73 | */ 74 | function test_handler () { 75 | // Test logging using a closure 76 | Analog::handler (function ($msg) { 77 | AnalogTest::$log .= vsprintf (Analog::$format, $msg); 78 | }); 79 | 80 | Analog::log ('Testing'); 81 | $this->assertStringMatchesFormat ( 82 | "localhost, %d-%d-%d %d:%d:%d, 3, Testing\n", 83 | self::$log 84 | ); 85 | 86 | self::$log = ''; 87 | } 88 | 89 | /** 90 | * @depends test_handler 91 | */ 92 | function test_level () { 93 | // Test default_level change 94 | Analog::$default_level = 1; 95 | Analog::log ('Testing'); 96 | $this->assertStringMatchesFormat ( 97 | "localhost, %d-%d-%d %d:%d:%d, 1, Testing\n", 98 | self::$log 99 | ); 100 | 101 | Analog::$default_level = 3; 102 | } 103 | 104 | /* 105 | * @depends test_level 106 | * @covers Analog::urgent 107 | * @covers Analog::alert 108 | * @covers Analog::critical 109 | * @covers Analog::error 110 | * @covers Analog::warning 111 | * @covers Analog::notice 112 | * @covers Analog::info 113 | * @covers Analog::debug 114 | */ 115 | function test_aliases () { 116 | self::$log = ''; 117 | 118 | Analog::urgent ('Testing'); 119 | $this->assertStringMatchesFormat ( 120 | "localhost, %d-%d-%d %d:%d:%d, 0, Testing\n", 121 | self::$log 122 | ); 123 | 124 | self::$log = ''; 125 | 126 | Analog::alert ('Testing'); 127 | $this->assertStringMatchesFormat ( 128 | "localhost, %d-%d-%d %d:%d:%d, 1, Testing\n", 129 | self::$log 130 | ); 131 | 132 | self::$log = ''; 133 | 134 | Analog::error ('Testing'); 135 | $this->assertStringMatchesFormat ( 136 | "localhost, %d-%d-%d %d:%d:%d, 3, Testing\n", 137 | self::$log 138 | ); 139 | 140 | self::$log = ''; 141 | 142 | Analog::warning ('Testing'); 143 | $this->assertStringMatchesFormat ( 144 | "localhost, %d-%d-%d %d:%d:%d, 4, Testing\n", 145 | self::$log 146 | ); 147 | 148 | self::$log = ''; 149 | 150 | Analog::notice ('Testing'); 151 | $this->assertStringMatchesFormat ( 152 | "localhost, %d-%d-%d %d:%d:%d, 5, Testing\n", 153 | self::$log 154 | ); 155 | 156 | self::$log = ''; 157 | 158 | Analog::info ('Testing'); 159 | $this->assertStringMatchesFormat ( 160 | "localhost, %d-%d-%d %d:%d:%d, 6, Testing\n", 161 | self::$log 162 | ); 163 | 164 | self::$log = ''; 165 | 166 | Analog::debug ('Testing'); 167 | $this->assertStringMatchesFormat ( 168 | "localhost, %d-%d-%d %d:%d:%d, 7, Testing\n", 169 | self::$log 170 | ); 171 | 172 | self::$log = ''; 173 | } 174 | } 175 | 176 | ?> 177 | -------------------------------------------------------------------------------- /examples/SplClassLoader.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | /** 22 | * SplClassLoader implementation that implements the technical interoperability 23 | * standards for PHP 5.3 namespaces and class names. 24 | * 25 | * http://groups.google.com/group/php-standards/web/psr-0-final-proposal?pli=1 26 | * 27 | * // Example which loads classes for the Doctrine Common package in the 28 | * // Doctrine\Common namespace. 29 | * $classLoader = new SplClassLoader('Doctrine\Common', '/path/to/doctrine'); 30 | * $classLoader->register(); 31 | * 32 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 33 | * @author Jonathan H. Wage 34 | * @author Roman S. Borschel 35 | * @author Matthew Weier O'Phinney 36 | * @author Kris Wallsmith 37 | * @author Fabien Potencier 38 | */ 39 | class SplClassLoader 40 | { 41 | private $_fileExtension = '.php'; 42 | private $_namespace; 43 | private $_includePath; 44 | private $_namespaceSeparator = '\\'; 45 | 46 | /** 47 | * Creates a new SplClassLoader that loads classes of the 48 | * specified namespace. 49 | * 50 | * @param string $ns The namespace to use. 51 | */ 52 | public function __construct($ns = null, $includePath = null) 53 | { 54 | $this->_namespace = $ns; 55 | $this->_includePath = $includePath; 56 | } 57 | 58 | /** 59 | * Sets the namespace separator used by classes in the namespace of this class loader. 60 | * 61 | * @param string $sep The separator to use. 62 | */ 63 | public function setNamespaceSeparator($sep) 64 | { 65 | $this->_namespaceSeparator = $sep; 66 | } 67 | 68 | /** 69 | * Gets the namespace seperator used by classes in the namespace of this class loader. 70 | * 71 | * @return void 72 | */ 73 | public function getNamespaceSeparator() 74 | { 75 | return $this->_namespaceSeparator; 76 | } 77 | 78 | /** 79 | * Sets the base include path for all class files in the namespace of this class loader. 80 | * 81 | * @param string $includePath 82 | */ 83 | public function setIncludePath($includePath) 84 | { 85 | $this->_includePath = $includePath; 86 | } 87 | 88 | /** 89 | * Gets the base include path for all class files in the namespace of this class loader. 90 | * 91 | * @return string $includePath 92 | */ 93 | public function getIncludePath() 94 | { 95 | return $this->_includePath; 96 | } 97 | 98 | /** 99 | * Sets the file extension of class files in the namespace of this class loader. 100 | * 101 | * @param string $fileExtension 102 | */ 103 | public function setFileExtension($fileExtension) 104 | { 105 | $this->_fileExtension = $fileExtension; 106 | } 107 | 108 | /** 109 | * Gets the file extension of class files in the namespace of this class loader. 110 | * 111 | * @return string $fileExtension 112 | */ 113 | public function getFileExtension() 114 | { 115 | return $this->_fileExtension; 116 | } 117 | 118 | /** 119 | * Installs this class loader on the SPL autoload stack. 120 | */ 121 | public function register() 122 | { 123 | spl_autoload_register(array($this, 'loadClass')); 124 | } 125 | 126 | /** 127 | * Uninstalls this class loader from the SPL autoloader stack. 128 | */ 129 | public function unregister() 130 | { 131 | spl_autoload_unregister(array($this, 'loadClass')); 132 | } 133 | 134 | /** 135 | * Loads the given class or interface. 136 | * 137 | * @param string $className The name of the class to load. 138 | * @return void 139 | */ 140 | public function loadClass($className) 141 | { 142 | if (null === $this->_namespace || $this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))) { 143 | $fileName = ''; 144 | $namespace = ''; 145 | if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator))) { 146 | $namespace = substr($className, 0, $lastNsPos); 147 | $className = substr($className, $lastNsPos + 1); 148 | $fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; 149 | } 150 | $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension; 151 | 152 | require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Analog - PHP micro logging package 2 | 3 | * Copyright: (c) 2012-Present Johnny Broadway 4 | * License: http://www.opensource.org/licenses/mit-license.php 5 | 6 | Click here to lend your support to: Analog and make a donation at www.pledgie.com ! 7 | 8 | A PHP logging package based on the idea of using closures 9 | for configurability and extensibility. It functions as a static class, but you can 10 | completely control the writing of log messages through a closure function 11 | (aka [anonymous functions](http://ca3.php.net/manual/en/functions.anonymous.php)), 12 | or use the `Analog\Logger` wrapper that implements the 13 | [PSR-3 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). 14 | 15 | By default, this class will write to a file named `sys_get_temp_dir() . '/analog.txt'` 16 | using the format `"machine - date - level - message\n"`, making it usable with no 17 | customization necessary. 18 | 19 | Analog also comes with over a dozen pre-written handlers in the Analog/Handlers folder, 20 | with examples for each in the examples folder. These include: 21 | 22 | * Amon - Send logs to the [Amon](http://amon.cx/) server monitoring tool 23 | * Buffer - Buffer messages to send all at once (works with File, Mail, Stderr, and Variable handlers) 24 | * ChromeLogger - Sends messages to [Chrome Logger](http://craig.is/writing/chrome-logger) browser plugin 25 | * File - Append messages to a file 26 | * FirePHP - Send messages to [FirePHP](http://www.firephp.org/) browser plugin 27 | * GELF - Send message to the [Graylog2](http://www.graylog2.org/) log management server 28 | * Ignore - Do nothing 29 | * LevelBuffer - Buffer messages and send only if sufficient error level reached 30 | * LevelName - Convert log level numbers to names in log output 31 | * Mail - Send email notices 32 | * Mongo - Save to MongoDB collection 33 | * Multi - Send different log levels to different handlers 34 | * Post - Send messages over HTTP POST to another machine 35 | * Slackbot - Post messages to Slack via Slackbot 36 | * Stderr - Send messages to STDERR 37 | * Syslog - Send messages to syslog 38 | * Threshold - Only writes log messages above a certain threshold 39 | * Variable - Buffer messages to a variable reference. 40 | 41 | So while it's a micro class, it's highly extensible and very capable out of the box too. 42 | 43 | ### Rationale 44 | 45 | I wrote this because I wanted something very small and simple like 46 | [KLogger](https://github.com/katzgrau/KLogger), and preferably not torn out 47 | of a wider framework if possible. After searching, I wasn't happy with the 48 | single-purpose libraries I found. With KLogger for example, I didn't want an 49 | object instance but rather a static class, and I wanted more flexibility in 50 | the back-end. 51 | 52 | I also found some that had the flexibility also had more complexity, for example 53 | [Monolog](https://github.com/Seldaek/monolog) is dozens of source files (not incl. tests). 54 | With closures, this seemed to be a good balance of small without sacrificing 55 | flexibility. 56 | 57 | > What about Analog, the logfile analyzer? Well, since it hasn't been updated 58 | > since 2004, I think it's safe to call a single-file PHP logging class the 59 | > same thing without it being considered stepping on toes :) 60 | 61 | ### Usage 62 | 63 | Basic usage, with a custom handler function: 64 | 65 | ```php 66 | mydb->log->insert ($info); 80 | }); 81 | 82 | // Log an alert 83 | Analog::log ('The sky is falling!', Analog::ALERT); 84 | 85 | // Log some debug info 86 | Analog::log ('Debugging info', Analog::DEBUG); 87 | 88 | ?> 89 | ``` 90 | 91 | Usage with [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md), 92 | [Composer](http://getcomposer.org/), and the FirePHP handler: 93 | 94 | 1\. Create a `composer.json` file in the root of your project with the following contents. 95 | 96 | ```json 97 | { 98 | "require": { 99 | "analog/analog": "dev-master" 100 | } 101 | } 102 | ``` 103 | 104 | 2\. Run `php composer.phar install` from the terminal in the root of your project. 105 | 106 | 3\. Include Composer's autoloader and use the `Analog\Analog` class. 107 | 108 | ```php 109 | 122 | ``` 123 | 124 | Usage with [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md), 125 | [Composer](http://getcomposer.org/), and the Variable handler: 126 | 127 | 1\. Create a `composer.json` file in the root of your project with the following contents. 128 | 129 | ```json 130 | { 131 | "require": { 132 | "analog/analog": "dev-master" 133 | } 134 | } 135 | ``` 136 | 137 | 2\. Run `php composer.phar install` from the terminal in the root of your project. 138 | 139 | 3\. Include Composer's autoloader and use the `Analog\Logger` class. 140 | 141 | ```php 142 | handler (Analog\Handler\Variable::init ($log)); 151 | 152 | $logger->alert ('Things are really happening right now!'); 153 | 154 | var_dump ($log); 155 | 156 | ?> 157 | ``` 158 | 159 | For more examples, see the [examples](https://github.com/jbroadway/analog/tree/master/examples) folder. 160 | -------------------------------------------------------------------------------- /lib/Analog/Logger.php: -------------------------------------------------------------------------------- 1 | notice ('Things are really happening right now.'); 48 | * 49 | * ?> 50 | * 51 | * @package Analog 52 | * @author Johnny Broadway 53 | */ 54 | class Logger implements LoggerInterface { 55 | /** 56 | * Converts from PSR-3 log levels to Analog log levels. 57 | */ 58 | public function convert_log_level ($level, $reverse = false) { 59 | if ($reverse) { 60 | switch ($level) { 61 | case Analog::URGENT: 62 | return LogLevel::EMERGENCY; 63 | case Analog::ALERT: 64 | return LogLevel::ALERT; 65 | case Analog::CRITICAL: 66 | return LogLevel::CRITICAL; 67 | case Analog::ERROR: 68 | return LogLevel::ERROR; 69 | case Analog::WARNING: 70 | return LogLevel::WARNING; 71 | case Analog::NOTICE: 72 | return LogLevel::NOTICE; 73 | case Analog::INFO: 74 | return LogLevel::INFO; 75 | case Analog::DEBUG: 76 | return LogLevel::DEBUG; 77 | } 78 | throw new InvalidArgumentException ('Level "' . $level . '" is not defined.'); 79 | } else { 80 | switch ($level) { 81 | case LogLevel::EMERGENCY: 82 | return Analog::URGENT; 83 | case LogLevel::ALERT: 84 | return Analog::ALERT; 85 | case LogLevel::CRITICAL: 86 | return Analog::CRITICAL; 87 | case LogLevel::ERROR: 88 | return Analog::ERROR; 89 | case LogLevel::WARNING: 90 | return Analog::WARNING; 91 | case LogLevel::NOTICE: 92 | return Analog::NOTICE; 93 | case LogLevel::INFO: 94 | return Analog::INFO; 95 | case LogLevel::DEBUG: 96 | return Analog::DEBUG; 97 | } 98 | throw new InvalidArgumentException ('Level "' . $level . '" is not defined.'); 99 | } 100 | } 101 | 102 | /** 103 | * Interpolates context values into the message placeholders. 104 | */ 105 | private function interpolate ($message, array $context = array ()) { 106 | if (is_array ($message)) { 107 | return $message; 108 | } 109 | 110 | // build a replacement array with braces around the context keys 111 | $replace = array (); 112 | foreach ($context as $key => $val) { 113 | if (is_object ($val) && get_class ($val) === 'DateTime') { 114 | $val = $val->format ('Y-m-d H:i:s'); 115 | } elseif (is_object ($val)) { 116 | $val = json_encode ($val); 117 | } elseif (is_array ($val)) { 118 | $val = json_encode ($val); 119 | } elseif (is_resource ($val)) { 120 | $val = (string) $val; 121 | } 122 | $replace['{' . $key . '}'] = $val; 123 | } 124 | 125 | // interpolate replacement values into the the message and return 126 | return strtr ($message, $replace); 127 | } 128 | 129 | /** 130 | * Sets the Analog log handler. 131 | */ 132 | public function handler ($handler) { 133 | Analog::handler ($handler); 134 | } 135 | 136 | /** 137 | * Sets the log message format. 138 | */ 139 | public function format ($format) { 140 | Analog::$format = $format; 141 | } 142 | 143 | /** 144 | * System is unusable. 145 | */ 146 | public function emergency ($message, array $context = array ()) { 147 | $this->_log (Analog::URGENT, $message, $context); 148 | } 149 | 150 | /** 151 | * Action must be taken immediately. 152 | */ 153 | public function alert ($message, array $context = array ()) { 154 | $this->_log (Analog::ALERT, $message, $context); 155 | } 156 | 157 | /** 158 | * Critical conditions. 159 | */ 160 | public function critical ($message, array $context = array ()) { 161 | $this->_log (Analog::CRITICAL, $message, $context); 162 | } 163 | 164 | /** 165 | * Runtime errors that do not require immediate action but should typically 166 | * be logged and monitored. 167 | */ 168 | public function error ($message, array $context = array ()) { 169 | $this->_log (Analog::ERROR, $message, $context); 170 | } 171 | 172 | /** 173 | * Exceptional occurrences that are not errors. 174 | */ 175 | public function warning ($message, array $context = array ()) { 176 | $this->_log (Analog::WARNING, $message, $context); 177 | } 178 | 179 | /** 180 | * Normal but significant events. 181 | */ 182 | public function notice ($message, array $context = array ()) { 183 | $this->_log (Analog::NOTICE, $message, $context); 184 | } 185 | 186 | /** 187 | * Interesting events. 188 | */ 189 | public function info ($message, array $context = array ()) { 190 | $this->_log (Analog::INFO, $message, $context); 191 | } 192 | 193 | /** 194 | * Detailed debug information. 195 | */ 196 | public function debug ($message, array $context = array ()) { 197 | $this->_log (Analog::DEBUG, $message, $context); 198 | } 199 | 200 | /** 201 | * Logs with an arbitrary level. 202 | */ 203 | public function log ($level, $message, array $context = array ()) { 204 | $this->_log ( 205 | $this->convert_log_level ($level), 206 | $message, 207 | $context 208 | ); 209 | } 210 | 211 | /** 212 | * Perform the logging to Analog after the log level has been converted. 213 | */ 214 | private function _log ($level, $message, $context) { 215 | Analog::log ( 216 | $this->interpolate ($message, $context), 217 | $level 218 | ); 219 | } 220 | } -------------------------------------------------------------------------------- /lib/Analog/Analog.php: -------------------------------------------------------------------------------- 1 | mydb->log->insert ($info); 68 | * }); 69 | * 70 | * // Log an alert 71 | * Analog::log ('The sky is falling!', Analog::ALERT); 72 | * 73 | * // Log some debug info 74 | * Analog::log ('Debugging info', Analog::DEBUG); 75 | * 76 | * ?> 77 | * 78 | * @package Analog 79 | * @author Johnny Broadway 80 | */ 81 | class Analog { 82 | /** 83 | * List of severity levels. 84 | */ 85 | const URGENT = 0; // It's an emergency 86 | const ALERT = 1; // Immediate action required 87 | const CRITICAL = 2; // Critical conditions 88 | const ERROR = 3; // An error occurred 89 | const WARNING = 4; // Something unexpected happening 90 | const NOTICE = 5; // Something worth noting 91 | const INFO = 6; // Information, not an error 92 | const DEBUG = 7; // Debugging messages 93 | 94 | /** 95 | * The default format for log messages (machine, date, level, message) 96 | * written to a file. To change the order of items in the string, 97 | * use `%1$s` references. 98 | */ 99 | public static $format = "%s - %s - %d - %s\n"; 100 | 101 | /** 102 | * The default date/time format for log messages written to a file. 103 | * Feeds into the `$format` property. 104 | */ 105 | public static $date_format = 'Y-m-d H:i:s'; 106 | 107 | /** 108 | * Timezone for date/time values. 109 | */ 110 | public static $timezone = 'GMT'; 111 | 112 | /** 113 | * Default log level. 114 | */ 115 | public static $default_level = 3; 116 | 117 | /** 118 | * The method of saving the log output. See Analog::handler() 119 | * for details on setting this. 120 | */ 121 | private static $handler = null; 122 | 123 | /** 124 | * The name of the current machine, defaults to $_SERVER['SERVER_ADDR'] 125 | * on first call to format_message(), or 'localhost' if $_SERVER['SERVER_ADDR'] 126 | * is not set (e.g., during CLI use). 127 | */ 128 | public static $machine = null; 129 | 130 | /** 131 | * Handler getter/setter. If no handler is provided, it will set it to 132 | * sys_get_temp_dir() . '/analog.txt' as a default. Usage: 133 | * 134 | * Analog::handler ('my_log.txt'); 135 | * 136 | * Using a closure: 137 | * 138 | * Analog::handler (function ($msg) { 139 | * return error_log ($msg); 140 | * }); 141 | */ 142 | public static function handler ($handler = false) { 143 | if ($handler) { 144 | self::$handler = $handler; 145 | } elseif (! self::$handler) { 146 | self::$handler = realpath (sys_get_temp_dir ()) . DIRECTORY_SEPARATOR . 'analog.txt'; 147 | } 148 | return self::$handler; 149 | } 150 | 151 | /** 152 | * Get the log info as an associative array. 153 | */ 154 | private static function get_struct ($message, $level) { 155 | if (self::$machine === null) { 156 | self::$machine = (isset ($_SERVER['SERVER_ADDR'])) ? $_SERVER['SERVER_ADDR'] : 'localhost'; 157 | } 158 | 159 | $dt = new \DateTime ('now', new \DateTimeZone (self::$timezone)); 160 | 161 | return array ( 162 | 'machine' => self::$machine, 163 | 'date' => $dt->format (self::$date_format), 164 | 'level' => $level, 165 | 'message' => $message 166 | ); 167 | } 168 | 169 | /** 170 | * Write a raw message to the log using a function or the default 171 | * file logging. 172 | */ 173 | private static function write ($struct) { 174 | $handler = self::handler (); 175 | 176 | if (! $handler instanceof \Closure) { 177 | $handler = \Analog\Handler\File::init ($handler); 178 | } 179 | return $handler ($struct); 180 | } 181 | 182 | /** 183 | * This is the main function you will call to log messages. 184 | * Defaults to severity level Analog::ERROR, which can be 185 | * changed via the `$default_level` property. 186 | * Usage: 187 | * 188 | * Analog::log ('Debug info', Analog::DEBUG); 189 | */ 190 | public static function log ($message, $level = null) { 191 | $level = ($level !== null) ? $level : self::$default_level; 192 | return self::write (self::get_struct ($message, $level)); 193 | } 194 | 195 | /** 196 | * Shortcut method for Analog::log($info, Analog::URGENT) 197 | * Usage: 198 | * 199 | * Analog::urgent ('Debug info'); 200 | */ 201 | public static function urgent ($message) { 202 | return self::write (self::get_struct ($message, self::URGENT)); 203 | } 204 | 205 | /** 206 | * Shortcut method for Analog::log($info, Analog::ALERT) 207 | * Usage: 208 | * 209 | * Analog::alert ('Debug info'); 210 | */ 211 | public static function alert ($message) { 212 | return self::write (self::get_struct ($message, self::ALERT)); 213 | } 214 | 215 | /** 216 | * Shortcut method for Analog::log($info, Analog::ERROR) 217 | * Usage: 218 | * 219 | * Analog::error ('Debug info'); 220 | */ 221 | public static function error ($message) { 222 | return self::write (self::get_struct ($message, self::ERROR)); 223 | } 224 | 225 | /** 226 | * Shortcut method for Analog::log($info, Analog::WARNING) 227 | * Usage: 228 | * 229 | * Analog::warning ('Debug info'); 230 | */ 231 | public static function warning ($message) { 232 | return self::write (self::get_struct ($message, self::WARNING)); 233 | } 234 | 235 | /** 236 | * Shortcut method for Analog::log($info, Analog::NOTICE) 237 | * Usage: 238 | * 239 | * Analog::notice ('Debug info'); 240 | */ 241 | public static function notice ($message) { 242 | return self::write (self::get_struct ($message, self::NOTICE)); 243 | } 244 | 245 | /** 246 | * Shortcut method for Analog::log($info, Analog::INFO) 247 | * Usage: 248 | * 249 | * Analog::info ('Debug info'); 250 | */ 251 | public static function info ($message) { 252 | return self::write (self::get_struct ($message, self::INFO)); 253 | } 254 | 255 | /** 256 | * Shortcut method for Analog::log($info, Analog::DEBUG) 257 | * Usage: 258 | * 259 | * Analog::debug ('Debug info'); 260 | */ 261 | public static function debug ($message) { 262 | return self::write (self::get_struct ($message, self::DEBUG)); 263 | } 264 | 265 | } -------------------------------------------------------------------------------- /lib/ChromePhp.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class ChromePhp 25 | { 26 | /** 27 | * @var string 28 | */ 29 | const VERSION = '4.0.0'; 30 | 31 | /** 32 | * @var string 33 | */ 34 | const HEADER_NAME = 'X-ChromeLogger-Data'; 35 | 36 | /** 37 | * @var string 38 | */ 39 | const BACKTRACE_LEVEL = 'backtrace_level'; 40 | 41 | /** 42 | * @var string 43 | */ 44 | const LOG = 'log'; 45 | 46 | /** 47 | * @var string 48 | */ 49 | const WARN = 'warn'; 50 | 51 | /** 52 | * @var string 53 | */ 54 | const ERROR = 'error'; 55 | 56 | /** 57 | * @var string 58 | */ 59 | const GROUP = 'group'; 60 | 61 | /** 62 | * @var string 63 | */ 64 | const INFO = 'info'; 65 | 66 | /** 67 | * @var string 68 | */ 69 | const GROUP_END = 'groupEnd'; 70 | 71 | /** 72 | * @var string 73 | */ 74 | const GROUP_COLLAPSED = 'groupCollapsed'; 75 | 76 | /** 77 | * @var string 78 | */ 79 | protected $_php_version; 80 | 81 | /** 82 | * @var int 83 | */ 84 | protected $_timestamp; 85 | 86 | /** 87 | * @var array 88 | */ 89 | protected $_json = array( 90 | 'version' => self::VERSION, 91 | 'columns' => array('log', 'backtrace', 'type'), 92 | 'rows' => array() 93 | ); 94 | 95 | /** 96 | * @var array 97 | */ 98 | protected $_backtraces = array(); 99 | 100 | /** 101 | * @var bool 102 | */ 103 | protected $_error_triggered = false; 104 | 105 | /** 106 | * @var array 107 | */ 108 | protected $_settings = array( 109 | self::BACKTRACE_LEVEL => 1 110 | ); 111 | 112 | /** 113 | * @var ChromePhp 114 | */ 115 | protected static $_instance; 116 | 117 | /** 118 | * Prevent recursion when working with objects referring to each other 119 | * 120 | * @var array 121 | */ 122 | protected $_processed = array(); 123 | 124 | /** 125 | * constructor 126 | */ 127 | private function __construct() 128 | { 129 | $this->_php_version = phpversion(); 130 | $this->_timestamp = $this->_php_version >= 5.1 ? $_SERVER['REQUEST_TIME'] : time(); 131 | $this->_json['request_uri'] = $_SERVER['REQUEST_URI']; 132 | } 133 | 134 | /** 135 | * gets instance of this class 136 | * 137 | * @return ChromePhp 138 | */ 139 | public static function getInstance() 140 | { 141 | if (self::$_instance === null) { 142 | self::$_instance = new self(); 143 | } 144 | return self::$_instance; 145 | } 146 | 147 | /** 148 | * logs a variable to the console 149 | * 150 | * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] 151 | * @return void 152 | */ 153 | public static function log() 154 | { 155 | return self::_log('', func_get_args()); 156 | } 157 | 158 | /** 159 | * logs a warning to the console 160 | * 161 | * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] 162 | * @return void 163 | */ 164 | public static function warn() 165 | { 166 | return self::_log(self::WARN, func_get_args()); 167 | } 168 | 169 | /** 170 | * logs an error to the console 171 | * 172 | * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] 173 | * @return void 174 | */ 175 | public static function error() 176 | { 177 | return self::_log(self::ERROR, func_get_args()); 178 | } 179 | 180 | /** 181 | * sends a group log 182 | * 183 | * @param string value 184 | */ 185 | public static function group() 186 | { 187 | return self::_log(self::GROUP, func_get_args()); 188 | } 189 | 190 | /** 191 | * sends an info log 192 | * 193 | * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] 194 | * @return void 195 | */ 196 | public static function info() 197 | { 198 | return self::_log(self::INFO, func_get_args()); 199 | } 200 | 201 | /** 202 | * sends a collapsed group log 203 | * 204 | * @param string value 205 | */ 206 | public static function groupCollapsed() 207 | { 208 | return self::_log(self::GROUP_COLLAPSED, func_get_args()); 209 | } 210 | 211 | /** 212 | * ends a group log 213 | * 214 | * @param string value 215 | */ 216 | public static function groupEnd() 217 | { 218 | return self::_log(self::GROUP_END, func_get_args()); 219 | } 220 | 221 | /** 222 | * internal logging call 223 | * 224 | * @param string $type 225 | * @return void 226 | */ 227 | protected static function _log($type, array $args) 228 | { 229 | // nothing passed in, don't do anything 230 | if (count($args) == 0 && $type != self::GROUP_END) { 231 | return; 232 | } 233 | 234 | $logger = self::getInstance(); 235 | 236 | $logger->_processed = array(); 237 | 238 | $logs = array(); 239 | foreach ($args as $arg) { 240 | $logs[] = $logger->_convert($arg); 241 | } 242 | 243 | $backtrace = debug_backtrace(false); 244 | $level = $logger->getSetting(self::BACKTRACE_LEVEL); 245 | 246 | $backtrace_message = 'unknown'; 247 | if (isset($backtrace[$level]['file']) && isset($backtrace[$level]['line'])) { 248 | $backtrace_message = $backtrace[$level]['file'] . ' : ' . $backtrace[$level]['line']; 249 | } 250 | 251 | $logger->_addRow($logs, $backtrace_message, $type); 252 | } 253 | 254 | /** 255 | * converts an object to a better format for logging 256 | * 257 | * @param Object 258 | * @return array 259 | */ 260 | protected function _convert($object) 261 | { 262 | // if this isn't an object then just return it 263 | if (!is_object($object)) { 264 | return $object; 265 | } 266 | 267 | //Mark this object as processed so we don't convert it twice and it 268 | //Also avoid recursion when objects refer to each other 269 | $this->_processed[] = $object; 270 | 271 | $object_as_array = array(); 272 | 273 | // first add the class name 274 | $object_as_array['___class_name'] = get_class($object); 275 | 276 | // loop through object vars 277 | $object_vars = get_object_vars($object); 278 | foreach ($object_vars as $key => $value) { 279 | 280 | // same instance as parent object 281 | if ($value === $object || in_array($value, $this->_processed, true)) { 282 | $value = 'recursion - parent object [' . get_class($value) . ']'; 283 | } 284 | $object_as_array[$key] = $this->_convert($value); 285 | } 286 | 287 | $reflection = new ReflectionClass($object); 288 | 289 | // loop through the properties and add those 290 | foreach ($reflection->getProperties() as $property) { 291 | 292 | // if one of these properties was already added above then ignore it 293 | if (array_key_exists($property->getName(), $object_vars)) { 294 | continue; 295 | } 296 | $type = $this->_getPropertyKey($property); 297 | 298 | if ($this->_php_version >= 5.3) { 299 | $property->setAccessible(true); 300 | } 301 | 302 | try { 303 | $value = $property->getValue($object); 304 | } catch (ReflectionException $e) { 305 | $value = 'only PHP 5.3 can access private/protected properties'; 306 | } 307 | 308 | // same instance as parent object 309 | if ($value === $object || in_array($value, $this->_processed, true)) { 310 | $value = 'recursion - parent object [' . get_class($value) . ']'; 311 | } 312 | 313 | $object_as_array[$type] = $this->_convert($value); 314 | } 315 | return $object_as_array; 316 | } 317 | 318 | /** 319 | * takes a reflection property and returns a nicely formatted key of the property name 320 | * 321 | * @param ReflectionProperty 322 | * @return string 323 | */ 324 | protected function _getPropertyKey(ReflectionProperty $property) 325 | { 326 | $static = $property->isStatic() ? ' static' : ''; 327 | if ($property->isPublic()) { 328 | return 'public' . $static . ' ' . $property->getName(); 329 | } 330 | 331 | if ($property->isProtected()) { 332 | return 'protected' . $static . ' ' . $property->getName(); 333 | } 334 | 335 | if ($property->isPrivate()) { 336 | return 'private' . $static . ' ' . $property->getName(); 337 | } 338 | } 339 | 340 | /** 341 | * adds a value to the data array 342 | * 343 | * @var mixed 344 | * @return void 345 | */ 346 | protected function _addRow(array $logs, $backtrace, $type) 347 | { 348 | // if this is logged on the same line for example in a loop, set it to null to save space 349 | if (in_array($backtrace, $this->_backtraces)) { 350 | $backtrace = null; 351 | } 352 | 353 | // for group, groupEnd, and groupCollapsed 354 | // take out the backtrace since it is not useful 355 | if ($type == self::GROUP || $type == self::GROUP_END || $type == self::GROUP_COLLAPSED) { 356 | $backtrace = null; 357 | } 358 | 359 | if ($backtrace !== null) { 360 | $this->_backtraces[] = $backtrace; 361 | } 362 | 363 | $row = array($logs, $backtrace, $type); 364 | 365 | $this->_json['rows'][] = $row; 366 | $this->_writeHeader($this->_json); 367 | } 368 | 369 | protected function _writeHeader($data) 370 | { 371 | header(self::HEADER_NAME . ': ' . $this->_encode($data)); 372 | } 373 | 374 | /** 375 | * encodes the data to be sent along with the request 376 | * 377 | * @param array $data 378 | * @return string 379 | */ 380 | protected function _encode($data) 381 | { 382 | return base64_encode(utf8_encode(json_encode($data))); 383 | } 384 | 385 | /** 386 | * adds a setting 387 | * 388 | * @param string key 389 | * @param mixed value 390 | * @return void 391 | */ 392 | public function addSetting($key, $value) 393 | { 394 | $this->_settings[$key] = $value; 395 | } 396 | 397 | /** 398 | * add ability to set multiple settings in one call 399 | * 400 | * @param array $settings 401 | * @return void 402 | */ 403 | public function addSettings(array $settings) 404 | { 405 | foreach ($settings as $key => $value) { 406 | $this->addSetting($key, $value); 407 | } 408 | } 409 | 410 | /** 411 | * gets a setting 412 | * 413 | * @param string key 414 | * @return mixed 415 | */ 416 | public function getSetting($key) 417 | { 418 | if (!isset($this->_settings[$key])) { 419 | return null; 420 | } 421 | return $this->_settings[$key]; 422 | } 423 | } --------------------------------------------------------------------------------