Skip to content

Instantly share code, notes, and snippets.

@kafene
Last active August 7, 2024 05:22
Show Gist options
  • Save kafene/9295ef4a18e1cb179ff3 to your computer and use it in GitHub Desktop.
Save kafene/9295ef4a18e1cb179ff3 to your computer and use it in GitHub Desktop.
Set up some Monolog handlers, a Pimple Container, and Whoops
<?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