Mở đầu
Logging là một việc cực kì quan trọng, và Cloudwatch là một nền tảng visualize và query log khá tốt đến từ AWS, chắc chắn các hệ thống trên AWS thường đều log tới e này, hôm nay mình xin giới thiệu cách để laravel 8 có thể dễ dàng push log tới Cloudwatch, bắt đầu thôi
Cách implement
Cài package
composer require maxbanton/cwh:^2.0
Tạo class CloudWatchLoggerFactory để tạo drive custom push lên cloudwatch
classCloudWatchLoggerFactory{/**
* Create a custom Monolog instance.
*
* @param array $config
* @return MonologLogger
*/publicfunction__invoke(array$config){$sdkParams=$config['sdk'];$tags=$config['tags']??[];$name=$config['name']??'cloudwatch';// Instantiate AWS SDK CloudWatch Logs Client$client=newCloudWatchLogsClient($sdkParams);// Log group name, will be created if none$groupName=$config['group'];// Log stream name, will be created if none$streamName=$config['stream'];// Days to keep logs, 14 by default. Set to `null` to allow indefinite retention.$retentionDays=$config['retention'];// Instantiate handler (tags are optional)$handler=newCloudWatch($client,$groupName,$streamName,$retentionDays,10000,$tags);// Create a log channel$logger=newLogger($name);// Set handler$logger->pushHandler($handler);return$logger;}}
Trong config/logging.php, thêm setting driver custom cho loại log này
'channels'=>['stack'=>['driver'=>'stack','channels'=>['single'],'ignore_exceptions'=>false,],'cloudwatch_error_log'=>['driver'=>'custom','via'=>AppServicesCloudWatchLoggerFactory::class,'sdk'=>['region'=>env('AWS_DEFAULT_REGION','ap-northeast-1'),'version'=>'latest',],'retention'=>30,'level'=>'info','group'=>env('CLOUDWATCH_LOG_GROUP','group-log'),'stream'=>env('CLOUDWATCH_LOG_STREAM','error-log'),],
Lựa chọn exception hợp lí để push lên cloudwatch, ở đây mình sẽ edit trong app/Exceptions/Handler.php, đây là nơi laravel hỗ trợ cho chúng ta custom cho toàn bộ quá trình handle Exception, bao gồm việc logging error và format response error của API, như lỗi 404 thì trả về thế nào, lỗi 500 thì trả về thế nào, … về phần mình, mình đính kèm thêm param request vào lỗi, như vậy chúng ta sẽ nắm bắt được chính xác request nào đã gây ra lỗi, thuận tiện hơn cho việc debug.
/**
* Report or log an exception.
*
* @param Throwable $e
* @return void
*
* @throws Throwable
*/publicfunctionreport(Throwable$e){$exceptionExcluse=[RouteNotFoundException::class,NotFoundHttpException::class,AuthorizationException::class,ValidationException::class,];if(!in_array(get_class($e),$exceptionExcluse)){$this->logPrettyError($e);}parent::report($e);}
Như các bạn thấy, các lỗi common như RouteNotFoundException hay NotFoundHttpException hoặc ValidationException, là do user nhập sai url hoặc form input thôi, nên ko đủ quan trọng để chúng ta phải log lại để điều tra :#)
Hàm log lỗi thêm param request:
privatefunctionlogPrettyError(Throwable$e){$request=request();$log=['access'=>['request'=>$request->all(),'method'=>$request->method(),'path'=>$request->path(),],'error'=>['class'=>get_class($e),'code'=>$e->getCode(),'message'=>$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine(),],];getLogger()->error(json_encode($log));}
Đừng quên chọn driver logging đúng với môi trường cần log cloudwatch nhé (thường là STG và PROD)
LOG_CHANNEL=cloudwatch_error_log
CLOUDWATCH_LOG_GROUP=
CLOUDWATCH_LOG_STREAM=
Thêm 2 helper để viết log trong code được tiện lợi hơn
/**
* Get logger
*/if(!function_exists('getLogger')){functiongetLogger(){returnLog::channel(env('LOG_CHANNEL','daily'));}}/**
* Log info
*/if(!function_exists('logInfo')){functionlogInfo($info){getLogger()->info($info);}}/**
* Log error
*/if(!function_exists('logError')){functionlogError($e){$logger=getLogger();if($einstanceofException){$logger->error($e->getMessage().' on line '.$e->getLine().' of file '.$e->getFile());}else{$logger->error($e);}}}
Cách filter trên cloudwatch thuận tiện cho việc đọc log
Dựa trên việc log phía trên, mình có 1 số cách query đọc log thuận tiện hơn
- query log lấy 20 cái gần nhất
fields @timestamp, @message | sort @timestamp desc | limit 25
- query get số lượng message chứa Exception trong vòng 1h
filter @message like /Exception/
| stats count(*) as exceptionCount by bin(1h)
| sort exceptionCount desc
- query log không chứa Exception
fields @message | filter @message not like /Exception/
- query đọc log lỗi đến từ API product
fields @timestamp, @message
| sort @timestamp desc
| filter @message like /ERROR(.*)api\/v1\/products/
Tham khảo:
https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html
Hy vọng việc logging cloudwatch với laravel sẽ cực kì dễ dàng cho bà con, thank for reading
Nguồn: viblo.asia