How to bootstrap a log library so that it is available everywhere?

I would like to use a log library like e.g laminas-log or monolog within LaminasMvc and my question is how should I initiate it so that it is available in all models, controllers and views.

I have an idea how to do it if it would be only in controllers but not how to make them automatically available in models and views as well. (It would be great if this would be covered as well in one of the advanced tutorials as well.)

Provide a factory and alias it to the Psr\Log\LoggerInterface. Then simply inject it into the service that needs it.

and/or

You can also provide a delegator factory so you can quickly add it to a service without needing to change the current service factory. The Psr package ships with a LoggerAwareTrait and LoggerAwareInterface that can be used for that exact purpose. Your delegator factory would check that the service instance is an instance of LoggerAwareInterface and then would simply call $instance->setLogger($container->get(LoggerInterface::class));

This way, you are only incurring the factory overhead for service instances you are actually adding the logger to instead of all created instances if you were to use an initializer. Initializers check EVERY service that is created. Delegation only happens for service for which there is mapping in the config.

You could also wrap the logger in a view helper to provide access, or if you need it in an existing helper you can use the delegator on the ViewHelperPluginManager. In other words you would setup the delegation in the config for the View helper plugin manager, not under the ā€˜service_managerā€™ key if you are trying to target helpers for delegation.

@Tyrsson,

Thatā€™s what I would have suggested as well (although I was not aware of the Psr\Log\LoggerInterface, I should spend more time looking at Psrā€™s specsā€¦).

I think an example would be useful for people to understand the trait, interface and the use of delegators factories.

Unless there is a tutorial somewhere that we can link to (thatā€™s giving me ideasā€¦) or alternatively, a recipe in the Laminas Service Manager documentation.

1 Like

Thanks @Tyrsson !

So there is one solution for the view (ViewHelper) and one for the rest (Delegator Factory). Then I will go in this direction.

And I agree with @visto9259 and example would be helpful. I will see how far I will get. If I can get it running then I will provide an example here.

@Tyrsson I just check the documentation and it seems that delegator factory is just available in Mezzio. So I need to do it as you explained in the first sentence.

@modir
Delegator factories are a Service Manager functionality, not related directly to Mezzio or MVC

Thanks @visto9259 The first result I got from google was the Mezzio page and interpreted the text there in a different way. I found now the correct page: Delegators - laminas-servicemanager - Laminas Docs

laminas-log has also documentation on implementing PSR-3 and provides an abstract factory to inject a logger in PSR-3 aware services.

1 Like

Itā€™s simpler to use monolog honestly. I have some example code that you can check out if you need.

If I may ask @modir what exactly are you trying to log? It may be simpler to just inject the logger into a listener and log exceptions etc from the provided events within the Mvc workflow.

It really depends on what you are trying to log.

I guess your code goes in this direction? Service Manager Integration - laminas-log - Laminas Docs with the PsrLoggerAbstractAdapterFactory?

@Tyrsson Sure you can ask. We have a software which communicates with APIs from UberEats and JustEat. Sometimes we have to make an API call to them sometimes they call a web hook on our side. Very often when a web hook is called we make changes on the data in the database. At the moment we have many error_log() messages in order to find out where something breaks. Because the problem could be on our side or e.g. on Uberā€™s side. And for this part of the code I would like to have a better solution than error_log().

1 Like

Now, this code is for mezzio but you just need the factories and maybe the processor. As you probably know the only different really is that the config for mezzio is provided by a ConfigProvider and in laminas its from a config file usually. But this is how I factoried Monolog for Mezzio. You can do the same in laminas mvc. Just check the getDependencies() method. Thats where you factories are registered. Ignore the middleware since they are not relevant.

(this is just development code but should get ya started)

1 Like

Well, you might consider wrapping the logger in a listener aggregate. Provide an event to pass the error/exception and just tie into the event manager aware functionality that is already running in the Mvc. For a laminas db layer it already supports events so you would have access to the event manager. For other services just add the correct interface and trait and the em initializer will auto inject it for you. As long as itā€™s not managed by a plug-in manager. You could then Just trigger you log event and set the channel which you could use to filter the messages.

Just thought I would mention it since that initializer is already running in the Mvc.

1 Like

The short answer for this is pretty straight forward. Some of the Psrā€™s and many of the laminas/mezzio packages use what I think of as a service ā€œawareā€ pattern. It simply introduces an interface and a paired trait. The interface can be targeted via instanceof without needing to branch on a method_exist check. Which allows initializers or preferrably a delegator to target an ā€œawareā€ service instance for injection simplifying decoration at the factory layer of the application.

Not as to say that you cant duck type an instance and perform a method_exist check, at which time you would only need to use the trait and not the interface. I would argue against this though since the interface is what should be used to create the contract which can be typed.

I hope I explained that well enough.

1 Like

This is clearly more dirty than all other solutions suggested until then. But I thought Iā€™d add a more pragmatic alternative.

If you are willing to make an exception for the log service to be global (:exploding_head:), for the sake of ease of use from anywhere, then you can configure a factory as usual for the logger service, and then have a single global function similar to this:


function _log(): LoggerInterface
{
    global $container;

    return $container->get(LoggerInterface::class);
}

Of course, this comes with limitations. One of them is that itā€™s probably a very bad idea in a Swoole / ReactPHP type of environnement. So make an informed decision, according to the tradeoff you are willing to make in your specific project.

1 Like

Ewwww ā€¦ that 's really dirrrty (as you said already). Sure ā€¦ it works for the moment. But ā€¦ dude! That 's strictly on of those things in PHP you shouldn 't do anymore.

For those who are actually thinking about implementing this approach: DON 'T DO IT!
Using dependency injection and a factory is the way to go. If you really need it automatically in everything everywhere ā€¦ use an abstract factory. Laminas serves everything you need for it.

1 Like