[Laravel] 用户请求与服务器响应的日志记录

1,850 阅读2分钟

在一个项目中,不是简简单单做好请求处理与响应就可以了,我们还需要完整的记录用户的所有行为,以方便我们掌握程序是否存在漏洞和bug,比如我们使用Laravel框架开发项目时,可以在中间件上记录用户的请求与服务器给出对应的响应数据。

这里假设你已经安装好环境和Laravel框架。

1.创建中间件

进入Laravel项目根目录,执行如下:

php artisan make:middleware UserRequestAndResponse

如果不报错的话,会提示成功如下:

Middleware created successfully.

2. 自定义日志类

进入app目录,新建文件夹 Utils

cd app && mkdir Utils

新建PHP类文件CustomLogger

<?php

namespace App\Utils; 
use Illuminate\Support\Facades\Log;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class CustomLogger 
{ 
    /** 
    * @param $message 
    * @param array $data 
    * @param string $filename 
    * @param string $type 
    */ 
    private static function _save($message, $data = [], $filename = 'log' , $type = 'info') 
    { 
        try{ 
            $dir_name = $filename; 
            $log = new Logger('req_res'); 
            if (PHP_SAPI == 'cli') { 
                // 命令行访问脚本的,加一个cli标识和用户浏览器访问的区分开 
                $filename .= '_cli'; 
            } 
            $filename = date('Y-m-d') . '-'.$filename .'.log'; 
            $path = storage_path('logs/'.$dir_name); 
            // 有时候运维没给号权限,容易导致写入日志失败 
            self::mkDirs($path); 
            $path = $path . '/' . $filename; 
            if (gettype($data) != 'array') { 
                $message .= ":" . $data; 
                $data = []; 
            } 
            if($type == 'info'){ 
                $log->pushHandler(new StreamHandler($path, Logger::INFO)); 
                $log->addInfo($message, $data); 
             }else{ 
                $log->pushHandler(new StreamHandler($path, Logger::ERROR)); 
                $log->addError($message , $data); 
             } 
        }catch (\Exception $e){ 
            Log::info("日志记录错误:".$e->getMessage()); 
        }
    } 

    /** 
    * @param $message 
    * @param array $data 
    * @param string $filename 
    */ 
    public static function info($message, $data = [], $filename = 'info') 
    { 
        self::_save($message, $data, $filename); 
    } 

    /** 
    * @param $message 
    * @param array $data 
    * @param string $filename 
    */ 
    public static function error($message, $data = [], $filename = 'error') 
    { 
        // 错误日志不会太多,按单文件记录可以了
        self::_save($message, $data, $filename); 
    } 

    /** 
    * 给日志文件夹权限 
    *
    * @param $dir 
    * @param int $mode 
    * @return bool 
    */ 
    public static function mkDirs($dir, $mode = 0777) 
    { 
        if (is_dir($dir) || @mkdir($dir, $mode)) {
            return TRUE;         
        } 

        if (!self::mkdirs(dirname($dir), $mode)) { 
            return FALSE; 
        } 
        return @mkdir($dir, $mode); 
    } 

CustomLogger日志类的使用如下:

use App\Utils\CustomLogger;

/**
* 定义日志名
*/
const LOG_NAME = 'req_res';

# 在方法中使用
CustomLogger::info($message , $data , self::LOG_NAME);
CustomLogger::error($message , $data , self::LOG_NAME);

3. 编辑UserRequestAndResponse中间件

文件位置:

app/Http/Middleware/UserRequestAndResponse.php

<?php

namespace App\Http\Middleware;
use App\Utils\ReqResLogger;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class UserRequestAndResponse
{    
    protected $sequence;   
    protected $code;

    const LOG_NAME = 'req_res';

    /**     
    * 记录请求与响应数据     
    *     
    * UserRequestAndResponse constructor.     
    * @param Request $request     
    */   
    public function __construct(Request $request)    
    {        
        $this->code     = $request->path();        
        $this->sequence = str_pad(microtime(true), 15, 0);    
    }    

    /**     
    * 处理主程序.     
    *     
    * @param  \Illuminate\Http\Request  $request     
    * @param  \Closure  $next     
    * @return mixed     
    */    
    public function handle($request, Closure $next)    
    {        
        ReqResLogger::info("======================================" , [] , self::LOG_NAME);

        $this->request($request); 

        $response = $next($request);        
        if ($response instanceof JsonResponse) {            
            $this->response($response);        
        }

        $this->time();        
        ReqResLogger::info("======================================" , [] , self::LOG_NAME);        return $response;    
    }    

    /**     
    * 时间计算     
    *     
    */    
    protected function time():void    
    {        
        $start = str_pad(LARAVEL_START, 15, 0);
        $end   = str_pad(microtime(true), 15, 0);
        $standard = config('develop.runtime-lower-limit');
        $time     = ($end - LARAVEL_START) * 1000;
        switch (true) {
            case $time > $standard:
                $mark = '超过[>]';
                $diff = $time - $standard;
                break;
            case $time === $standard: 
                $mark = '持平[=]';
                $diff = 0;
                break;
            default:
                $mark = '低于[<]';
                $diff = $time - $standard;
        } 
        $message = "请求序号[{$this->sequence}] 请求接口[{$this->code}] 开始时间[{$start}] 结束时间[{$end}] 运行时间[{$time}] {$mark}[{$standard}] {$diff}毫秒";

        ReqResLogger::info($message ,[], self::LOG_NAME);    }    

    /**    
    * 请求记录     
    *     
    * @param $request     
    */    
    protected function request($request):void    
    {        
        $input = $request->input();
        if (array_key_exists('data', $input)) {
            $input['data'] = json_decode($input['data'], true);
        }

        ReqResLogger::info("请求数据:" ,$input, self::LOG_NAME);    }    

    /**     
    * 响应记录     
    *     
    * @param JsonResponse $response     
    */    
    protected function response(JsonResponse $response):void    
    {        
        $output = json_decode($response->getContent(), true);        
        if (json_last_error() === JSON_ERROR_NONE) {            
            ReqResLogger::info("响应数据:" ,$output, self::LOG_NAME);                }    
    }
}

4. 编辑Kernel文件

文件位置:

app/Http/Kernel.php

protected $middleware = [    
    \App\Http\Middleware\TrustProxies::class,    
    \App\Http\Middleware\CheckForMaintenanceMode::class,    
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,    
    \App\Http\Middleware\TrimStrings::class,    
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,    
+   \App\Http\Middleware\UserRequestAndResponse::class];

end.至此配置已经结束

当访问项目的时候,会在storage/logs目录下新建一个目录res_req,并在该目录下生成一个日志文件xxxx-xx-xx-res_req.log

试试吧,以后用户的请求与服务器的响应就有个记录了,长吐一口气......心里踏实了。