laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.43k stars 10.99k forks source link

Trying to create custom monolog log channel Laravel 5.6 #25170

Closed Tarasovych closed 6 years ago

Tarasovych commented 6 years ago

Description:

My config/logging.php:

    'channels' => [
            'stack' => [
                'driver' => 'stack',
                'channels' => ['single', 'mongo'],
            ],
            ...
            'mongo' => [
                'driver' => 'monolog',
                'handler' => \Monolog\Handler\MongoDBHandler::class,
                'handler_with' => [
                    'mongo' => new MongoDB\Client(),
                    'database' => 'logs',
                    'collection' => 'test'
                ]
            ]
        ],

My .env: LOG_CHANNEL=stack.

I'm sure that MongoDB database logs exists, test collection too.

I'm trying to execute php artisan config:cache, I get an error:

Expected $document to have type "array or object" but found "string"

Full trace: Expected $document to have type "array or object" but found "string" {"exception":"[object] (MongoDB\Exception\InvalidArgumentException(code: 0): Expected $document to have type \"array or object\" but found \"string\" at path_to_my_project\vendor\mongodb\mongodb\src\Exception\InvalidArgumentException.php:32) [stacktrace]

0 path_to_my_project\vendor\mongodb\mongodb\src\Operation\InsertOne.php(70): MongoDB\Exception\InvalidArgumentException::invalidType('$document', '[2018-08-09 22:...', 'array or object')

1 path_to_my_project\vendor\mongodb\mongodb\src\Collection.php(889): MongoDB\Operation\InsertOne->__construct('logs', 'prod', '[2018-08-09 22:...', Array)

2 path_to_my_project\vendor\monolog\monolog\src\Monolog\Handler\MongoDBHandler.php(46): MongoDB\Collection->insertOne('[2018-08-09 22:...')

3 path_to_my_project\vendor\monolog\monolog\src\Monolog\Handler\AbstractProcessingHandler.php(37): Monolog\Handler\MongoDBHandler->write(Array)

4 path_to_my_project\vendor\monolog\monolog\src\Monolog\Logger.php(337): Monolog\Handler\AbstractProcessingHandler->handle(Array)

5 path_to_my_project\vendor\monolog\monolog\src\Monolog\Logger.php(616): Monolog\Logger->addRecord(400, 'Your configurat...', Array)

6 path_to_my_project\vendor\laravel\framework\src\Illuminate\Log\Logger.php(176): Monolog\Logger->error('Your configurat...', Array)

7 path_to_my_project\vendor\laravel\framework\src\Illuminate\Log\Logger.php(87): Illuminate\Log\Logger->writeLog('error', 'Your configurat...', Array)

8 path_to_my_project\vendor\laravel\framework\src\Illuminate\Log\LogManager.php(526): Illuminate\Log\Logger->error('Your configurat...', Array)

9 path_to_my_project\vendor\laravel\framework\src\Illuminate\Foundation\Exceptions\Handler.php(113): Illuminate\Log\LogManager->error('Your configurat...', Array)

10 path_to_my_project\app\Exceptions\Handler.php(41): Illuminate\Foundation\Exceptions\Handler->report(Object(LogicException))

11 path_to_my_project\vendor\

unomaduro\collision\src\Adapters\Laravel\ExceptionHandler.php(60): App\Exceptions\Handler->report(Object(LogicException))

12 path_to_my_project\vendor\laravel\framework\src\Illuminate\Foundation\Console\Kernel.php(353): NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler->report(Object(LogicException))

13 path_to_my_project\vendor\laravel\framework\src\Illuminate\Foundation\Console\Kernel.php(124): Illuminate\Foundation\Console\Kernel->reportException(Object(LogicException))

14 path_to_my_project\artisan(37): Illuminate\Foundation\Console\Kernel->handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

15 {main}

"}

What's wrong? I guess, my config was amde accordingly to documentation.

What I've tried so far:

'mongo' => [
                'driver' => 'monolog',
                'handler' => \Monolog\Handler\MongoDBHandler::class,
            ]

same error.

'mongo' => [
        'driver' => 'monolog',
        'handler' => new \Monolog\Handler\MongoDBHandler(new MongoDB\Client(),'logs', 'test'),
    ]

LogicException : Your configuration files are not serializable.

mfn commented 6 years ago

2 path_to_my_project\vendor\monolog\monolog\src\Monolog\Handler\MongoDBHandler.php(46): MongoDB\Collection->insertOne('[2018-08-09 22:...')

I believe here we see the problem: a string is passed which looks like from the default line formatter, but insertOne expects either an array or an string.

I don't see a formatter in your configuration, please see if this works:

  'formatter' => \Monolog\Formatter\MongoDBFormatter::class,
Tarasovych commented 6 years ago

@mfn thank you! Didn't know that formatter is required. I'm done with clearing config cache.

But now I'm trying to log something and I get

Class 'MongoDate' not found in .../vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php on line 103

Tarasovych commented 6 years ago

phpinfo():

MongoDB support => enabled
MongoDB extension version => 1.5.0
MongoDB extension stability => stable
libbson bundled version => 1.11.0
libmongoc bundled version => 1.11.0
libmongoc SSL => enabled
libmongoc SSL library => OpenSSL
libmongoc crypto => enabled
libmongoc crypto library => libcrypto
libmongoc crypto system profile => disabled
libmongoc SASL => enabled
libmongoc ICU => disabled
libmongoc compression => disabled
mfn commented 6 years ago

It seems that \MongoDate is something outdated/older, define by the "PECL mongo" extension, see the warning on http://php.net/manual/en/class.mongodate.php

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

The referenced class OTOH MongoDB\BSON\UTCDateTime is from the "mongodb" extension you're using => http://php.net/manual/en/class.mongodb-bson-utcdatetime.php

It seems that \Monolog\Handler\MongoDBHandler::__construct is prepared to handle different mongodb clients, but \Monolog\Formatter\MongoDBFormatter only works with "PECL mongo".

For you to move forward, my advice is to simply copy that MongoDBFormatter into your application and adapt it.

I found your Monolog issue that you created about this problem, so maybe you can figure out what's with the formatter and maybe contribute back a version working with both clients.

mfn commented 6 years ago

Oh, I just found out: Monologs master has an updated version: https://github.com/Seldaek/monolog/blob/master/src/Monolog/Formatter/MongoDBFormatter.php

But it's a different version in the latest release (1.23.0).

Monologs last release was >1 year ago and a lot has happened since then. I guess the 1.x version is maintenance only (it contains the version you're using, see https://github.com/Seldaek/monolog/blob/1.x/src/Monolog/Formatter/MongoDBFormatter.php ) and at somepoint there will probably a 2.x release with the version you need.

In this case: see if you can directly copy over that version into your project and work with that.

Tarasovych commented 6 years ago

@mfn thank you a lot! So I

And it worked!

Tarasovych commented 6 years ago

Maybe it's reasonable to have newer version inside https://github.com/laravel/framework/blob/5.6/composer.json#L25

mfn commented 6 years ago

@Tarasovych there is no newer release of Monolog with what you need, so there's nothing to do for Laravel.

Please close the issue.

Tarasovych commented 6 years ago

I got one more error connected with this. So my logging.php:

        'mongo' => [
            'driver' => 'monolog',
            'handler' => \Monolog\Handler\MongoDBHandler::class,
            'handler_with' => [
                'mongo' => new MongoDB\Client(), // MongoDB\Client()::class doesn't work
                'database' => 'logs',
                'collection' => 'prod'
            ],
            'formatter' => App\MongoDBFormatter::class
        ]

I can't execute php artisan config:cache. It returns

LogicException : Your configuration files are not serializable.

Exception trace:

  1   Error::("Call to undefined method MongoDB\Driver\Manager::__set_state()")
      ...\bootstrap\cache\config.php:445

  2   require()
      ...\vendor\laravel\framework\src\Illuminate\Foundation\Console\ConfigCacheCommand.php:64

I can run php artisan config:clear, and I can log in MongoDB by the way.

P. S. I've also tried


                'mongo' => [
                    'manager' => MongoDB\Driver\Manager::class,
                    'uri' => 'mongodb://localhost:27017',
                    'typeMap' => [
                        'array' => MongoDB\Model\BSONArray::class,
                        'document' => MongoDB\Model\BSONDocument::class,
                        'root' => MongoDB\Model\BSONDocument::class
                    ],
                    'writeConcern' => MongoDB\Driver\WriteConcern::class
                ],

without any success.

I understand why I got "not serializable", but that config works... What to do now?

mfn commented 6 years ago

Instead of

            'mongo' => new MongoDB\Client(), // MongoDB\Client()::class doesn't work

did you try

            'mongo' => new MongoDB\Client::class,

(no parenthesis)

Tarasovych commented 6 years ago

@mfn IDE doesn't allow me to do that)

syntax error, unexpected 'class' (T_CLASS), expecting variable (T_VARIABLE) or '$'

mfn commented 6 years ago

Sorry, typo:

 'mongo' => MongoDB\Client::class,

(no new)

Tarasovych commented 6 years ago

@mfn I tried that, doesn't work.

P. S. I meant, there is no problem with config serialization, but nothing logs into mongo.

Tarasovych commented 6 years ago

I've also tried to call different methods from MongoDB\Client. Like 'mongo' => (string)(new MongoDB\Client()->...) in hope it will work, but no success. If I know what actually mongo might be for success logging - connection URL or something other.

mfn commented 6 years ago

I think I now understand the issue better.

You're creating driver=monolog which calls \Illuminate\Log\LogManager::createMonologDriver and creates a MongoDBHandler instance and and passes handler_with as its arguments.

The problem is: the arguments are passed literally, that's why the first argument, which is only class string reference to MongoDB\Client, is passed literally to the MongoDBHandler => there's nothing "in between" which understands this needs to be an actual instance of MongoDB\Client (that's why in your examples it works with new in the config but has the other issue with caching the config).

I think what you want to achieve is not possible directly with the driver=monolog adapter ATM because of how MongoDBHandler expects its argument.

What you need is more flexibility and therefore I suggest you create a custom driver like this:

            'mongo' => [
                'driver' => 'custom',
                'via' => YourOwnMongodbAdapter::class,
                'name' => 'default',
                'database' => 'logs',
                'collection' => 'test',
            ],

(see also https://laravel.com/docs/5.6/logging#creating-channels-via-factories )

Then YourOwnMongodbAdapter::__invoke receives your $config array and you can create the Monolog driver the way you need it; untested but it should something similar to this:

<?php

use Monolog\Handler\MongoDBHandler;

class YourOwnMongodbAdapter
{
    public function __invoke(array $config)
    {
        $handler = new MongoDBHandler(
            new MongoDB\Client(),
            $config['database'],
            $config['collection']
        );

        $handler->setFormatter(new \App\MongoDBFormatter);

        return new Monolog\Logger(
            $config['name'],
            [$handler]
        );
    }
}
Tarasovych commented 6 years ago

@mfn thanks! Now I can execute php artisan config:cache without errors, but when logging, I got

Your configuration files are not serializable.

yet in Mongo. Update: ^ that was my miss, because of date format issue. So, it works!

Also I get 1970 year using UTCDateTime class for formatting data, but this is another question...

Tarasovych commented 6 years ago

So I went from

    protected function formatDate(\DateTime $value, $nestingLevel)
    {
        return new UTCDateTime($value->getTimestamp());
    }

to

    protected function formatDate(\DateTime $value, $nestingLevel)
    {
        return $value;
    }

and I have correct date.

Amirhb commented 6 years ago

you can also try this : https://github.com/Amirhb/laravel-mongodb-log tell me if you needed any update. doc: https://packagist.org/packages/amirhb/laravel-mongodb-log

ghost commented 1 year ago

You can register the mongo client in service provider and live the $mongodb value empty:

Laravel 9.x

// AppServiceProvider.php

// register function
$this->app->when(\Monolog\Handler\MongoDBHandler::class)
    ->needs('$mongodb')
    ->give(app(\MongoDB\Client::class));
// logging.php
'mongo' => [
    'driver' => 'monolog',
    'handler' => \Monolog\Handler\MongoDBHandler::class,
    'formatter' =>\Monolog\Formatter\MongoDBFormatter::class,
    'with' => [
        'database' => 'logs',
        // 'mongo' => new MongoDB\Client(), <-- Remove this key
        'collection' => 'prod',
    ],
]