Laravel-final-logger provides unique and consistent formats throughout log (all Http, Ajax requests & responses), payload (all Http, Ajax requests & responses), and server-side error log. Also, you can nullify any children properties to reduce the size of logged files.
composer require arwg/laravel-final-logger
php artisan vendor:publish --provider="Arwg\FinalLogger\FinalLoggerServiceProvider" --tag="config"
'providers' => [
Arwg\FinalLogger\FinalLoggerServiceProvider::class
]
$app->singleton(Arwg\FinalLogger\ErrorLogHandlerInterface::class, Arwg\FinalLogger\ErrorLogHandler::class);
$app->singleton(Arwg\FinalLogger\GeneralLogHandlerInterface::class, Arwg\FinalLogger\GeneralLogHandler::class);
$app->singleton(Arwg\FinalLogger\Exceptions\CommonExceptionModel::class, function ($app) {
return new Arwg\FinalLogger\Exceptions\CommonExceptionModel();
});
return [
'general_logger' => \Arwg\FinalLogger\GeneralLogHandler::class, // necessary
'error_logger' => \Arwg\FinalLogger\ErrorLogHandler::class, // necessary
'general_log_path' => 'your-path', // necessary
'request_excepted_log_data' => [
'final-test-uri' => [['password'],['password_reset']]
],
'response_excepted_log_data' => [
'final-test-uri' => [['a','b', 'c'], ['a','d']]
],
'success_code' => [
'OK' => 200,
'No Content' => 204
],
'error_code' => [
'Internal Server Error' => 500, // necessary
'Bad Request' => 400,
'Unauthorized' => 401,
'Not Found' => 404,
'Request Timeout' => 408,
'Precondition Failed' => 412,
'Unprocessable Entity' => 422
],
'error_user_code' => [
'all unexpected errors' => 900, // necessary
'socket error' => 1113,
'DB procedure...' => 1200,
]
];
Simple to use
####1. How to error-log
// app/Http/Exceptions/Handler.php (or your registered Handler)
namespace App\Exceptions;
use Arwg\FinalLogger\Payload;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
protected $dontReport = [
];
public function report(Exception $exception)
{
// If you want to leave auth token invalidation info, don't comment this.
if ($this->shouldntReport($exception)) {
return;
}
// Can use only 'Payload::reportError($exception)' but recommends to create your own FinalException. So I have created a sample below.
Payload::reportError($exception, function ($exception){
return $this->createFinalException($exception);
});
}
public function render($request, Exception $exception)
{
$message = null;
/* XmlHttpRequest (Ajax) */
if (\Request::wantsJson()) {
if (Config('app.env') && Config('app.env') == 'local') {
return Payload::renderError($exception, false);
}else{
return Payload::renderError($exception, true);
}
} else {
/* HttpRequest */
try{
if (Config('app.env') && Config('app.env') == 'local') {
return parent::render($request, $exception->getPrevious() ? $exception->getPrevious() : $exception);
}else{
// Follow Laravel auth in case of HttpRequest 401, 422.
if($exception->getCode() == 422 || $exception->getCode() == 401){
return parent::render($request, $exception->getPrevious() ? $exception->getPrevious() : $exception);
}
// Customize this according to your environment.
return response()->view('errors.error01', ['code' => '...', 'message' => 'Server error. Ask the administrator.']);
}
}catch (\Throwable $e){
// Customize this according to your environment.
return response()->view('errors.error01', ['code' => '...', 'message' => 'Server error. Ask the administrator.']);
}
}
}
// This is only sample. You can create your own FinalException.
private function createFinalException(\Exception $e) : FinalException
{
$internalMessage = $e->getMessage();
$lowLeverCode = $e->getCode();
// 422: Laravel validation error
if (isset($e->status) && $e->status == 422) {
return new FinalException('Failed in Laravel validation check.',
$internalMessage, config('final-logger.error_user_code')['paraemeter validation'], $e->errors(),
config('final-logger.error_code')['Unprocessable Entity'], $e->getTraceAsString(), $e);
}// 401 Oauth2 (id, password)
else if ($e instanceof AuthenticationException || ($e instanceof ClientException && $lowLeverCode == 401)) {
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$internalMessage .= ' / ' . $_SERVER['HTTP_AUTHORIZATION'];
}
if (preg_match('/invalid.credentials|Unauthorized/', $internalMessage)) {
return new FinalException('Wrong ID, Password.',
$internalMessage, null, "",
config('final-logger.error_code')['Unauthorized'], $e->getTraceAsString(), $e);
} else if (preg_match('/Unauthenticated/', $internalMessage)) {
return new FinalException('token not valid.',
$internalMessage, null, "",
config('final-logger.error_code')['Unauthorized'], $e->getTraceAsString(), $e);
} else {
return new FinalException('Auth error.',
$internalMessage, null, "",
config('final-logger.error_code')['Unauthorized'], $e->getTraceAsString(), $e);
}
} // Oauth2 (token)
else if ($e instanceof OAuthServerException) {
return new FinalException('Oauth2 token error.',
$internalMessage, config('final-logger.error_user_code')['AccessToken error'], $e->getPayload(),
config('final-logger.error_code')['Unauthorized'], $e->getTraceAsString(), $e);
} else {
$userCode = config('final-logger.error_user_code')['all unexpected errors'];
return new FinalException('Data (server-side) error has occurred.',
$internalMessage, $userCode, "LowLeverCode : " . $lowLeverCode,
config('final-logger.error_code')['Internal Server Error'], $e->getTraceAsString(), $e);
}
}
}
####2. How to general-log
Register logging path on 'config/final-logger.php'
return [
'general_log_path' => 'your-path', // necessary
];
Register this Middleware to 'app/Http/Kernal.php'
protected $routeMiddleware = [
//...
'your-name' => \Arwg\FinalLogger\Middlewares\WriteGeneralLog::class
];
// In api, web.php. Surround every route.
Route::group(['middleware' => 'your-name'], function () {
//...
});
The advantage of the library is that it is possible to set certain properties empty in payload hierarchy. Let's say we are setting the property 'stats' empty and 'img_cnt' empty at an uri (api/v2/final-test-uri/images).
{
"baseData":{
"data":{
"stats":[
{
"id":18,
"binary":"base64LDLDLDLS....",
"cnt":1,
"created_at":"2020-06-10 15:19:56",
"updated_at":"2020-06-10 15:19:56"
}
],
"img_cnt":9,
"img_total_cnt":100000
}
},
"successCode":200
}
Modify 'config/final-logger.php'.
// config/final-logger.php
return [
'response_excepted_log_data' => [
'api/v2/final-test-uri/images' => [['baseData', 'data','stats'],['baseData', 'data','img_cnt']]
],
'request_excepted_log_data' => [
// ... others
]
];
Now they all have been marked as "xxx" for 'api/v2/final-test-uri/images'.
{
"baseData":{
"data":{
"stats":[
{
"id":18,
"binary":"xxx",
"cnt":1,
"created_at":"2020-06-10 15:19:56",
"updated_at":"2020-06-10 15:19:56"
}
],
"img_cnt":"xxx",
"img_total_cnt":100000
}
},
"successCode":200
}
####3. Config file Check the properties marked 'necessary' below. The others are just what I customized.
// config/final-logger.php
return [
'general_logger' => \Arwg\FinalLogger\GeneralLogHandler::class, // necessary
'error_logger' => \Arwg\FinalLogger\ErrorLogHandler::class, // necessary
'general_log_path' => config('app.dashboard_all_request_response'), // necessary
'request_excepted_log_data' => [
'final-test-uri' => [['password'],['password_reset']]
],
'response_excepted_log_data' => [
'final-test-uri' => [['a','b', 'c'], ['a','d']]
],
'success_code' => [
'OK' => 200,
'No Content' => 204
],
'error_code' => [
'Internal Server Error' => 500, // necessary
'Bad Request' => 400,
'Unauthorized' => 401,
'Not Found' => 404,
'Request Timeout' => 408,
'Precondition Failed' => 412,
'Unprocessable Entity' => 422
],
'error_user_code' => [
'all unexpected errors' => 900, // necessary
'socket error' => 1113,
'DB procedure...' => 1200,
]
];
####4. How to throw errors on codes For handled errors, use this unique format.
// In case of NOT Ajax, add a current exception to the last parameter of FinalException (like $e below).
throw new FinalException('requested email address is not valid.',
"", config('final-logger.error_user_code')['parameter validation'], "",
config('final-logger.error_code')['Bad Request'], $e);
or for unhandled errors. No need to handle any. But if you need...
Payload::createFinalException($e, function ($e){
// example
return new FinalException(...);
// OR I recommend creating one function as shown Number 1.
});
####5. Error logging without throwing exceptions This doesn't make application stop but just log.
try {
$binary = Storage::disk('aaa')->get($file_name);
}catch (\Exception $e){
Payload::processFinalErrorLog(config('final-logger.error_code')['Internal Server Error'], \Arwg\FinalLogger\Exceptions\CommonExceptionModel::getExceptionMessage('error when opening a file',
$e->getMessage(),
'lowlevelcode : ' . $e->getCode(), config('final-logger.error_user_code')['all unexpected errors'], $e->getTraceAsString()));
}
####6. Success payload (not necessary)
class ArticleController extends Controller
{
public function index(Request $request)
{
$data = $this->getData($$request->all());
return Payload::renderSuccess(['list' => $data], config('final-logger.success_code')['OK']);
}
}
1. server-side error log sample
{
"final":{
"error_code":401,
"error_payload":{
"errors":{
"userMessage":"Oauth2 token error.",
"internalMessage":"The resource owner or authorization server denied the request.",
"userCode":1400,
"info":{
"error":"access_denied",
"error_description":"The resource owner or authorization server denied the request.",
"hint":"Access token has been revoked",
"message":"The resource owner or authorization server denied the request."
},
"stackTraceString":""
}
},
"general_log":{
"ip":"::1",
"date":"2020-06-22 17:18:18",
"type":"api",
"uri":"api\/v1\/comments",
"auth_header":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjgwZjdkM2RjNmNhZGJhYWYzMjg3YTY0YzIyY2U3MjZjNGY2MDIzODUyNmYyMWI0OGY5ZTdhZTQ1ZGJmYjZkNjhmZmE4MGMwOThkNDA3NzI4In0.eyJhdWQiOiIxMSIsImp0aSI6IjgwZjdkM2RjNmNhZGJhYWYzMjg3YTY0YzIyY2U3MjZjNGY2MDIzODUyNmYyMWI0OGY5ZTdhZTQ1ZGJmYjZkNjhmZmE4MGMwOThkNDA3NzI4IiwiaWF0IjoxNTkyODEzODQ1LCJuYmYiOjE1OTI4MTM4NDUsImV4cCI6MTU5MzI0NTg0NSwic3ViIjoiMTIyIiwic2NvcGVzIjpbXX0.A-7pTEoF7GyNc6zbCgDcK1IzMPoc6UWE3XNbl8Q6ZWyBe-a7Pfr0f5Ku1yCkQDimXBxH08Zy_7BQwULclTVO68XE0YgEWvP27FtlpXzMc4lzafUxhXKGR9NmLiXBUcYIWzx6r4tm6fgD337P5Gf0921jJ-tT33Pu7oZAbrLVQqiFu_gDKUBTBOcGVjHsQF5EwNAzpMb3Orn6AVF5W8rtO-flKrDUnnJcflS-XAtJiqobv5AGEa6faUrywCkElztJH9B2c5jSE_gxIozuH8ek7IC0lKPquwwqZvv-b_XukJOKEO4rgqyPSvDqVn9qJuV2uHkdNV05sdHZEU1a2BCmORj7BCtlCQpzDmVE4jdedXTwU1VZA8fxlyGZgW9_lACIx2Sc_fpmrEVULrT1SKfOvikZXFJSMBcxVh3z7ZF55Mbgqs4ifkjfk3MkeSYq9xsM-vB--Sxzzz7FsGh9KgGCTDNftNT8YmvokX5jSzruNxZUg4SGT7mqRd61Wplyd4sURkIEQvBEeTQmH0jwv-xWYCfK5Edm0HEP0DPs_TChF27NmDkp4kFBpahfph2-rkcf6fxvzyk6ZNJspUsDjbVfVN8A0MjG7pHm53IlDVtYqORkRMnjVNIaQqGLMX5NPKWqRoXhkAdW3TzN_ShxXzG_KRG3ciCOTXWUzuydmHkbkPc",
"user_id":null,
"request_data":[
],
"response_status":null,
"response_data":null
}
}
}
2. response log sample : this is the same as children of the general_log property above.
{
"ip":"::1",
"date":"2020-06-22 17:18:18",
"type":"api",
"uri":"api\/v1\/comments",
"auth_header":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjgwZjdkM2RjNmNhZGJhYWYzMjg3YTY0YzIyY2U3MjZjNGY2MDIzODUyNmYyMWI0OGY5ZTdhZTQ1ZGJmYjZkNjhmZmE4MGMwOThkNDA3NzI4In0.eyJhdWQiOiIxMSIsImp0aSI6IjgwZjdkM2RjNmNhZGJhYWYzMjg3YTY0YzIyY2U3MjZjNGY2MDIzODUyNmYyMWI0OGY5ZTdhZTQ1ZGJmYjZkNjhmZmE4MGMwOThkNDA3NzI4IiwiaWF0IjoxNTkyODEzODQ1LCJuYmYiOjE1OTI4MTM4NDUsImV4cCI6MTU5MzI0NTg0NSwic3ViIjoiMTIyIiwic2NvcGVzIjpbXX0.A-7pTEoF7GyNc6zbCgDcK1IzMPoc6UWE3XNbl8Q6ZWyBe-a7Pfr0f5Ku1yCkQDimXBxH08Zy_7BQwULclTVO68XE0YgEWvP27FtlpXzMc4lzafUxhXKGR9NmLiXBUcYIWzx6r4tm6fgD337P5Gf0921jJ-tT33Pu7oZAbrLVQqiFu_gDKUBTBOcGVjHsQF5EwNAzpMb3Orn6AVF5W8rtO-flKrDUnnJcflS-XAtJiqobv5AGEa6faUrywCkElztJH9B2c5jSE_gxIozuH8ek7IC0lKPquwwqZvv-b_XukJOKEO4rgqyPSvDqVn9qJuV2uHkdNV05sdHZEU1a2BCmORj7BCtlCQpzDmVE4jdedXTwU1VZA8fxlyGZgW9_lACIx2Sc_fpmrEVULrT1SKfOvikZXFJSMBcxVh3z7ZF55Mbgqs4ifkjfk3MkeSYq9xsM-vB--Sxzzz7FsGh9KgGCTDNftNT8YmvokX5jSzruNxZUg4SGT7mqRd61Wplyd4sURkIEQvBEeTQmH0jwv-xWYCfK5Edm0HEP0DPs_TChF27NmDkp4kFBpahfph2-rkcf6fxvzyk6ZNJspUsDjbVfVN8A0MjG7pHm53IlDVtYqORkRMnjVNIaQqGLMX5NPKWqRoXhkAdW3TzN_ShxXzG_KRG3ciCOTXWUzuydmHkbkPc",
"user_id":null,
"request_data":[
],
"response_status":null,
"response_data":null
}
3. error response payload sample : this is the same as children of the error_payload above.
// userMessage : intended to be sent to clients.
// internalMessage : not intended to be sent to clients, but logged.
// userCode : intended to be sent to clients. (recommends to customize it )
"errors":{
"userMessage":"Oauth2 token error.",
"internalMessage":"",
"userCode":1400,
"info":{
"error":"access_denied",
"error_description":"The resource owner or authorization server denied the request.",
"hint":"Access token has been revoked",
"message":"The resource owner or authorization server denied the request."
},
"stackTraceString":""
}
We focus on the final endpoints, so logging is conducted at the only following two points.
1. Middleware : Response endpoints.
2. The point when the error occurs.
No request endpoint logging is conducted, as the two points can catch all request data.