Created
August 12, 2020 23:47
-
-
Save thecrypticace/e5fd770f83228395c6b8ef9b5ecd22e6 to your computer and use it in GitHub Desktop.
Log requests middleware
This file contains hidden or 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 | |
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; | |
} | |
}); | |
} | |
} |
This file contains hidden or 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 | |
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 = []; | |
} | |
} |
This file contains hidden or 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 | |
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