Skip to content

Instantly share code, notes, and snippets.

@thecrypticace
Created August 12, 2020 23:47
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 thecrypticace/e5fd770f83228395c6b8ef9b5ecd22e6 to your computer and use it in GitHub Desktop.
Save thecrypticace/e5fd770f83228395c6b8ef9b5ecd22e6 to your computer and use it in GitHub Desktop.
Log requests middleware
<?php
namespace App\Logs\Sticky;
use Illuminate\Log\Logger;
class AttachContext
{
public function __invoke(Logger $logger)
{
/** @var \Monolog\Logger */
$monolog = $logger->getLogger();
$monolog->pushProcessor(new class {
public function __invoke(array $record)
{
$record["extra"] = array_replace(Context::all(), $record["extra"]);
return $record;
}
});
}
}
<?php
namespace App\Logs\Sticky;
class Context
{
/**
* This is the array in which we store all sticky context data.
*
* @var array
*/
protected static $data = [];
/**
* Add context-loggable data to the sticky context.
*
* @param string $key
* @param mixed $data
*/
public static function add($key, $data)
{
static::$data[$key] = $data;
}
/**
* Retrieve all data in the sticky context.
*
* @return array
*/
public static function all()
{
return static::$data;
}
/**
* Clear all the sticky context data.
*/
public static function flush()
{
static::$data = [];
}
}
<?php
namespace App\Http\Middleware;
use App\Logs\Sticky\Context;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\RedirectResponse;
class LogRequests
{
public function handle($request, Closure $next)
{
$request = $this->identifyRequest($request);
$request = $this->populateRequestTiming($request);
Context::add("request", $this->requestContext($request));
logger(vsprintf("Receiving Request %s /%s", [
$request->method(),
$request->path() === "/" ? "" : $request->path(),
]), [
"query" => $request->query->all(),
]);
$response = $next($request);
$response->headers->set("X-Request-Id", $request->attributes->get("id"));
logger("Sending Response", $this->responseContext($request, $response));
return $response;
}
public function terminate($request, $response)
{
logger("Sent Response");
Context::flush();
}
private function requestContext($request)
{
return [
"id" => $request->attributes->get("id"),
"parent" => $request->attributes->get("parent") ?: null,
];
}
private function identifyRequest($request)
{
// Each request is identified by a given, unique ID
$id = Str::uuid()->toString();
// In addition ajax requests will have a header
// that identifies the id of the request that
// eventually resulted in the ajax request
$parent = $request->ajax() ? $request->header("x-parent-request") : null;
// These are stored as custom attributes on the request so
// any component in the application that has access to
// the request may access them if need be
$request->attributes->add([
"id" => $id,
"parent" => $parent,
]);
return $request;
}
private function populateRequestTiming($request)
{
$networkingDelay = null;
$browserStartTime = null;
/** @var float $scriptStartTime */
/** @psalm-suppress UndefinedConstant **/
$scriptStartTime = LARAVEL_START;
if (! is_null($request->headers->get("x-start-time"))) {
$browserStartTime = (double) $request->headers->get("x-start-time");
}
if (! is_null($browserStartTime)) {
$networkingDelay = $browserStartTime - $scriptStartTime;
}
$request->attributes->add([
"times.script_start" => $scriptStartTime,
"times.browser_start" => $browserStartTime,
]);
return $request;
}
private function responseContext($request, $response)
{
$now = microtime(true);
$scriptStart = $request->attributes->get("times.script_start");
$browserStart = $request->attributes->get("times.browser_start");
$context = [
"status" => $response->getStatusCode(),
"times" => [
"script_start" => $this->formatTime($scriptStart),
"script_end" => $this->formatTime($now),
"script_start_to_script_end" => $this->formatDifference($scriptStart, $now),
"browser_start" => $this->formatTime($browserStart),
"browser_start_to_script_end" => $this->formatDifference($browserStart, $now),
],
];
if ($response instanceof RedirectResponse) {
$context["target"] = $response->getTargetUrl();
}
return $context;
}
private function formatDifference($a, $b)
{
if (is_null($a) || is_null($b)) {
return null;
}
return $this->formatTime($b - $a);
}
private function formatTime($value)
{
if (is_null($value)) {
return null;
}
return sprintf("%.03f", $value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment