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  ·  1年前

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

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

本帖由系统于 1年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 11
Complicated

有laravel版本要求吗?

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

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

1年前 评论
GeorgeKing (楼主) 1年前

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

1年前 评论

laravel 5.6 完全用不了

1年前 评论
GeorgeKing (楼主) 1年前
蜗牛 (作者) 1年前

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

1年前 评论
GeorgeKing (楼主) 1年前

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

1年前 评论
GeorgeKing (楼主) 1年前
Senkorl

爱了爱了

1年前 评论

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

1年前 评论
GeorgeKing (楼主) 1年前

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

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

1年前 评论
GeorgeKing (楼主) 1年前
GeorgeKing (楼主) 1年前

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

11个月前 评论
GeorgeKing (楼主) 11个月前

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