Last active
August 7, 2024 05:22
-
-
Save kafene/9295ef4a18e1cb179ff3 to your computer and use it in GitHub Desktop.
Set up some Monolog handlers, a Pimple Container, and Whoops
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Various configurations of Monolog and Whoops | |
* using a Pimple Container as service locator | |
* | |
* Monolog: https://github.com/Seldaek/monolog | |
* Pimple: https://github.com/fabpot/Pimple | |
* Whoops: https://github.com/filp/whoops | |
* Swift Mailer: https://github.com/swiftmailer/swiftmailer | |
* | |
* Setup: | |
* | |
* composer require pimple/pimple @stable --update-no-dev | |
* composer require filp/whoops @stable --update-no-dev | |
* composer require monolog/monolog @stable --update-no-dev | |
* | |
* If you want to use Swift Mailer to email yourself logs: | |
* | |
* composer require swiftmailer/swiftmailer @stable --update-no-dev | |
* | |
* Replace "@stable" with "@dev" for dev versions. | |
* | |
* I've disabled the logging handlers by prepending `false &&` | |
* because it's probably not prudent to run them all together, | |
* that will need to be removed before testing. | |
*/ | |
namespace WhateverNamespaceYouWant; | |
require_once 'vendor/autoload.php'; | |
use DateTime; | |
use Swift_Mailer; | |
use Swift_Message; | |
use Swift_SmtpTransport; | |
use Pimple\Container as Pimple; | |
use Monolog\Logger; | |
use Monolog\Registry as LoggerRegistry; | |
use Monolog\ErrorHandler as LoggerErrorHandler; | |
# Pick your favorites: | |
use Monolog\Handler\StreamHandler; | |
use Monolog\Handler\BufferHandler; | |
use Monolog\Handler\GroupHandler; | |
use Monolog\Handler\TestHandler; | |
use Monolog\Handler\ErrorLogHandler; | |
use Monolog\Handler\SyslogHandler; | |
use Monolog\Handler\RotatingFileHandler; | |
use Monolog\Handler\SwiftMailerHandler; | |
use Monolog\Handler\NativeMailerHandler; | |
use Monolog\Handler\FirePHPHandler; | |
use Monolog\Handler\ChromePHPHandler; | |
use Monolog\Handler\BrowserConsoleHandler; | |
use Monolog\Formatter\LineFormatter; | |
use Monolog\Formatter\JsonFormatter; | |
use Monolog\Formatter\ScalarFormatter; | |
use Monolog\Formatter\HtmlFormatter; | |
use Monolog\Formatter\NormalizerFormatter; | |
use Monolog\Processor\PsrLogMessageProcessor; | |
use Monolog\Processor\UidProcessor; | |
use Monolog\Processor\WebProcessor; | |
use Monolog\Processor\MemoryPeakUsageProcessor; | |
use Monolog\Processor\MemoryUsageProcessor; | |
use Monolog\Processor\ProcessIdProcessor; | |
use Monolog\Processor\IntrospectionProcessor; | |
# ###################################################################### | |
# ###################################################################### | |
# ###################################################################### | |
# Set up the pimple container | |
$c = new Pimple(); | |
# Initialize the shared logger service | |
$c['logger'] = function ($c) { | |
$logger = new Logger('main'); # Main channel, or whatever name you like. | |
# PSR 3 log message formatting for all handlers | |
$logger->pushProcessor(new PsrLogMessageProcessor()); | |
return $logger; | |
}; | |
# ###################################################################### | |
# ###################################################################### | |
# ###################################################################### | |
# Continue extending the logger service, filling it up with | |
# whatever handlers, processors and formatters you like. | |
# Log to stderr, use some nice colors | |
$c->extend('logger', function ($logger, $c) { | |
$color = [ | |
'gray' => "\033[37m", | |
'green' => "\033[32m", | |
'yellow' => "\033[93m", | |
'blue' => "\033[94m", | |
'purple' => "\033[95m", | |
'white' => "\033[97m", | |
'bold' => "\033[1m", | |
'reset' => "\033[0m", | |
]; | |
$width = getenv('COLUMNS') ?: 60; # Console width from env, or 60 chars. | |
$separator = str_repeat('━', $width); # A nice separator line | |
$format = "{$color["bold"]}"; | |
$format .= "{$color["green"]}[%datetime%]"; | |
$format .= "{$color["white"]}[%channel%."; | |
$format .= "{$color["yellow"]}%level_name%"; | |
$format .= "{$color["white"]}]"; | |
$format .= "{$color["blue"]}[UID:%extra.uid%]"; | |
$format .= "{$color["purple"]}[PID:%extra.process_id%]"; | |
$format .= "{$color["reset"]}:".PHP_EOL; | |
$format .= "%message%".PHP_EOL; | |
$format .= "{$color["gray"]}{$separator}{$color["reset"]}".PHP_EOL; | |
$handler = new StreamHandler('php://stderr'); | |
$handler->pushProcessor(new UidProcessor(24)); | |
$handler->pushProcessor(new ProcessIdProcessor()); | |
$dateFormat = 'H:i:s'; # Just the time for command line logs | |
$allowInlineLineBreaks = true; | |
$formatter = new LineFormatter($format, $dateFormat, $allowInlineLineBreaks); | |
$handler->setFormatter($formatter); | |
$logger->pushHandler($handler); | |
return $logger; | |
}); | |
# syslogd handler using PHP's built-in syslog support | |
false && $c->extend('logger', function ($logger, $c) { | |
$ident = $logger->getName(); | |
$facility = LOG_USER; | |
$option = LOG_PID | LOG_CONS | LOG_ODELAY; | |
$handler = new SyslogHandler($ident, $facility, Logger::DEBUG, true, $option); | |
$handler->pushProcessor(new UidProcessor(24)); | |
$handler->pushProcessor(new MemoryUsageProcessor()); | |
$handler->pushProcessor(new MemoryPeakUsageProcessor()); | |
$handler->pushProcessor(new ProcessIdProcessor()); | |
$handler->pushProcessor(new WebProcessor()); | |
$handler->pushProcessor(new IntrospectionProcessor()); | |
$logger->pushHandler($handler); | |
return $logger; | |
}); | |
# Rotating log file inside a BufferHandler (write at most once per request) | |
false && $c->extend('logger', function ($logger, $c) { | |
$filename = sprintf("/var/log/php/%s", $logger->getName()); | |
$handler = new RotatingFileHandler($filename, 24, Logger::NOTICE, true, 0644, true); | |
$handler->setFilenameFormat('{filename}-{date}.log', 'Y-m-d'); | |
$format = "[%datetime%][%channel%][%level_name%][%extra.uid%]: %message%\n"; | |
$handler->setFormatter(new LineFormatter($format, 'U')); | |
$handler->pushProcessor(new UidProcessor(24)); | |
$logger->pushHandler(new BufferHandler($handler)); | |
return $logger; | |
}); | |
# Swift Mailer email logger inside a BufferHandler (send at most one email per request). | |
# Configured to use Gmail SMTP and HTML Formatter to send nice HTML emails. | |
false && $c->extend('logger', function ($logger, $c) { | |
$subject = sprintf('PHP Log (%s)', $logger->getName()); | |
$transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 587, 'tls'); | |
$transport->setUsername('[email protected]'); | |
$transport->setPassword('hunter2'); | |
$message = Swift_Message::newInstance(); | |
$message->setSubject($subject); | |
$message->setFrom('[email protected]') # Who sends you *your* logs? | |
$message->setTo(['[email protected]', '[email protected]']); | |
$message->setContentType('text/html'); | |
$message->setCharset('utf-8'); | |
$message->setMaxLineLength(1000); # long wrapping for HTML messages | |
$mailer = Swift_Mailer::newInstance($transport); | |
$handler = new SwiftMailerHandler($mailer, $message); | |
$handler->setFormatter(new HtmlFormatter(DateTime::RFC2822)); | |
$handler->pushProcessor(new UidProcessor(24)); | |
$handler->pushProcessor(new WebProcessor()); | |
$logger->pushHandler(new BufferHandler($handler)); | |
return $logger; | |
}); | |
# Emails logs using mail() | |
# Wrapped inside a BufferHandler (send at most one email per request) | |
false && $c->extend('logger', function ($logger, $c) { | |
$subject = sprintf('PHP Log (%s)', $logger->getName()); | |
$to = ['[email protected]', '[email protected]']; | |
$from = 'logs@this-website\'s-domain-name.tld'; | |
$level = $c['logger.handler.native_mailer.level']; | |
$bubble = $c['logger.handler.native_mailer.bubble']; | |
$maxline = 1000; # Maximum line length: long wrapping for HTML messages | |
$handler = new NativeMailerHandler($to, $subject, $from, Logger::ERROR, true, $maxline); | |
$handler->setContentType('text/html'); | |
$handler->setEncoding('utf-8'); | |
$handler->setFormatter(new HtmlFormatter(DateTime::RFC2822)); | |
$handler->pushProcessor(new UidProcessor(24)); | |
$handler->pushProcessor(new WebProcessor()); | |
$logger->pushHandler(new BufferHandler($handler)); | |
return $logger; | |
}); | |
# Direct output to browser as HTML | |
# Whoops covers this, though. | |
false && $c->extend('logger', function ($logger, $c) { | |
$handler = new StreamHandler('php://output', $level, $bubble); | |
$handler->setFormatter(new HtmlFormatter(DateTime::RFC2822)); | |
$handler->pushProcessor(new UidProcessor(24)); | |
$handler->pushProcessor(new MemoryUsageProcessor()); | |
$handler->pushProcessor(new MemoryPeakUsageProcessor()); | |
$handler->pushProcessor(new ProcessIdProcessor()); | |
$handler->pushProcessor(new WebProcessor()); | |
$handler->pushProcessor(new IntrospectionProcessor()); | |
$logger->pushHandler($handler); | |
return $logger; | |
}); | |
# Test handler captures all records and gives you access to them for verification. | |
# For example, after logging some stuff: | |
# var_dump($c['logger.handler.test']->getRecords()); | |
# NormalizerFormatter is the most minimal formatter, leaving records mostly | |
# intact as arrays rather than formatted into strings. | |
false && $c->extend('logger', function ($logger, $c) { | |
$handler = new TestHandler(); | |
$handler->setFormatter(new NormalizerFormatter()); # Minimal formatting | |
$handler->pushProcessor(new UidProcessor(24)); | |
$handler->pushProcessor(new MemoryUsageProcessor()); | |
$handler->pushProcessor(new MemoryPeakUsageProcessor()); | |
$handler->pushProcessor(new ProcessIdProcessor()); | |
$handler->pushProcessor(new WebProcessor()); | |
$handler->pushProcessor(new IntrospectionProcessor()); | |
$c['logger.handler.test'] = $handler; # Allow for easy access later on | |
$logger->pushHandler($handler); | |
return $logger; | |
}); | |
# Register | |
$c->extend('logger', function ($logger, $c) { | |
LoggerRegistry::addLogger($logger); | |
LoggerErrorHandler::register($logger); | |
return $logger; | |
}); | |
$logger = $c['logger']; | |
$logger->debug('Monolog is configured.', [$logger]); | |
# ###################################################################### | |
# ###################################################################### | |
# ###################################################################### | |
# Setting up Whoops | |
$c['whoops'] = function ($c) { | |
# stop PHP from polluting exception messages | |
# with html that Whoops escapes and prints. | |
ini_set('html_errors', false); | |
return new Whoops(); | |
}; | |
# Pretty page handler | |
$c->extend('whoops', function ($whoops, $c) { | |
$handler = new PrettyPageHandler(); | |
$handler->addEditor('edit', 'edit://open/?url=file://%file&line=%line'); | |
$handler->setEditor('xdebug'); | |
$whoops->pushHandler($handler); | |
return $whoops; | |
}); | |
# Plain text handler for our logger | |
$c->extend('whoops', function ($whoops, $c) { | |
$handler = new PlainTextHandler(); | |
$handler->onlyForCommandLine(false); | |
$handler->outputOnlyIfCommandLine(false); | |
$handler->loggerOnly(true); | |
$handler->setLogger($c['logger']); | |
$whoops->pushHandler($handler); | |
return $whoops; | |
}); | |
# Responds to AJAX requests with JSON formatted exceptions | |
$c->extend('whoops', function ($whoops, $c) { | |
$handler = new JsonResponseHandler(); | |
$handler->onlyForAjaxRequests(true); | |
$handler->addTraceToOutput(true); | |
$whoops->pushHandler($handler); | |
return $whoops; | |
}); | |
$whoops = $c['whoops']; | |
$whoops->register(); | |
$logger->debug('Whoops is configured.'); | |
# ###################################################################### | |
# ###################################################################### | |
# ###################################################################### | |
# Demo | |
class FruitLover { | |
protected $loves; | |
public function __construct($fruit = 'apples') { | |
$this->love($fruit); | |
} | |
function love($fruit) { | |
if ($fruit !== 'apples') { | |
static::$fruit('I only like apples.', 1155, 9022); | |
} | |
echo 'I love apples too!'; | |
$this->loves = 'apples'; | |
} | |
public static function __callStatic($method, array $args) { | |
$thisKillsTheUser = 'null === "<script>alert("Boom!");</script>"'; | |
assert($thisKillsTheUser, 'I hate static calls, take this!'); | |
} | |
} | |
$lover = new FruitLover(); | |
$lover->love('bananas'); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment