Laravel 使用 Elasticsearch 作为日志存储

Laravel 使用 Elasticsearch 作为日志存储

简介

在实际开发中,我们发现在 Debug 的时候经常需要查询日志。而传统的方式是需要 SSH 到生产环境,然后使用 cattailgrep 等命令查询日志,且无法进行日志的统计和分析,深度挖掘这些日志的价值。

本片文章的侧重点在于优雅的让 Laravel 直接将日志写入 Elasticsearch,当然你也可以选择使用 Logstash 采集 Laravel 的本地日志。

环境搭建可以参考 《Elastic Stack 之 Elasticsearch》《Elastic Stack 之 Kibana》 这两篇博文。

所需依赖

  • elasticsearch/elasticsearch
  • betterde/logger(如果需要)

原理分析

Laravel 使用 monolog/monolog 作为默认日志处理模块。monolog 自带了很多 Handler 其中就有 ElasticsearchHandler。

生命周期

  • Illuminate\Foundation\Application 的构造函数中注册基本的服务提供者,其中就有 Illuminate\Log\LogServiceProvider

  • Illuminate\Log\LogServiceProvider 中将 Illuminate\Log\LogManager 注册到容器,此时还没有 Logger。

  • 当我们使用 Illuminate\Support\Facades\Log::info() 的时候,LogManager 会调用内部一系列方法根据配置文件创建 Logger 和其所需的 Handlers

  • 最终调用 Logger 的 info()、error()、debug() 等方法,实现记录日志的功能。

lifecycle

简单适配

只需要通过修改 config/logging.php.env 文件就可以实现,将日志直接写入 Elasticcsearch:

'elastic' => [
    'driver' => 'monolog',
    'level' => 'debug',
    'name' => 'Develop',
    'tap' => [],
    'handler' => ElasticsearchHandler::class,
    'formatter' => \Monolog\Formatter\ElasticsearchFormatter::class,
    'formatter_with' => [
        'index' => 'monolog',
        'type' => '_doc'
    ],

    'handler_with' => [
        'client' => \Elasticsearch\ClientBuilder::create()->setHosts(['http://localhost:9200'])->build(),
    ],
],
LOG_CHANNEL=elastic

轮子

简单适配后会发现当我们在写入日志的时候,Laravel 是单条同步进行的,对于我们使用原创存储来说这将影响一定的性能。所以我们需要在整个生命周期中临时保存所有的日志信息,在请求结束前触发同步或异步将日志批量写入存储。

为此我周末写了一个扩展包betterde/logger,如果你在使用的过程中有任何问题可以在Github上提 IssuePR

目前只支持 Laravel ^6.0.0 因为从 6.0 开始 monolog 的依赖是 2.,后面如果需要的话,会考虑开个1.分支兼容5.* 的版本。

安装

$ composer require betterde/logger
$ php artisan vendor:publish --tag=betterde.logger

配置

将如下配置追加到 config/logging.phpchannels 中:

use Betterde\Logger\ElasticsearchLogger;
'channels' => [
     'elastic' => [
         'driver' => 'custom',
         'via' => ElasticsearchLogger::class,
     ],
 ],

\Betterde\Logger\Http\Middleware\BulkCollectionLog 中间件添加到 App\Http\Kernel.php 文件中

/**
 * The application's global HTTP middleware stack.
 *
 * These middleware are run during every request to your application.
 *
 * @var array
 */
 protected $middleware = [
     \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
     \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
     \App\Http\Middleware\TrimStrings::class,
     \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
     \App\Http\Middleware\TrustProxies::class,
     \Betterde\Logger\Http\Middleware\BulkCollectionLog::class
 ];

.env 文件中添加如下配置项:

LOG_CHANNEL=elastic
ELASTICSEARCH_HOST=localhost
ELASTICSEARCH_PORT=9200
ELASTICSEARCH_SCHEME=http
ELASTICSEARCH_USER=
ELASTICSEARCH_PASS=

修改 config/logger.php 配置文件:

<?php
return [
    /*
    * 是否开启批量写入,需要设置中间件
    */
    'batch' => false,

    /*
    * 是否使用队列
    */
    'queue' => [
        'enable' => false,
        'name' => env('LOG_QUEUE_NAME', 'logging')
    ],
    /*
    * 日志级别,值可以参考 Monolog\Logger.php 中的定义
    */
    'level' => 200,
    /*
    * 是否在多个 Handler 中流转日志数据
    */
    'bubble' => false,
    /*
    * Elasticsearch DB
    */
    'elasticsearch' => [
        'hosts' => [
            [
                /*
                * host 是必填项
                */
                'host' => env('ELASTICSEARCH_HOST', 'localhost'),
                'port' => env('ELASTICSEARCH_PORT', 9200),
                'scheme' => env('ELASTICSEARCH_SCHEME', 'http'),
                'user' => env('ELASTICSEARCH_USER', null),
                'pass' => env('ELASTICSEARCH_PASS', null)
            ],
        ],
        'retries' => 2,
        /*
        * 证书路径
        */
        'cert' => ''
    ],
    /*
    * Handler 的设置
    */
    'options' => [
        'index' => 'monolog', // Elastic index name
        'type' => '_doc', // Elastic document type
        'ignore_error' => false, // Suppress Elasticsearch exceptions
    ],

    /*
    * 对于异常日志是否记录追踪详情
    */
    'exception' => [
        'trace' => false,
    ],
    /*
    * 扩展属性,你可以用于 Elasticsearch Index,extra 数组里的 Key 都是可以自定义的,我这里只是举例
    */
    'extra' => [
        'host' => 'example.com',
        'php' => '7.3.5',
        'laravel' => '6.5.2'
    ]
];

Kibana

好了现在可以尽情的享受写 CURD 的乐趣了!

如果你对我的文章感兴趣可以关注我的订阅号

本作品采用《CC 协议》,转载必须注明作者和本文链接
附言 1  ·  6年前

Laravel 版本大于 5.6.* 小于 6.0 的,请使用 betterde/logger 1.8.0

Laravel 版本大于或等于 6.0 的 请使用 betterde/logger 2.0.0

本帖由系统于 6年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 11
Complicated

有laravel版本要求吗?

6年前 评论
Image GeorgeKing (楼主) 6年前
Image Complicated (作者) 6年前
Image GeorgeKing (楼主) 6年前
Image GeorgeKing (楼主) 6年前
Image Complicated (作者) 6年前
Image GeorgeKing (楼主) 6年前

文本存储一份,感觉比较安心 :joy:

6年前 评论
Image GeorgeKing (楼主) 6年前

感谢楼主分享,我现在用的直接是官方推荐的 papertrailapp 的收费服务

6年前 评论
mouyong

laravel 5.6 完全用不了

6年前 评论
Image GeorgeKing (楼主) 6年前
Image mouyong (作者) 6年前

楼主这个方法是不是也可以结合阿里云的日志服务?

6年前 评论
Image GeorgeKing (楼主) 6年前
mouyong

@Jinrenjie 一个不小心发了好多 issue :joy:

6年前 评论
Image GeorgeKing (楼主) 6年前
Senkorl

爱了爱了

6年前 评论

我在github发了个issue,麻烦看看。

5年前 评论
Image GeorgeKing (楼主) 5年前

是的,如果elasticSearch崩了,企码我还有按天保存的日志文件可以查看。

我测试了下,如果直接使用原来的elasticsearch/elasticsearch组件,可以多渠道生成每天日志,但缺点就是一旦elasticSearch服务宕机,整个Laravel网站运行都会受影响,这点已做过测试。 而楼主你写的组件,唯一问题就是本地日志生成这个bug,但是elasticSearch服务宕机也不会影响Laravel网站运行。

5年前 评论
Image GeorgeKing (楼主) 5年前
Image GeorgeKing (楼主) 5年前

请教一下大佬,为啥我用这个组件一直都在往es里面写日志 没有脚本,也没有请求 但是每秒都在写日志,能解释一些这个诡异的现象

5年前 评论
Image GeorgeKing (楼主) 5年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!