First of all, we stated that we only want to send mails for errors, so in the first lines, we check whether the attempted log is of that level and return otherwise. In other words, we don't do anything if we're not dealing with an error and rely on other registered loggers for those.
Next, we determine who we want the email to be sent to and the langcode to send it in (both are mandatory arguments to the mail manager's mail() method). We opt to use the site-wide email address (just as we did for the From value). We also use the same configuration object as we used earlier in the hook_mail() implementation. In the next code snippet, we will take care of injecting the config factory into the class.
When we talk about langcode, we refer to the machine name of a language object. In this case, that is what is being stored for the site-wide default language. Also, we'll default to that for our emails. In a later chapter, we will cover more aspects regarding internationalization and multilingual in Drupal 8.
Then, we prepare the message that is being sent out. For this, we use the FormattableMarkup class to which we pass the message string and an array of variable values that can be used to replace the placeholders in our message. We can retrieve these values using the LogMessageParser service the same way as the DbLog logger does. With this, we are basically extracting the placeholder variables from the entire context array of the logged message.
Lastly, we will use the mail manager plugin to send the email. The first parameter to its mail() method is the module we want to use for the mailing. The second is the key (or template) we want to use for it (which we defined in hook_mail()). The third and fourth are self-explanatory, whereas the fifth is the $params array we encountered in hook_mail(). If you look back on that, you'll note that we used the message key as the body. Here, we populate that key with our markup object, which has a _toString() method that renders it with all the placeholders replaced.
You may wonder why I did not inject the Drupal mail manager as I did in the rest of the dependencies. Unfortunately, the core mail manager uses the logger channel factory itself, which in turn depends on our MailLogger service. So if we make the mail manager a dependency of the latter, we find ourselves in a circular loop. So when the container gets rebuilt, a big fat error is thrown. It might still work, but it's not alright. So, I opted to use it statically, because, in any case, this method is very small and would be difficult to test due to its expected result being difficult to assert (it sends an email). Sometimes, you have to make these choices, as the alternative would have been to inject the entire service container just to trick it. However, that is a code smell and would not have helped anyway had I wanted to write a test for this class.
Even if I did not inject the mail manager, I did inject the rest. So, let's take a look at what we have now at the top:
useDrupal\Core\Logger\LogMessageParserInterface; use Drupal\Core\Config\ConfigFactoryInterface;
There seems nothing out of the ordinary. We just take in the services I mentioned above. Also, let's quickly see the service definition of our mail logger as it looks now:
We simply have two new arguments--nothing new to you by now.
Clearing the caches and logging an error should send the logged message (with the placeholders replaced) to the site email address (and from the same address) using the PHP native mail() function. Congratulations! You just sent out your first email programmatically in Drupal 8.