Send Symfony application logs to Slack with Monolog

Published on 2019-03-14 • Modified on 2019-03-20

In this post we will see how to send logs to Slack. A typical usage is of course to send critical errors so you are warned in real time to be able to quickly fix the issues. But we can also send other types of notifications. Let's go! 😎

Configuration

As this code is directly used by this Symfony project you are sure it is up to date. If you use Symfony 3.4 (or 3.x) it should be OK, you will have to do small modifications to use the parameters.yml file instead of the .env file for parameters and you will have to declare the logger service. This blog post is using the following components:

  • Symfony 4.3.2
  • PHP 7.2

Slack API Setup

When I started to write this tutorial I still was using an old API Token. So let's do this the right way. The first thing is to create a Slack application.
Access this page (sign-in before) and hit the button. Fill out the form, choose your workspace and validate. Your Slack application is now created.
Now we have to give our slack application the right to post on a given channel. This can be done in the Features > OAuth & Permissions page. Go to the scope section, in the Select Permission Scopes select box, find the Post to specific channels in Slack (incoming webhook) item then click on the button.

PS: Be careful that if it's a private channel you want to publish too, you will have to add the chat:write:user scope.

To be able to use this API we need an OAuth token. To get it, click on the "Install app" link on the left menu. It looks like this: xoxp-1111111-22222222-33333333-aaaaaaaaaaaaaaaaaaaaaa.
In your .env file, add a SLACK_TOKEN parameter:

SLACK_TOKEN=xoxp-1111111-22222222-33333333-aaaaaaaaaaaaaaaaaaaaaa

In the .env.dist file also add this key. Here, I like to add the URL of the service where the token can be accessed or changed. This will save you time later when you will have forgotten how to access this token. Change the fake app id with you application one. It's a nine length string, it's in the URL of the page or you can get it in the Basic Information > Credential page, it is the APP ID parameter.

SLACK_TOKEN=https://api.slack.com/apps/ABCDEFGHI/install-on-team

Never commit an API key!

The Symfony setup

Now that the Slack API is correctly configured. Let's see how to use it in our Symfony application. First create a config/packages/monolog.yaml file. As logs environments are quite different in dev and prod, this file may not exists but we will use it to simplify the setup. Add the following configuration:

# config/packages/monolog.yaml
parameters:
    slack_token: '%env(SLACK_TOKEN)%'
    slack_bot_name: '@@slack'
    slack_channel: '#strangebuzz'

monolog:
    channels: ['strangebuzz'] # Change the channel name with your own one

We introduce several parameters, the token we've just created, we get it from the environment. The bot name that will be used to publish our messages and the channel on which messages will be published too. Below, we introduce a specific channel we will see later. Now that we defined these parameters, let's modify our prod monolog setup. Indeed, in dev we don't need this. Open your config/packages/prod/monolog.yaml file and add the two handlers after the # Slack ### section: (this file is the real file used by this website)

# config/packages/prod/monolog.yaml
monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: critical
            handler: nested
            excluded_404s: # regex: exclude all 404 errors from the logs
                - ^/
        nested:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
        console:
            type:   console
            process_psr_3_messages: false
            channels: ["!event", "!doctrine"]

        # Slack ################################################################

        # Critical errors only
        slack_errors:
            type:        slack
            token:       '%slack_token%'
            channel:     '%slack_channel%'
            bot_name:    '%slack_bot_name%'
            icon_emoji:  ':ghost:'
            level:         critical
            include_extra: true
            excluded_404s:
                - ^/

        # Application's messages
        slack:
            type:        slack
            token:       '%slack_token%'
            channel:     '%slack_channel%'
            bot_name:    '%slack_bot_name%'
            icon_emoji:  ":heavy_check_mark:"
            level:       debug
            channels:    ['strangebuzz']

As you can see we introduced two new handlers. The first one will be responsible to forward critical errors to Slack and the second one will allow us to send arbitrary messages. (notices or whatever you want). In both cases we use the parameters we introduced in the main monolog configuration file. Check out the full SlackHandler configuration.

Usage

Now that our configuration is OK, let's test this. As we modified the prod env only, we must use the prod env also locally in order to be able to test what we just added. So, in your .env file, let's switch to the prod env and don't forget to clear all the cache. Now, to test, in a controller, raise an exception, for example:

throw new \RuntimeException('Hello Ghost!');
A Symfony exception displayed in Slack

This is what you should see on your slack channel when there is a critical error. Note that the 👻 emoji is what we configured in the icon_emoji parameter of our handler.
PS: If you want to include extra information in your logs check out this snippet.

Now that the error handler works correctly, let's have a look at the second one. In this case, we will log something on purpose. Let's see how to user this handler in a controller, check out the following code: (there is also an action to raise a critical error)

<?php declare(strict_types=1);

// src/Controller/SlackController.php

namespace App\Controller;

use Monolog\Logger;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Slack debugging stuff.
 *
 * @Route("/slack", name="slack_")
 */
class SlackController extends AbstractController
{
    /**
     * This is a mini action to test the "slack_errors" Monolog hanlder.
     * The final route is: /slack/testError
     *
     * @Route("/testError", name="test_error")
     */
    public function testError(): Response
    {
        throw new \RuntimeException('Hello Ghost!');
    }

    /**
     * This is a mini action to test the "slack" Monolog hanlder.
     * The final route is: /slack/testInfo
     *
     * @Route("/testInfo", name="test_info")
     */
    public function testInfo(Logger $slackLogger): Response
    {
        $slackLogger->info('This is an example for the blog post! Check! ✅');

        return $this->json(['success' => true]);
    }
}

As you can see we inject a logger service into our controller function. (we could also inject it into the class itself). This service can't be auto-wired like this because there are several instances of Monolog\Logger in our application. So we have to bind the $slackLogger argument in our services.yaml file so the application is aware of the service to inject in this case:

# config/services.yaml
services:
    _defaults:
        bind:
            $slackLogger: '@monolog.logger.strangebuzz'

Now we are done, access /slack/testInfo and the message should appear on your slack channel. The display will be different because as it isn't an error, there is no exception to display but only the message that was sent by the application.

A Symfony application info displayed in Slack

That's it! 😁

Of course you can use all the different logging levels provided by the PSR-3 Logger interface. Happing logging/slacking! See you! COil. 😁


 The Slack API  The Symfony doc

» Published in "A week of Symfony 637" (11-17 March 2019).


» Call to action

Did you like this post? You can help me back in several ways: (use the Tweet on the right to comment/contact me )

  • Report any error/typo.
  • Report something that could be improved.
  • Like and retweet!
  • Follow me on Twitter
  • Subscribe to the RSS feed.

Thank you for reading! And see you soon on Strangebuzz! 😉

COil