arwg / laravel-final-logger
Laravel 包,用于保持请求、响应负载和日志格式的唯一一致性。
Requires
- php: ^7.1
- illuminate/support: ~5.8.0|^6.0|^7.0
Requires (Dev)
- orchestra/testbench: ~3.8.0|^4.0|^5.0
- phpunit/phpunit: ^8.0|^9.0
README
概述
Laravel-final-logger 为所有 HTTP 和 Ajax 请求、响应以及服务器端错误日志提供唯一且一致的日志格式。此外,它允许您将任何子属性置为空,以减小日志文件的大小。
安装
composer require arwg/laravel-final-logger php artisan vendor:publish --provider="Arwg\FinalLogger\FinalLoggerServiceProvider" --tag="config"
config/app.php
'providers' => [ Arwg\FinalLogger\FinalLoggerServiceProvider::class ]
bootstrap/app.php
$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(); });
config/final-logger.php (样本)
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, ] ];
使用方法
简单易用
####1. 如何进行错误日志记录
// 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. 如何进行一般日志记录
重要
在 'config/final-logger.php' 上注册日志路径
return [ 'general_log_path' => 'your-path', // necessary ];
在 '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 () { //... });
设置某些属性为空(由于减小日志文件的大小或其他原因)
该库的优势在于,它允许您在负载层次结构内设置某些属性为空,以减小日志文件的大小或出于其他原因。例如,您可以针对特定 URI(api/v2/final-test-uri/images)将属性 'stats' 和 'img_cnt' 设置为空。
{ "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 }
修改 '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 ] ];
现在它们都已被标记为 "xxx",针对 '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/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. 在代码中抛出错误的方法 对于已处理错误,使用此独特格式。
// In the case of non-Ajax requests, add the current exception as 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);
或对于未处理错误。不需要处理任何错误。但如果需要...
Payload::createFinalException($e, function ($e){ // example return new FinalException(...); // OR I recommend creating one function as shown Number 1. });
####5. 无抛出异常的错误日志记录 这不会使应用程序停止,只是记录。
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. 成功负载(非必需)
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. 服务器端错误日志示例
{
"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. 响应日志示例:这是与上述 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
}
3. 错误响应负载示例:这是与上述 error_payload 的子属性相同。
// 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":""
}
我们专注于最终端点,因此日志记录仅在以下两个位置进行
1. 中间件:响应端点。
2. 错误发生时。
在请求端点不进行日志记录,因为上述提到的两个点可以捕获所有请求数据。