If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeLet's add some logging to MarkdownHelper
. As always, we just need to find which type-hint to use. Run:
./bin/console debug:autowiring
And look for log. We've seen this before: LoggerInterface
. To get this in MarkdownHelper
, just add a third argument: LoggerInterface $logger
:
... lines 1 - 5 | |
use Psr\Log\LoggerInterface; | |
... lines 7 - 8 | |
class MarkdownHelper | |
{ | |
... lines 11 - 14 | |
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $logger) | |
{ | |
... lines 17 - 19 | |
} | |
... lines 21 - 35 | |
} |
Like before, we need to create a new property and set it below. Great news! PhpStorm has a shortcut for this! With your cursor on $logger
, press Alt
+Enter
, select "Initialize fields" and hit OK:
... lines 1 - 5 | |
use Psr\Log\LoggerInterface; | |
... lines 7 - 8 | |
class MarkdownHelper | |
{ | |
... lines 11 - 12 | |
private $logger; | |
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $logger) | |
{ | |
... lines 17 - 18 | |
$this->logger = $logger; | |
} | |
... lines 21 - 35 | |
} |
Awesome! Down in parse()
, if the source contains the word bacon
... then of course, we need to know about that! Use $this->logger->info('They are talking about bacon again!')
:
... lines 1 - 8 | |
class MarkdownHelper | |
{ | |
... lines 11 - 21 | |
public function parse(string $source): string | |
{ | |
if (stripos($source, 'bacon') !== false) { | |
$this->logger->info('They are talking about bacon again!'); | |
} | |
... lines 27 - 34 | |
} | |
} |
Ok, try it! This article does talk about bacon. Refresh! To see if it logged, open the profiler and go to "Logs". Yes! Here is our message. I love autowiring.
Go back to your terminal. The debug:autowiring
output say that LoggerInterface
is an alias to monolog.logger
. That is the id of the service that's being passed to us. Fun fact: you can get a bit more info about a service by running:
./bin/console debug:container monolog.logger
This is cool - but you could also learn a lot by dumping it. Anyways, we normally use debug:container
to list all of the services in the container. But we can also get a filtered list. Let's find all services that contain the word "log":
./bin/console debug:container --show-private log
There are about 6 services that I'm really interested in: these monolog.logger.
something services.
Here's what's going on. Symfony uses a library called Monolog for logging. And Monolog has a feature called channels, which are kind of like categories. Instead of having just one logger, you can have many loggers. Each has a unique name - called a channel - and each can do totally different things with their logs - like write them to different log files.
In the profiler, it even shows the channel. Apparently, the main logger uses a channel called app
. But other parts of Symfony are using other channels, like request
or event
. If you look in config/packages/dev/monolog.yaml
, you can see different behavior based on the channel:
monolog: | |
handlers: | |
main: | |
type: stream | |
path: "%kernel.logs_dir%/%kernel.environment%.log" | |
level: debug | |
channels: ["!event"] | |
# uncomment to get logging in your browser | |
# you may have to allow bigger header sizes in your Web server configuration | |
#firephp: | |
# type: firephp | |
# level: info | |
#chromephp: | |
# type: chromephp | |
# level: info | |
console: | |
type: console | |
process_psr_3_messages: false | |
channels: ["!event", "!doctrine", "!console"] |
For example, most logs are saved to a dev.log
file. But, thanks to this channels: ["!event"]
config, which means "not event", anything logged to the "event" logger is not saved to this file.
This is a really cool feature. But mostly... I'm telling you about this because it's a great example of a new problem: how could we access one of these other Logger objects? I mean, when we use the LoggerInterface
type-hint, it gives us the main logger. But what if we need a different Logger, like the "event" channel logger?
Actually, let's create our own new channel called markdown
. I want anything in this channel to log to a different file.
To do this, inside config/packages
, create a file: monolog.yaml
. Monolog is interesting: it doesn't normally have a main configuration file: it only has environment-specific config files for dev
and prod
. That makes sense: we log things in completely different ways based on the environment.
But we're going to add some config that will create a new channel, and we want that to exist in all environments. Add monolog
, then channels
set to [markdown]
:
monolog: | |
channels: ['markdown'] |
That's it!
Because of a Symfony bug - which, is now fixed (woo!) - but won't be available until the next version - Symfony 4.0.5 - we need to clear the cache manually when adding a new config file:
./bin/console cache:clear
As soon as that finishes, run debug:container
again:
./bin/console debug:container log
Yea! Suddenly we have a new logger service - monolog.logger.markdown
! So cool.
Go back to the "dev" monolog.yaml
file. Copy the first log handler, paste, and give it a key called markdown_logging
- that's just a meaningless internal name. Change the path to markdown.log
and only log the markdown
channel:
monolog: | |
handlers: | |
... lines 3 - 7 | |
markdown_logging: | |
type: stream | |
path: "%kernel.logs_dir%/markdown.log" | |
level: debug | |
channels: ["markdown"] | |
... lines 13 - 25 |
Ok! If you go to your browser now and refresh... it does work. But if you check the logs, we are - of course - still logging to the app
channel Logger. Yep, there's no markdown.log
file yet.
So how can we tell Symfony to not pass us the "main" logger, but instead to pass us the monolog.logger.markdown
service? This is our first case where autowiring doesn't work.
That's no problem: when autowiring doesn't do what you want, just... correct it! Open config/services.yaml
. Ignore all of the configuration on top for now. But notice that we're under a key called services
. Yep, this is where we configure how our services work. At the bottom, add App\Service\MarkdownHelper
, then below it, arguments
:
... lines 1 - 4 | |
services: | |
... lines 6 - 25 | |
App\Service\MarkdownHelper: | |
arguments: | |
... lines 28 - 32 |
The argument we want to configure is called $logger
. Use that here: $logger
. We are telling the container what value to pass to that argument. Use the service id: monolog.logger.markdown
. Paste!
... lines 1 - 4 | |
services: | |
... lines 6 - 25 | |
App\Service\MarkdownHelper: | |
arguments: | |
$logger: 'monolog.logger.markdown' | |
... lines 29 - 32 |
Find your browser and... try it! Bah! A big error:
Argument 3 passed to
MarkdownHelper::__construct()
must implementLoggerInterface
, string given.
Ah! It's totally legal to set an argument to a string value. But we don't want to pass the string monolog.logger.markdown
! We want to pass the service with this id!
To do that, use a special Symfony syntax: add an @
symbol:
... lines 1 - 4 | |
services: | |
... lines 6 - 25 | |
App\Service\MarkdownHelper: | |
arguments: | |
$logger: '@monolog.logger.markdown' | |
... lines 29 - 32 |
This tells Symfony not to pass us that string, but to pass us the service with that id.
Try it again! It works! Check out the var/log
directory... boom! We have a markdown.log
file!
Next, I'll show you an even cooler way to configure this. And we'll learn more about what all this config in services.yaml
does.
Hey Putera,
Yes, you did it right! Just specify whatever you want path explicitly as you did. So, probably you just need to clear the cache to see it in action. Try to clear the cache first.
Cheers!
I'm confused as to the addition in packages/monolog.yaml. Why was it added there? since we need to add a dev and prod config anyway. The other channels in the prod and dev monolog.yaml files are not declared at the top level and don't appear to be anywhere else either (or my grep is failing in vendor)
Hey Richard
When you declare a new channel at packages/monolog.yaml
what it does is to create a new logger service under the hood but you still have to configure it, maybe for the dev environment you may want to log everything coming from that channel into a file but in the production environment you may want to send the log to a Slack channel, so the config will be different. That's why the channels configuration is separated in environments but the definition of channels is kept in a single file. I hope it helps making things clearer
Cheers!
Hi, I am on symfony 5.4 but did not find any related video regarding my query.
I want to have a variable in .env.local file something like DEBUG_EMAILS_ENABLED = TRUE/FALSE
Then I want to use this variable in monolog.yaml to disable sending error emails
Something like
symfony_mailer:
enabled: '%env(DEBUG_EMAILS_ENABLED)%'
type: symfony_mailer
from_email: '%env(MAILER_FROM)%'
to_email: ['%env(SUPPORT_EMAIL)%']
subject: 'An Error Occurred! %%message%%'
level: notice
formatter: monolog.formatter.email_error_formatter
content_type: text/html
But there is no such option so how can I pass somehow an option that will disable this functionality? Would level: 1000000 work? So it will basically never send an email?
Yo @Peter-K!
Hmm. This is usually done, for logging, on an environment-by-environment basis. So, you have a set of Monolog config for dev that does NOT send error emails, and you have some config for prod that DOES send emails. I'm sure you've noticed your monolog.yaml
is likely already set up in this way: https://github.com/symfony/recipes/blob/4031f688ba05b714093cf39a5795e51d2af27a73/symfony/monolog-bundle/3.7/config/packages/monolog.yaml#L40
However, if you need an actual environment variable (so you could switch this functionality on/off at-will on production), then it might be a bit more complex.
Would level: 1000000 work? So it will basically never send an email?
That's an interesting idea. I assume you're using the symfony_mailer
monolog handler? Yea, you could try creating a MAILER_LOG_LEVEL
environment variable and setting that to, I think, anything greater than 600 - 600 is the highest built-in level. I have no idea if this will work, but it's worth a try as it would be super easy.
Let me know if it works!
Cheers!
Hello,
i'm using symfony 4.4.*. Why i cannot see the logs in the profiler? I made all the steps like you did, and this option is empty.
i used this command (shown below with the results)
λ php bin/console debug:container --tag=data_collector
Symfony Container Services Tagged with "data_collector" Tag
===========================================================
---------------------------- ---------------------------------------------- ------------- ---------- --------------------------------------------------------------------
Service ID template id priority Class name
---------------------------- ---------------------------------------------- ------------- ---------- --------------------------------------------------------------------
data_collector.request @WebProfiler/Collector/request.html.twig request 335 Symfony\Component\HttpKernel\DataCollector\RequestDataCollector
data_collector.time @WebProfiler/Collector/time.html.twig time 330 Symfony\Component\HttpKernel\DataCollector\TimeDataCollector
data_collector.memory @WebProfiler/Collector/memory.html.twig memory 325 Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector
data_collector.ajax @WebProfiler/Collector/ajax.html.twig ajax 315 Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector
data_collector.exception @WebProfiler/Collector/exception.html.twig exception 305 Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector
data_collector.logger @WebProfiler/Collector/logger.html.twig logger 300 Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector
data_collector.events @WebProfiler/Collector/events.html.twig events 290 Symfony\Component\HttpKernel\DataCollector\EventDataCollector
data_collector.router @WebProfiler/Collector/router.html.twig router 285 Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector
data_collector.cache @WebProfiler/Collector/cache.html.twig cache 275 Symfony\Component\Cache\DataCollector\CacheDataCollector
data_collector.twig @WebProfiler/Collector/twig.html.twig twig 257 Symfony\Bridge\Twig\DataCollector\TwigDataCollector
data_collector.http_client @WebProfiler/Collector/http_client.html.twig http_client 250 Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector
data_collector.dump @Debug/Profiler/dump.html.twig dump 240 Symfony\Component\HttpKernel\DataCollector\DumpDataCollector
data_collector.config @WebProfiler/Collector/config.html.twig config -255 Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector
---------------------------- ---------------------------------------------- ------------- ---------- --------------------------------------------------------------------```
So it seems it's working - the log is also appearing in the var/log/dev.log (the line with finding bacon)
How can i enable showing the logs in the profiler?
Hey Dzosef,
Hm, it should just work. Do you see "Logs" item in the left sidebar on this page: https://127.0.0.1:8000/_profiler/latest ? What if you go directly to this link: https://127.0.0.1:8000/_profiler/latest?panel=logger ? Do you see any errors or you can see log tabs? Btw, any missing installed packages? Please, run "composer install", clear the cache and try again. Does it help?
Cheers!
I am totally lost, I am new to PHP. So I am not sure what I need to master before doing this tutorial on Symfony? Thank you
Hey Travel,
We're sorry to hear this! unfortunately, if you're a newbie to PHP - yes, it might be difficult to you to understand some concepts, that's why I strongly recommend you to learn about PHP itself first. After you feel confident in PHP - try to learn Symfony again. When you don't know PHP programming language but trying to learn Symfony is the same as learn how to cook in Spanish but when you don't know Spanish at all. I hope you understand what I mean here.
Btw, I don't see your reply on my previous comment, so I suppose you haven't seen it yet. I gave you some good tips where to start first before you deal with Symfony framework, see another my comment here: https://symfonycasts.com/sc...
I hope this helps! And I believe that strategy will help you a lot in learning Symfony as a result.
P.S. But if you didn't get some concept we're talking about in this chapter - please, give us a bit more context of it, like asking more specific questions, and we would try to help you. Unfortunately, hearing "I am totally lost" does not say much to us, I don't know what exactly try to explain to you in different words to make you get it.
Cheers!
Hi all,
A quick question, I configured my custom logger as in the tutorial, but now the messages are logged in both files dev.log and markdown.log. Is it normal or there's something wrong?
Hey Wazir,
Yes, that's should be OK, because I see in this chapter the "main" handler log every channel except "event", so it looks like we just duplicate logs from markdown channel in markdown.log.
Cheers!
Great Work Men.
You passed custom markdown object in the service but how can we pass it in the controller?
Hey @Shakeel!
You want to get the MarkdownHelper service from in a controller? Just add it as an argument to your method/action - that's a super-power of controllers:
use App\Service\MarkdownHelper;
public function newProduct(MarkdownHelper $markdownHelper)
{
// and use it
}
We talk a bit about this in episode 1 of this series: https://symfonycasts.com/screencast/symfony/services#using-the-logger-service
Hope that helps!
Cheers!
Great work men.
You passed custom markdown logger in the services but how can we pass in the controller?
Hello,
In the Symfony Profiler (Logs section, under "Deprecations" tab), I have this message :
A tree builder without a root node is deprecated since Symfony 4.2 and will not be supported anymore in 5.0.
{
/var/www/html/perso/the_spacebar/vendor/symfony/config/Definition/Builder/TreeBuilder.php:30 {
› if (null === $name) {
› @trigger_error('A tree builder without a root node is deprecated since Symfony 4.2 and will not be supported anymore in 5.0.', E_USER_DEPRECATED);
› } else {
}
/var/www/html/perso/the_spacebar/vendor/knplabs/knp-markdown-bundle/DependencyInjection/Configuration.php:17 {▼
› {
› $treeBuilder = new TreeBuilder();
›
}
}
Hello ojtouch
It's normal that you see that deprecation message. You are using Symfony 4.2 and course code uses version 4.1. You can ignore this message, soon knplabs/knp-markdown-bundle will be updated and this message will fly away
Cheers!
I just updated the bundle! If you run "composer update knplabs/knp-markdown-bundle" it should go away!
Cheers!
I believe...as far as I can tell...that I've followed the lesson exactly. However, when I modify services.yaml as indicated, I get the following error:
"The definition for "App\Service\MarkdownHelper\" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error."
In PhpStorm, hovering over App\Service\MarkdownHelper\: in services.yaml gives the error: "Missing Class".
But the class is in MarkdownHelp.php in the Service directory, with namespace App\Service; at the top.
Any help would be greatly appreciated.
Hey Burke C.
Your MarkdownHelper class exists and lives inside "src/Service/MarkdownHelper.php" right? (Double check it's namespace)
Oh, probably you are getting that error because of that ending backslash in your service definition name "App\Service\MarkdownHelper\"
Cheers!
Hi there !
I wanted to know : how would you do when you need to use the entityManager inside your own service ?
Dependency Injection through the constructor doesn't seem to work (typehints issues for example). What whould be the best solution in order to be able to call findAll() on an entity in my service ?
Thanks, keep up the good work, love symfony 4 so far.
Would you say it's a good solution to inject ManagerRegistry into the service's constructor in order to getConnection() and getRepository() ?
Try to inject an Entity Manager to your service like this (in services.yaml):
App\Service\MyServiceName:
arguments:
- '@doctrine.orm.default_entity_manager'
And get $em as usual in your service constructor.
I haven't had the need of using directly the ManagerRegistry, if you want to make use of the connection, you can do it through the "EntityManager" as well.
Hey @elbarto
Injecting the "EntityManager" into your services is totally ok. You can do it by type hinting the interface "EntityManagerInterface"
> Dependency Injection through the constructor doesn't seem to work
Can you tell me what error do you see?
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.1.4
"symfony/asset": "^4.0", // v4.0.4
"symfony/console": "^4.0", // v4.0.14
"symfony/flex": "^1.0", // v1.17.6
"symfony/framework-bundle": "^4.0", // v4.0.14
"symfony/lts": "^4@dev", // dev-master
"symfony/twig-bundle": "^4.0", // v4.0.4
"symfony/web-server-bundle": "^4.0", // v4.0.4
"symfony/yaml": "^4.0" // v4.0.14
},
"require-dev": {
"easycorp/easy-log-handler": "^1.0.2", // v1.0.4
"symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
"symfony/dotenv": "^4.0", // v4.0.14
"symfony/maker-bundle": "^1.0", // v1.0.2
"symfony/monolog-bundle": "^3.0", // v3.1.2
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
"symfony/profiler-pack": "^1.0", // v1.0.3
"symfony/var-dumper": "^3.3|^4.0" // v4.0.4
}
}
Thanks for the tutorial,
btw is there a way to change the log location, I change the path to /var/www/logs/
but did not work, the log still stored in var/logs
I have created new channel to log all rest request :
Thanks