Để giúp bạn hiểu thêm về những gì đang xảy ra trong ứng dụng của bạn, Laravel cung cấp các dịch vụ ghi log mạnh mẽ cho phép bạn log các message vào file, log lỗi hệ thống hoặc thậm chí là cả Slack để thông báo cho toàn bộ team của bạn.
Laravel logging được dựa trên "channel". Mỗi channel sẽ thể hiện một cách ghi log cụ thể. Ví dụ: channel single sẽ ghi các file log vào một file log duy nhất, trong khi channel slack sẽ gửi thông báo log đến Slack. Thông báo log có thể được ghi vào nhiều channel dựa trên mức độ nghiêm trọng của chúng.
Laravel sử dụng thư viện Monolog để cung cấp và hỗ trợ nhiều cách xử lý log mạnh mẽ. Laravel giúp bạn dễ dàng cấu hình các cách xử lý này, cho phép bạn pha trộn và kết hợp chúng lại để tùy chỉnh việc xử lý log trong ứng dụng của bạn.
Tất cả các tùy chọn cấu hình dành cho việc điều khiển các hành động của hệ thống ghi log của ứng dụng của bạn sẽ được lưu trong file cấu hình config/logging.php. File này cho phép bạn cấu hình các channel log, vì vậy hãy đảm bảo là bạn đã xem qua các channel hiện có và các tùy chọn của chúng. Chúng ta cũng sẽ xem xét một số tùy chọn phổ biến ở bên dưới.
Mặc định, Laravel sẽ sử dụng channel stack để ghi log. Channel stack có thể được sử dụng để tổng hợp nhiều channel log thành một channel. Để biết thêm thông tin về cách xây dựng stack, hãy xem tài liệu ở bên dưới.
Mỗi log channel được cung cấp bởi một "driver". Driver sẽ xác định xem cách thức và vị trí của log message sẽ được ghi lại. Những Driver log channel sau đây sẽ có sẵn trong mọi ứng dụng Laravel. Một mục dành riêng cho các driver này đã có sẵn trong file cấu hình config/logging.php của ứng dụng của bạn, vì vậy hãy nhớ xem lại file này để làm quen với nội dung của nó:
[!NOTE] Xem tài liệu về tùy chỉnh channel nâng cao để tìm hiểu thêm về driver
monologvàcustom.
Mặc định, Monolog được khởi tạo bởi "tên channel" phù hợp với môi trường hiện tại đang chạy ứng dụng, chẳng hạn như production hoặc local. Để thay đổi giá trị này, bạn có thể thêm tùy chọn name vào cấu hình channel của bạn:
'stack' => [
'driver' => 'stack',
'name' => 'channel-name',
'channels' => ['single', 'slack'],
],
Các channel single và daily có thêm ba tùy chọn cấu hình khác: bubble, permission, và locking.
Ngoài ra, thời hạn lưu giữ file log cho channel daily có thể được cấu hình thông qua biến môi trường LOG_DAILY_DAYS hoặc thông qua cách set tùy chọn cấu hình days.
Channel papertrail sẽ yêu cầu các tùy chọn cấu hình host và port. Các giá trị này có thể được định nghĩa thông qua các biến môi trường PAPERTRAIL_URL và PAPERTRAIL_PORT. Bạn có thể lấy các giá trị này từ Papertrail.
Channel slack yêu cầu một cấu hình url. Giá trị này có thể được định nghĩa thông qua biến môi trường LOG_SLACK_WEBHOOK_URL. URL này phải khớp với một URL đã cho của một webhook mà bạn đã cấu hình trong nhóm Slack của bạn.
Mặc định, Slack sẽ chỉ nhận các log ở cấp độ critical trở lên; tuy nhiên, bạn có thể điều chỉnh điều này bằng cách sử dụng biến môi trường LOG_LEVEL hoặc bằng cách sửa tùy chọn cấu hình level trong mảng cấu hình của channel log Slack của bạn.
PHP, Laravel và các thư viện khác thường thông báo cho người dùng biết một số tính năng của php, laravel hoặc của một thư viện khác sẽ không dùng được nữa và sẽ bị loại bỏ trong những phiên bản khác. Nếu muốn ghi lại những cảnh báo này, bạn có thể chỉ định log channel deprecations bằng cách sử dụng biến môi trường LOG_DEPRECATIONS_CHANNEL, hoặc trong file cấu hình config/logging.php của ứng dụng:
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
'channels' => [
// ...
]
Hoặc, bạn có thể định nghĩa một log channel có tên là deprecations. Nếu log channel có tên này tồn tại, nó sẽ luôn được sử dụng để ghi lại các trường hợp ngừng sử dụng như thế này:
'channels' => [
'deprecations' => [
'driver' => 'single',
'path' => storage_path('logs/php-deprecation-warnings.log'),
],
],
Như đã đề cập trước đây, driver stack cho phép bạn kết hợp nhiều channel thành một channel duy nhất để thuận tiện. Để minh họa cho cách sử dụng stack log, hãy xem một cấu hình mẫu sau mà bạn có thể thấy trong ứng dụng thực tế:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['syslog', 'slack'], // [tl! add]
'ignore_exceptions' => false,
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
],
Hãy cùng xem xét cấu hình này. Đầu tiên, hãy để ý đến channel stack của chúng ta, nó được tổng hợp từ hai channel khác nhau thông qua tùy chọn channels của nó: syslog và slack. Vì vậy, khi ghi log message, cả hai channel này đều sẽ được ghi log message. Tuy nhiên, như chúng ta sẽ thấy, việc các channel này có thực sự ghi log hay không có thể được xác định bởi mức độ nghiêm trọng của tin nhắn.
Hãy lưu ý tùy chọn cấu hình level có trong cấu hình channel syslog và slack có trong ví dụ ở trên. Tùy chọn này sẽ xác định xem "mức độ" tối thiểu mà một message phải có để có thể được channel ghi log. Monolog hỗ trợ các service ghi log của Laravel, cung cấp tất cả các cấp độ log được định nghĩa trong đặc tả RFC 5424. Theo thứ tự giảm dần về mức độ nghiêm trọng, các mức log này là: emergency, alert, critical, error, warning, notice, info, và debug.
Vì thế, code dưới đây là chúng ta đang ghi log một message bằng phương thức debug:
Log::debug('An informational message.');
Dựa vào cấu hình ở phía trên, thì channel syslog sẽ ghi message này vào trong system log; tuy nhiên, vì message này không phải là loại critical hoặc lớn hơn, nên nó sẽ không được gửi đến Slack. Tuy nhiên, nếu chúng ta ghi lại một log message là emergency, thì nó sẽ được gửi đến cả system log và Slack vì mức độ emergency sẽ cao hơn mức độ mà chúng ta đã cài đặt cho cả hai channel:
Log::emergency('The system is down!');
Bạn có thể ghi thêm thông tin vào log bằng cách sử dụng facade Log. Như đã đề cập ở trên, log sẽ cung cấp tám cấp độ ghi log được định nghĩa trong đặc tả RFC 5424: emergency, alert, critical, error, warning, notice, info và debug:
use Illuminate\Support\Facades\Log;
Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);
Vì vậy, bạn có thể gọi bất kỳ phương thức nào trong các phương thức này để ghi log một message cho một cấp độ tương ứng. Mặc định, message sẽ được ghi vào channel log như được cấu hình bởi file cấu hình logging của bạn:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function show(string $id): View
{
Log::info('Showing the user profile for user: {id}', ['id' => $id]);
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
Một mảng dữ liệu có thể được truyền vào cho các phương thức log. Các dữ liệu này sẽ được định dạng và hiển thị cùng với thông báo log:
use Illuminate\Support\Facades\Log;
Log::info('User {id} failed to login.', ['id' => $user->id]);
Đôi khi, bạn có thể muốn chỉ định một số thông tin ngữ cảnh cần được đưa vào log. Ví dụ: bạn có thể muốn ghi lại ID request được liên kết với từng request được gửi đến ứng dụng của bạn trong một channel cụ thể. Để thực hiện điều này, bạn có thể gọi phương thức withContext của facade Log:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class AssignRequestId
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$requestId = (string) Str::uuid();
Log::withContext([
'request-id' => $requestId
]);
$response = $next($request);
$response->headers->set('Request-Id', $requestId);
return $response;
}
}
Nếu bạn muốn chia sẻ thông tin ngữ cảnh trên tất cả các channel log, bạn có thể gọi phương thức Log::shareContext(). Phương thức này sẽ cung cấp thông tin ngữ cảnh cho tất cả các channel đã tạo và bất kỳ channel nào được tạo sau đó:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class AssignRequestId
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$requestId = (string) Str::uuid();
Log::shareContext([
'request-id' => $requestId
]);
// ...
}
}
[!NOTE] Nếu bạn cần chia sẻ log trong khi xử lý các queued job, bạn có thể sử dụng job middleware.
Đôi khi bạn cũng có thể muốn ghi log một message vào một channel khác, khác với channel mặc định của ứng dụng của bạn. Bạn có thể sử dụng phương thức channel trên facade Log để lấy ra và log message vào bất kỳ channel nào mà đã được định nghĩa trong file cấu hình của bạn:
use Illuminate\Support\Facades\Log;
Log::channel('slack')->info('Something happened!');
Nếu bạn muốn tạo một stack để ghi log ở nhiều channel khác nhau, bạn có thể sử dụng phương thức stack:
Log::stack(['single', 'slack'])->info('Something happened!');
Cũng có thể tạo một channel theo yêu cầu bằng cách cung cấp cấu hình trong khi chạy thời gian thực mà không cần có cấu hình đó trong file cấu hình logging của ứng dụng của bạn. Để thực hiện điều này, bạn có thể truyền một mảng cấu hình tới phương thức build của facade Log:
use Illuminate\Support\Facades\Log;
Log::build([
'driver' => 'single',
'path' => storage_path('logs/custom.log'),
])->info('Something happened!');
Bạn cũng có thể muốn đưa channel theo yêu cầu này vào stack logging. Điều này có thể đạt được bằng cách đưa instance channel theo yêu cầu của bạn vào mảng được truyền cho phương thức stack:
use Illuminate\Support\Facades\Log;
$channel = Log::build([
'driver' => 'single',
'path' => storage_path('logs/custom.log'),
]);
Log::stack(['slack', $channel])->info('Something happened!');
Thỉnh thoảng bạn có thể cần kiểm soát cách cấu hình Monolog cho một channel. Ví dụ: bạn có thể muốn cấu hình một implementation Monolog FormatterInterface tùy biến cho channel single có sẵn của Laravel .
Để bắt đầu, hãy định nghĩa một mảng tap trong cấu hình channel của bạn. Mảng tap phải chứa một danh sách các class để tùy biến (hoặc "sửa") instance Monolog sau khi nó được tạo ra. Không có một vị trí mặc định nào để lưu các class này, vì vậy bạn có thể thoải mái tạo một thư mục trong ứng dụng của bạn để chứa các class này:
'single' => [
'driver' => 'single',
'tap' => [App\Logging\CustomizeFormatter::class],
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
Sau khi bạn đã cấu hình tùy chọn tap trong file cấu hình channel của bạn, bạn đã sẵn sàng để định nghĩa class sẽ tùy biến instance Monolog. Class này chỉ cần một phương thức duy nhất: __invoke phương thức này nhận vào một instance Illuminate\Log\Logger. Instance Illuminate\Log\Logger sẽ chuyển hướng tất cả các cuộc gọi phương thức đến trực tiếp instance Monolog để thực hiện:
<?php
namespace App\Logging;
use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;
class CustomizeFormatter
{
/**
* Customize the given logger instance.
*/
public function __invoke(Logger $logger): void
{
foreach ($logger->getHandlers() as $handler) {
$handler->setFormatter(new LineFormatter(
'[%datetime%] %channel%.%level_name%: %message% %context% %extra%'
));
}
}
}
[!NOTE] Tất cả các class "tap" của bạn đều được service container resolve, vì vậy mọi phụ thuộc trong hàm constructor sẽ tự động được đưa vào.
Monolog có nhiều xử lý có sẵn và Laravel không chứa bất kỳ channel nào cho mỗi xử lý đó. Trong một số trường hợp, bạn có thể muốn tạo ra một channel tùy biến là một instance của Monolog handler cụ thể tương ứng. Thì các channel này có thể dễ dàng được tạo ra bằng cách dùng driver monolog.
Khi sử dụng driver monolog, tùy chọn cấu hình handler sẽ chỉ định xử lý nào sẽ được khởi tạo. Còn các tham số khác thì đều có thể được chỉ định thông qua cách sử dụng tùy chọn cấu hình handler_with:
'logentries' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\SyslogUdpHandler::class,
'handler_with' => [
'host' => 'my.logentries.internal.datahubhost.company.com',
'port' => '10000',
],
],
Khi sử dụng driver monolog, Monolog LineFormatter sẽ được sử dụng làm định dạng mặc định. Tuy nhiên, bạn có thể tùy chỉnh loại định dạng mà bạn muốn bằng cách sử dụng tùy chọn cấu hình formatter và formatter_with:
'browser' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\BrowserConsoleHandler::class,
'formatter' => Monolog\Formatter\HtmlFormatter::class,
'formatter_with' => [
'dateFormat' => 'Y-m-d',
],
],
Nếu bạn đang sử dụng xử lý Monolog mà có khả năng cung cấp một định dạng của riêng nó, thì bạn có thể set giá trị của tùy chọn cấu hình formatter thành default:
'newrelic' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\NewRelicHandler::class,
'formatter' => 'default',
],
Monolog cũng có thể xử lý các message trước khi log chúng. Bạn có thể tạo một bộ xử lý của riêng bạn hoặc sử dụng bộ xử lý hiện có do Monolog cung cấp.
Nếu bạn muốn tùy chỉnh bộ xử lý cho driver monolog, bạn hãy thêm giá trị cấu hình processors vào cấu hình channel của bạn:
'memory' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\StreamHandler::class,
'with' => [
'stream' => 'php://stderr',
],
'processors' => [
// Simple syntax...
Monolog\Processor\MemoryUsageProcessor::class,
// With options...
[
'processor' => Monolog\Processor\PsrLogMessageProcessor::class,
'with' => ['removeUsedContextFields' => true],
],
],
],
Nếu bạn muốn định nghĩa một channel tùy biến, trong đó bạn có toàn quyền kiểm soát về việc khởi tạo và cấu hình Monolog, bạn có thể chỉ định loại driver custom trong file cấu hình config/logging.php của bạn. Cấu hình của bạn nên chứa một tùy chọn via để chứa tên của class factory sẽ được gọi để tạo instance Monolog:
'channels' => [
'example-custom-channel' => [
'driver' => 'custom',
'via' => App\Logging\CreateCustomLogger::class,
],
],
Sau khi bạn đã cấu hình xong driver channel custom, bạn đã sẵn sàng để định nghĩa class sẽ tạo instance Monolog của bạn. Class này chỉ cần một phương thức __invoke duy nhất sẽ trả về instance logger Monolog. Phương thức này sẽ nhận một mảng cấu hình channel làm tham số duy nhất của nó:
<?php
namespace App\Logging;
use Monolog\Logger;
class CreateCustomLogger
{
/**
* Create a custom Monolog instance.
*/
public function __invoke(array $config): Logger
{
return new Logger(/* ... */);
}
}
Thông thường, bạn có thể cần theo dõi log ứng dụng của bạn theo thời gian thực. Ví dụ, khi gỡ lỗi một sự cố hoặc khi theo dõi log ứng dụng của bạn để tìm một loại lỗi cụ thể nào đó.
Laravel Pail là một package cho phép bạn dễ dàng truy cập vào các file log của ứng dụng Laravel trực tiếp từ command line. Không giống như lệnh tail thuần tuý, Pail được thiết kế để hoạt động với bất kỳ driver log nào, bao gồm cà Sentry hoặc Flare. Ngoài ra, Pail cung cấp một bộ lọc hữu ích để giúp bạn nhanh chóng tìm thấy những gì bạn đang tìm kiếm.
[!WARNING] Laravel Pail yêu cầu extension PCNTL của PHP.
Để bắt đầu, hãy cài đặt Pail vào dự án của bạn bằng trình quản lý package Composer:
composer require laravel/pail
Để bắt đầu theo dõi log, hãy chạy lệnh pail:
php artisan pail
Để output chi tiết hơn và xoá bớt (…), hãy sử dụng thêm tùy chọn -v:
php artisan pail -v
Để chi tiết hơn nữa và hiển thị stack trace của ngoại lệ, hãy sử dụng tùy chọn -vv:
php artisan pail -vv
Để dừng theo dõi log, hãy nhấn Ctrl+C bất cứ lúc nào.
--filterBạn có thể sử dụng tùy chọn --filter để lọc bất kỳ log nào theo loại của chúng, file, tin nhắn và nội dung stack trace:
php artisan pail --filter="QueryException"
--messageĐể lọc log theo message của chúng, bạn có thể sử dụng tùy chọn --message:
php artisan pail --message="User created"
--levelTùy chọn --level có thể được sử dụng để lọc log theo log level:
php artisan pail --level=error
--userĐể hiển thị các log cho một người dùng nhất định, bạn có thể cung cấp ID của người dùng đó cho tùy chọn --user:
php artisan pail --user=1
entry