Monolog human readable exception email with stack trace

For the times when you just want those exception to be delivered to your inbox with as much information as possible.

Converting over some smaller apps to use monolog I found my favourite snippet for emailing the critical exceptions which bubble to the top of the app wasn't that easy to replicate.

Update January 2020: A few years back I swapped to using a dedicated online error tracker sentry.io (no affiliation) personally and professionally and now would now not advocate trying to roll your own error tracking if you can help it.
			
				}catch (Exception $e) {
					mail( 
						'[email protected]',
						__FILE__, 
						print_r( $e, true ).print_r( $_SERVER, true ) 
					);
				}
			
		
			
				### Output ###
				Exception Object
				(
					[message:protected] =>
					[string:Exception:private] =>
					[code:protected] => 0
					[file:protected] => /foo/bar/index.php
					[line:protected] => 19
					[trace:Exception:private] => Array
						(
							[0] => Array
								(
									[file] => /foo/bar/RubbishCode.php
									[line] => 23
									[function] => __construct
									[class] => RubbishCode
									[type] => ->
									[args] => Array
										(
										)
								)
						)
					[previous:Exception:private] =>
				)
				Array
				(
					[SERVER_PROTOCOL] => HTTP/1.1
					[REQUEST_METHOD] => GET
					...
				)
			
		

monolog emailing out of the box

			
				use Monolog\Logger;
				use Monolog\Handler\NativeMailerHandler;
				$log = new Logger('foo');
				$handler = new NativeMailerHandler(
					'[email protected]',
					'Subject',
					'From'
				);
				
				$log->pushHandler($handler);
			
		
			
				### Output ###
				[YYYY-MM-DD HH:MM:SS] foo.ERROR: Exception {"exception":"[object]
				(Exception(code: 0):  at
				/foo/bar/RubbishCode.php:29)"} []
			
		

monolog LineFormatter->includeStacktraces()

LineFormatter does have a property which can be set which will cause exceptions to output the $exception->getTrace() however the output is still going to be missing the args array etc.

			
				$lineFormatter = new LineFormatter;
				$lineFormatter-> includeStacktraces();
				$handler->setFormatter($lineFormatter);
				
				$log->pushHandler($handler);
        	
		
			
				### Output ###
				[YYYY-MM-DD HH:MM:SS] foo.ERROR: Exception {"exception":"[object]
				(Exception(code: 0):  at
				/foo/bar/index.php:19)\n[stacktrace]\n#0
				/foo/bar/RubbishCode.php(23):
				RubbishCode->__construct()\n#1 {main}"} []
			
		

Extending LineFormatter to use print_r()

Personally I find print_r($foo) the easiest to read, with json_encode($foo, JSON_PRETTY_PRINT) a close second.

			
				class PrintRLineFormatter extends Monolog\Formatter\LineFormatter
				{
					/**
					 * {@inheritdoc}
					 */
					public function format(array $record)
					{
						return print_r( $record, true );
					}
				}
			
		
			
				### Output ###
				Array
				(
					[message] => Exception
					[context] => Array
						(
							[exception] => Exception Object
								(
									[message:protected] =>
									[string:Exception:private] =>
									[code:protected] => 0
									[file:protected] =>/foo/bar/index.php
									[line:protected] => 19
									[trace:Exception:private] => Array
										(
											[0] => Array
												(
													[file] =>/foo/bar/RubbishCode.php
													[line] => 23
													[function] => __construct
													[class] => RubbishCode
													[type] => ->
													[args] => Array
														(
														)
												)
										)
									[previous:Exception:private] =>
								)
						)
					[level] => 400
					[level_name] => ERROR
					[channel] => foo
					[datetime] => DateTime Object
						(
							[date] => YYYY-MM-DD HH:MM:SS
							[timezone_type] => 3
							[timezone] => Europe/London
						)
				...
			
		

Adding in global variables with a Processor

Most of the time you can't get away from the fact that the global PHP variables are going to give a good place to start debugging the state of the script at the time of failure:

	
		$log->pushProcessor(function ($record) {
			if( !empty( $_SERVER ) ){    
				$record['extra']['_SERVER'] = $_SERVER;
			}
			if( !empty( $_SESSION ) ){
				$record['extra']['_SESSION'] = $_SESSION;
			}
			if( !empty( $_POST ) ){
				$record['extra']['_POST'] = $_POST;
			}
			return $record;
		});
	

All together

			
				use Monolog\Logger;
				use Monolog\Formatter\LineFormatter;
				use Monolog\Handler\NativeMailerHandler;
				
				class PrintRLineFormatter extends LineFormatter 
				{
					/**
					 * {@inheritdoc}
					 */
					public function format(array $record)
					{
						return print_r( $record, true );
					}
				}
				
				$log = new Logger('foo');
				$handler = new NativeMailerHandler(
					'[email protected]',
					'Subject',
					'From'
				);
				
				$handler->setFormatter(new PrintRLineFormatter);
				
				$log->pushHandler($handler);
				
				$log->pushProcessor(function ($record) {
					if( !empty( $_SERVER ) ){    
						$record['extra']['_SERVER'] = $_SERVER;
					}
					if( !empty( $_SESSION ) ){
						$record['extra']['_SESSION'] = $_SESSION;
					}
					if( !empty( $_POST ) ){
						$record['extra']['_POST'] = $_POST;
					}
					return $record;
				});
			
		
			
				### Output ###
				Array
				(
					[message] => Exception
					[context] => Array
						(
							[exception] => Exception Object
								(
									[message:protected] =>
									[string:Exception:private] =>
									[code:protected] => 0
									[file:protected] =>/foo/bar/index.php
									[line:protected] => 19
									[trace:Exception:private] => Array
										(
											[0] => Array
												(
													[file] =>/foo/bar/RubbishCode.php
													[line] => 23
													[function] => __construct
													[class] => RubbishCode
													[type] => ->
													[args] => Array
														(
														)
												)
										)
									[previous:Exception:private] =>
								)
						)
					[level] => 400
					[level_name] => ERROR
					[channel] => foo
					[datetime] => DateTime Object
						(
							[date] => YYYY-MM-DD HH:MM:SS
							[timezone_type] => 3
							[timezone] => Europe/London
						)
					[extra] => Array
						(
							[_SERVER] => Array
								(
									...
									[SERVER_PROTOCOL] => HTTP/1.1
									[REQUEST_METHOD] => GET
									...
								)
						)
				)
			
		
Load Comments...