Skip to content

Instantly share code, notes, and snippets.

@aljana
Last active April 25, 2019 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aljana/d0454ff39c19bdc71693 to your computer and use it in GitHub Desktop.
Save aljana/d0454ff39c19bdc71693 to your computer and use it in GitHub Desktop.
Symfony2 graylog integration
graylog:
ip: 54.76.221.242
port: 12201
enabled: false
<?php
/**
* File containing RequestListener class
*
* @author Aljana Polanc <aljana.polanc@dlabs.si>
* @copyright 2015 DLabs (http://www.dlabs.si)
*/
namespace RPS\ServiceBundle\EventListener;
use Doctrine\DBAL\Logging\DebugStack;
use Doctrine\ORM\EntityManager;
use Gelf\Message;
use Gelf\Publisher;
use Gelf\Transport\HttpTransport;
use Gelf\Transport\UdpTransport;
use RPS\ServiceBundle\Entity\User;
use Symfony\Bridge\Monolog\Logger;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Class RequestListener
*
* @package RPS\ServiceBundle\EventListener
*/
class RequestListener {
/** Maximum bytes per field */
const GRAYLOG_MAX_BYTES = 32766;
/** @var Request */
protected $request;
/** @var array */
protected $params;
/** @var TokenStorageInterface */
protected $tokenStorage;
/** @var Logger */
protected $logger;
/** @var float */
protected $startTime;
/** @var array */
protected $exception;
/** @var array */
protected $session;
/** @var EntityManager */
protected $entityManager;
/**
* Construct.
*
* @param TokenStorageInterface $tokenStorage
* @param Logger $logger
* @param EntityManager $entityManager
* @param array
*/
public function __construct(TokenStorageInterface $tokenStorage, Logger $logger, EntityManager $entityManager, $params) {
$this->tokenStorage = $tokenStorage;
$this->logger = $logger;
$this->params = $params;
$this->entityManager = $entityManager;
}
/**
* Handle on kernel request event.
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event) {
if (!$this->params["enabled"]) {
return false;
}
$this->request = $event->getRequest();
// TODO: move this to onControllerFinish action or something, otherwise session can be out-dated.
$this->session = $this->request->getSession()->all();
$this->startTime = microtime(true);
// Setup queries logging
$this->entityManager
->getConnection()
->getConfiguration()
->setSQLLogger(new \Doctrine\DBAL\Logging\DebugStack())
;
}
/**
* Handle on kernel terminate event.
*
* @param PostResponseEvent $event
*/
public function onKernelTerminate(PostResponseEvent $event) {
if (!$this->params["enabled"]) {
return false;
}
$token = $this->tokenStorage->getToken();
$user = $token ? $token->getUser() : null;
$userInfo = [];
if ($user instanceof User) {
$userInfo = [
"id" => $user->getId(),
"email" => $user->getEmail()
];
}
$request = $event->getRequest();
// Filter out password
$post = [];
foreach ($request->request->all() as $key => $val) {
if (stristr($key, "password") === false) {
$post[$key] = $val;
}
}
$post = json_encode($post, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$rawPost = $request->getContent();
$compressedPost = "";
$compressedRawPost = "";
if (strlen($post) > static::GRAYLOG_MAX_BYTES) {
$compressedPost = base64_encode(gzcompress($post));
$post = substr($post, 0, static::GRAYLOG_MAX_BYTES);
}
if (strlen($rawPost) > static::GRAYLOG_MAX_BYTES) {
$compressedRawPost = base64_encode(gzcompress($rawPost));
$rawPost = substr($rawPost, 0, static::GRAYLOG_MAX_BYTES);
}
// TODO: check if compressed raw still exceeds max bytes.
$responseTime = number_format(microtime(true) - $this->startTime, 4);
$component = (strpos($request->getRequestUri(), "/api") === 0)
? "api"
: "web"
;
/** @var DebugStack $sqlLogger */
$sqlLogger = $this->entityManager
->getConnection()
->getConfiguration()
->getSQLLogger()
;
$logInfo = [
"url" => $request->getUri(),
"route" => $request->get("_route"),
"method" => $request->getMethod(),
"section" => "http/" . $component,
"post" => $post,
"post_compressed" => $compressedPost,
"raw_post" => !$post ? $rawPost : null,
"raw_post_compressed" => !$post ? $compressedRawPost : null,
"get" => $request->getQueryString(),
"cookies" => $request->cookies->all(),
"headers" => $request->headers->all(),
"session" => $this->session,
"ip" => $request->getClientIps(),
"userId" => $userInfo ? $userInfo["id"] : null,
"userEmail" => $userInfo ? $userInfo["email"] : null,
"response_time" => $responseTime,
"response_time_ms" => $responseTime * 1000,
"exception" => $this->exception,
"controller" => $request->attributes->get('_controller'),
"status" => $event->getResponse()->getStatusCode(),
"queries" => count($sqlLogger->queries),
"mem_usage_mb" => memory_get_peak_usage(true) / 1024 / 1024
];
// Log response for API
if ($component === "api") {
$response = $event->getResponse()->getContent();
$compressedResponse = "";
if (strlen($response) > static::GRAYLOG_MAX_BYTES) {
$compressedResponse = base64_encode(gzcompress($response));
$response = substr($response, 0, static::GRAYLOG_MAX_BYTES);
}
// TODO: check if compressed response still over limit
$logInfo["response"] = $response;
$logInfo["response_compressed"] = $compressedResponse;
}
$summary = sprintf("[%d] %s %s (%.2fs)",
$event->getResponse()->getStatusCode(),
$request->getMethod(),
$request->getUri(),
$logInfo["response_time"]
);
$this->logEvent($summary, $logInfo);
}
/**
* Handle the kernel exception event
*
* @param GetResponseForExceptionEvent $event event
*/
public function onKernelException(GetResponseForExceptionEvent $event) {
$this->exception = $event->getException()->getMessage();
}
/**
* Handle the console exception the event
*
* @param ConsoleExceptionEvent $event event
*/
public function onConsoleException(ConsoleExceptionEvent $event) {
$this->exception = $event->getException()->getMessage();
}
/**
* Handle the console command event.
*
* @param ConsoleCommandEvent $event event
*/
public function onConsoleCommand(ConsoleCommandEvent $event) {
$this->startTime = microtime(true);
// Setup queries logging
$this->entityManager
->getConnection()
->getConfiguration()
->setSQLLogger(new \Doctrine\DBAL\Logging\DebugStack())
;
}
/**
* Handle the console terminate event.
*
* @param ConsoleTerminateEvent $event event
*/
public function onConsoleTerminate(ConsoleTerminateEvent $event) {
$responseTime = number_format(microtime(true) - $this->startTime, 4);
$summary = sprintf("[%d] %s (%.2fs)", $event->getExitCode(), $event->getCommand()->getName(), $responseTime);
/** @var DebugStack $sqlLogger */
$sqlLogger = $this->entityManager
->getConnection()
->getConfiguration()
->getSQLLogger()
;
$logInfo = [
"command" => $event->getCommand()->getName(),
"exit_code" => $event->getExitCode(),
"exception" => $this->exception,
"response_time" => $responseTime,
"response_time_ms" => $responseTime * 1000,
"section" => "console",
"queries" => count($sqlLogger->queries),
"mem_usage_mb" => memory_get_peak_usage(true) / 1024 / 1024
];
$this->logEvent($summary, $logInfo);
}
/**
* Write to GrayLog.
*
* @param $summary
* @param $details
*/
protected function logEvent($summary, $details) {
$jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
$transport = new UdpTransport($this->params["ip"], $this->params["port"], UdpTransport::CHUNK_SIZE_LAN);
$publisher = new Publisher();
$publisher->addTransport($transport);
$logger = new \Gelf\Logger($publisher, "rps");
foreach ($details as $key => $values) {
$details[$key] = is_array($values) ? json_encode($values, $jsonOptions) : $values;
}
if ($this->exception) {
$logger->error($summary, $details);
} else {
$logger->notice($summary, $details);
}
}
}
<service id="rps.request.listener" class="RPS\ServiceBundle\EventListener\RequestListener">
<argument type="service" id="security.token_storage"></argument>
<argument type="service" id="logger" on-invalid="null" />
<argument type="service" id="doctrine.orm.default_entity_manager" on-invalid="null" />
<argument key="graylog">%graylog%</argument>
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" priority="0"/>
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="0"/>
<tag name="kernel.event_listener" event="kernel.terminate" method="onKernelTerminate" priority="0"/>
<tag name="kernel.event_listener" event="console.exception" method="onConsoleException" priority="0"/>
<tag name="kernel.event_listener" event="console.command" method="onConsoleCommand" priority="0"/>
<tag name="kernel.event_listener" event="console.terminate" method="onConsoleTerminate" priority="0"/>
</service>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment