arwg/laravel-final-logger

Laravel 包,用于保持请求、响应负载和日志格式的唯一一致性。

v1.0.7 2021-02-02 05:01 UTC

This package is auto-updated.

Last update: 2024-09-24 09:07:29 UTC


README

Latest Version on Packagist

概述

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. 错误发生时。

在请求端点不进行日志记录,因为上述提到的两个点可以捕获所有请求数据。

变更日志

变更日志

许可证

许可证文件