本次环境介绍:

  • Laravel5.4
  • Mongodb3.2.16

上次说过我想实现项目日志监控和搜索系统,通过MongoDB+Elasticsearch做数据同步来实现,上篇文章已经在架构和配置层面实现了,这里介绍Laravel项目中如何将Log从默认的文件存储改为Mongodb存储。

安装jenssegers/laravel-mongodb

[root@lnmp laravel-project]# composer require jenssegers/mongodb -vvv

Problem 1
    - jenssegers/mongodb v3.2.0 requires mongodb/mongodb ^1.0.0 -> satisfiable by mongodb/mongodb[1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.1.0, 1.1.1, 1.1.2].
    - jenssegers/mongodb v3.2.1 requires mongodb/mongodb ^1.0.0 -> satisfiable by mongodb/mongodb[1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.1.0, 1.1.1, 1.1.2].
    - jenssegers/mongodb v3.2.2 requires mongodb/mongodb ^1.0.0 -> satisfiable by mongodb/mongodb[1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.1.0, 1.1.1, 1.1.2].
    - mongodb/mongodb 1.1.2 requires ext-mongodb ^1.2.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.1.1 requires ext-mongodb ^1.2.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.1.0 requires ext-mongodb ^1.2.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.0.5 requires ext-mongodb ^1.1.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.0.4 requires ext-mongodb ^1.1.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.0.3 requires ext-mongodb ^1.1.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.0.2 requires ext-mongodb ^1.1.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.0.1 requires ext-mongodb ^1.1.0 -> the requested PHP extension mongodb is missing from your system.
    - mongodb/mongodb 1.0.0 requires ext-mongodb ^1.1.0 -> the requested PHP extension mongodb is missing from your system.
    - Installation request for jenssegers/mongodb ^3.2 -> satisfiable by jenssegers/mongodb[v3.2.0, v3.2.1, v3.2.2].

  To enable extensions, verify that they are enabled in your .ini files:
    - /etc/php.ini
  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.

提示我们需要Mongodb扩展,而且提供了可选的版本,我这里就挑选安装mongodb1.1.2吧,我们从pecl下载即可,注意,我现在安装的是Mongodb扩展,而不是mongo扩展!链接:https://pecl.php.net/package/mongodb

安装Mongodb扩展

[root@lnmp laravel-project]# cd /var/soft
[root@lnmp laravel-project]# wget https://pecl.php.net/get/mongodb-1.1.2.tgz
[root@lnmp soft]# tar xf mongodb-1.1.2.tgz 
[root@lnmp soft]# cd mongodb-1.1.2
[root@lnmp mongodb-1.1.2]# phpize
Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226
[root@lnmp mongodb-1.1.2]# ./configure --with-php-config=/usr/local/php/bin/php-config
[root@lnmp mongodb-1.1.2]# make && make install
// 省略...
Installing shared extensions:     /usr/local/php/lib/php/extensions/no-debug-non-zts-20131226/
[root@lnmp mongodb-1.1.2]# ll /usr/local/php/lib/php/extensions/no-debug-non-zts-20131226
-rwxr-xr-x 1 root root 2495692 Sep 14 20:28 mongodb.so

接下来编辑/etc/php.ini,添加如下一行:

[root@lnmp mongodb-1.1.2]# extension=mongodb.so

接着重启php-fpm,运行:

[root@lnmp mongodb-1.1.2]# service php-fpm restart

现在,重新切换到你的laravel项目目录,再次运行安装命令:

[root@lnmp laravel-project]# composer require jenssegers/mongodb -vvv

OK!现在安装一切正常,我们成功的安装了jenssegers/mongodb包!

编辑Laravel项目的config/app.php,在键名为”providers”的数组中添加:

Jenssegers\Mongodb\MongodbServiceProvider::class,

创建测试路由

1. 创建一个logging的路由,指到HomeController下的logging方法,作为我们的测试路由。当然,如果HomeController和logging方法如果不存在,你需要自己新建这样的文件,打开routes/web.php,加入如下行:

Route::get('logging', 'HomeController@logging');

2. HomeController内容如下:

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    public function logging()
    {
        echo 'logging测试!';
    }
}

3. 浏览器访问测试,是否能正常看到我们的输出内容!

MongoDB配置加入.env

编辑.env文件,加入如下内容:

# Mongodb连接
MONGO_DB_HOST=192.168.2.116
MONGO_DB_PORT=27017
MONGO_DB_DATABASE=laravel_project_db
MONGO_DB_USERNAME=
MONGO_DB_PASSWORD=

此时默认db操作还是Mysql,我们只是新增了一个mongodb的配置而已!不过要注意,如果单独在一个机器上部署的mongodb,我就是这样的,所以你在启动mongodb时要特别注意,要使用能访问的ip,而不是默认的监听127.0.0.1,否则无法支持其它机器连接!

MongDB配置加入config/database.php

编辑config/database.php,在connections数组中,添加一个新的mongodb driver:

'mongodb' => [
    'driver'   => 'mongodb',
    // 我的mongodb在另一台linux虚拟机上,所以我填的并不是localhost
    'host'     => env('MONGO_DB_HOST', '192.168.2.116'),
    'port'     => env('MONGO_DB_PORT', 27017),
    'database' => env('MONGO_DB_DATABASE', 'laravel_project_db'),
    //'username' => env('DB_USERNAME'),
    //'password' => env('DB_PASSWORD'),
    'options'  => [
        'database' => 'laravel_project_db'
    ]
],

测试laravel-mongodb是否正常

回到我们的HomeController中,在logging方法中测试,HomeController内容如下:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class HomeController extends Controller
{
    public function logging()
    {
        $status = DB::connection('mongodb');
        var_dump($status);
    }
}

浏览器输出了如下内容:

object(Jenssegers\Mongodb\Connection)#220 (18) {
  ["db":protected]=>
  object(MongoDB\Database)#228 (6) {
    ["databaseName"]=>
    string(18) "laravel_project_db"
    ["manager"]=>
    object(MongoDB\Driver\Manager)#227 (3) {
      ["request_id"]=>
      int(1804289383)
      ["uri"]=>
      string(48) "mongodb://192.168.2.116:27017/laravel_project_db"
      ["cluster"]=>
      array(1) {
        [0]=>
        array(11) {
          ["host"]=>
          string(13) "192.168.2.116"
          ["port"]=>
          int(27017)
          // 省略...

到此,说明我们包的安装,配置已经准备完毕,现在laravel-mongodb能正常工作!接下来我们将会进入正题!

Laravel修改默认的Log存储方式为MongoDB

方式一:

让我们来看看文档的一段描述:

Custom Monolog Configuration

If you would like to have complete control over how Monolog is configured for your application, you may use the application's configureMonologUsing method. You should place a call to this method in your bootstrap/app.php file right before the $app variable is returned by the file:

$app->configureMonologUsing(function ($monolog) {
    $monolog->pushHandler(...);
});

return $app;

它让我们在bootstrap/app.php的return $app之前,加入那个代码块,好的,我根据手册以及借助google,发现最终代码块如下:

// 前面的省略...

// 我指定了数据库和collection
// laravel_project_db:Mongodb的数据库名称
// logs:数据库下的collection名称
$app->configureMonologUsing(function ($monolog) {
    $mongoHandler = new \Monolog\Handler\MongoDBHandler(
        new MongoClient('mongodb://192.168.2.116:27017'), 'laravel_project_db', 'logs'
    );
    
    $monolog->pushHandler($mongoHandler);
});

return $app;

可能会问,我们在.env已经配置了mongodb,这里为什么再写一次,可以用env之类的函数直接取值吗,很遗憾,在bootstrap/app.php这个文件中,无法使用helper之类的函数!

方式二(推荐)

编辑app/providers/AppServiceProvider.php文件,如下:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Log;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $monolog = Log::getMonolog();
        $mongoHost = env('MONGO_DB_HOST');
        $mongoPort = env('MONGO_DB_PORT');
        $mongoDsn = 'mongodb://' . $mongoHost . ':' . $mongoPort;
        $mongoHandler = new \Monolog\Handler\MongoDBHandler(new \MongoClient($mongoDsn), 'laravel_project_db', 'logs');
        $monolog->pushHandler($mongoHandler);
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

你会发现,方式二由于是ServiceProvider中,所以可以访问到配置项,这样更加灵活!如果你不想直接修改AppServiceProvider文件,你可以新建一个自定义的ServiceProvider文件,然后在config/app.php中的providers数组中,引入你的自定义Provider即可!

打开vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php这个文件,它的内容大致如下:

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;

/**
 * Logs to a MongoDB database.
 *
 * usage example:
 *
 *   $log = new Logger('application');
 *   $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod");
 *   $log->pushHandler($mongodb);
 *
 * @author Thomas Tourlourat <thomas@tourlourat.com>
 */
class MongoDBHandler extends AbstractProcessingHandler
{
    protected $mongoCollection;

    public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
    {
        if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) {
            throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required');
        }

        $this->mongoCollection = $mongo->selectCollection($database, $collection);

        parent::__construct($level, $bubble);
    }
    // 省略...
}

它的构造方法允许三个类型的mongo类扩展,否则会报错!我有想过在这个文件最上方去引入一些帮助类,使它能够使用自带的env之类的函数,但是这样就感觉破坏了这个文件一般,可能我有点强迫症,好吧!这里就先这样写了,我们怎么去让PHP有MongoClient这个类呢,答案是:装扩展!

为PHP安装Mongo扩展

看看官方对该类的描述,建议我们使用MongoDB\Driver\Manage:

The MongoClient class

This extension that defines this class is deprecated. Instead, the MongoDB extension should be used. Alternatives to this class include: MongoDB\Driver\Manager

然而jenssegers的包并不支持它!所以我们只能选择安装不被推荐的MongoClient了!

下载mongo1.6.0

[root@lnmp mongodb-1.1.2]# cd /var/soft
[root@lnmp soft]# wget https://pecl.php.net/get/mongo-1.6.0.tgz
[root@lnmp soft]# tar xf mongo-1.6.0.tgz 
[root@lnmp soft]# cd mongo-1.6.0
[root@lnmp mongo-1.6.0]# phpize
Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226
[root@lnmp mongo-1.6.0]# ./configure --with-php-config=/usr/local/php/bin/php-config 
[root@lnmp mongo-1.6.0]# make && make install

编辑/etc/php.ini,加入一行:

extension=mongo.so

然后,重启php-fpm:

[root@lnmp mongo-1.6.0]# service php-fpm restart

最后查看phpinfo:

好的,我们的Mongo客户端扩展已经安装并加载完成!

回到我们的Laravel框架

我们增加一个新的路由:

Route::get('storeLog', 'HomeController@storeLog');

添加新的storeLog方法并修改之前的logging方法,HomeController如下:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Log;

class HomeController extends Controller
{
    public function logging()
    {
        // 我取了所有logs这个collection(相当于Mysql的表)中的所有日志
        $logs = DB::connection('mongodb')->collection('logs')->get()->toArray();
        var_dump($logs);
    }
    
    public function storeLog()
    {
        // 使用默认的Log facade去保存日志,现在它会自动写入到Mongodb
        Log::info(md5(uniqid()));
    }
}

测试写入和查询:

先访问storeLog,然后在访问logging,如果我们之前没有安装MongoClient,在访问logging时,网页会无法响应的,这里要注意你上面的Mongo扩展成功安装了!最终我访问了storeLog两次,然后访问logging结果如下:

array(2) {
  [0]=>
  array(8) {
    ["_id"]=>
    object(MongoDB\BSON\ObjectID)#243 (1) {
      ["oid"]=>
      string(24) "59bc7076dc2a9a640f8b4567"
    }
    ["message"]=>
    string(32) "51f34708318c93485010ad65f8d84db5"
    ["context"]=>
    array(0) {
    }
    ["level"]=>
    int(200)
    ["level_name"]=>
    string(4) "INFO"
    ["channel"]=>
    string(5) "local"
    ["datetime"]=>
    string(19) "2017-09-16 00:29:42"
    ["extra"]=>
    array(0) {
    }
  }
  [1]=>
  array(8) {
    ["_id"]=>
    object(MongoDB\BSON\ObjectID)#241 (1) {
      ["oid"]=>
      string(24) "59bc7085dc2a9a5e0f8b4567"
    }
    ["message"]=>
    string(32) "988d2b9bba91012b622ae652a1030a77"
    ["context"]=>
    array(0) {
    }
    ["level"]=>
    int(200)
    ["level_name"]=>
    string(4) "INFO"
    ["channel"]=>
    string(5) "local"
    ["datetime"]=>
    string(19) "2017-09-16 00:29:57"
    ["extra"]=>
    array(0) {
    }
  }
}