🎉 New release for Pusher Chatkit - Webhooks! Extend your in-app chat functionality
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Creating a Laravel logger - Part 2: Creating a Pusher logger package

  • Neo Ighodaro
March 21st, 2019
For this part of the series, you will need PHP 7.13+, Laravel 5.7+ and Composer installed on your machine.

In this tutorial, we will build a Laravel package from scratch to help us dispatch logs to Pusher. All logs will be sent to Pusher Channels but error logs will be sent both to Pusher Channels and Pusher Beams. Let’s dig in!

This is the second part of our six-part series on building a logging monitoring system. In the first part, we created the shell for the Laravel application. The application has the UI that will help us manually dispatch logs.

Requirements

To follow along with this series you need the following things:

  • Completed previous parts of the series.
  • Laravel installed on your local machine. Installation guide.
  • Knowledge of PHP and the Laravel framework.
  • Composer installed on your local machine. Installation guide.
  • Android Studio >= 3.x installed on your machine (If you are building for Android).
  • Knowledge of Kotlin and the Android Studio IDE.
  • Xcode >= 10.x installed on your machine (If you are building for iOS).
  • Knowledge of the Swift programming language and the Xcode IDE.

Creating our Laravel package

The first thing we will do is create a new folder to store the package. To do this cd to the project you started from the first part and create a new packages folder by running this on your terminal:

    $ mkdir packages

This creates a new folder named packages. Next, we will create the main package folder based on the name of our package. Our package will be called PusherLogger. Create the folder like this:

    $ cd packages
    $ mkdir PusherLogger

After creating the folder, we will start adding files to the folder. The first file we need is the composer.json file. This file will contain information about our package like the name, description, dependencies, and other properties. To generate this file, cd to the PusherLogger directory and run this command:

    $ composer init

This initiates the composer config generator that will request information about your package. Follow the wizard to provide information about your package. At the end of it, your composer.json file should look similar to this:

    {
        "name": "package/pusher-logger",
        "description": "A package to distribute logs to Pusher",
        "type": "library",
        "authors": [
            {
                "name": "Neo Ighodaro",
                "email": "neo@creativitykills.co"
            }
        ],
        "require": {}
    }

You should change package in the name property to your own name.

Next, we will add dependencies needed for package. We will add it to the the require object of the composer.json file. Add the dependencies like so:

    {
        // [...]

        "require": {
          "php": ">=7.1.3",
          "illuminate/support": "~5",
          "monolog/monolog": "^1.24.0",
          "pusher/pusher-php-server": "^3.2",
          "pusher/pusher-push-notifications": "^1.0"
        }
    }

For this package, we require PHP 7.1.3 and up, the Pusher channel package, and Pusher push notifications package to help us broadcast the logs to Pusher.

Next, let’s instruct the package where it should load the files from. Add the snippet below to the composer.json file:

    {
        // [...]

        "autoload": {
            "psr-4": {
                "PackageNamespace\\PusherLogger\\": "src/"
            }
        },
        "autoload-dev": {
            "psr-4": {
                "PackageNamespace\\PusherLogger\\Tests\\": "tests"
            }
        },
        "extra": {
            "laravel": {
                "providers": [
                    "PackageNamespace\\PusherLogger\\PusherLoggerServiceProvider"
                ],
                "aliases": {
                    "PusherLogger": "PackageNamespace\\PusherLogger\\PusherLogger"
                }
            }
        }
    }

You can use a different camelcase namespace from PackageNamespace if you wish. Just remember to replace the namespace everywhere you changed it below.

Now we have instructed Composer’s autoloader how to load files from a certain namespace. This tells the package to look out for the /src directory for the package files. This directory is not available yet so create the folder in your PusherLogger folder. You can do that by running this command:

    $ mkdir src

Navigate to the src folder and create two files. First we will create the PusherLoggerServiceProvider.php file and paste the following into it:

    <?php // File: ./src/PusherLoggerServiceProvider.php

    namespace PackageNamespace\PusherLogger;

    use Pusher\Pusher;
    use Illuminate\Support\ServiceProvider;
    use Pusher\PushNotifications\PushNotifications;

    class PusherLoggerServiceProvider extends ServiceProvider
    {
        /**
         * Bootstrap the application services.
         *
         * @return void
         */
        public function boot()
        {
          //
        }

        /**
         * Register the application services.
         *
         * @return void
         */
        public function register()
        {
          //
        }
    }

You can replace PackageNamespace with your own namespace.

Service providers are the central place of all Laravel application bootstrapping. Your own application, as well as all of Laravel's core services are bootstrapped via service providers. - Laravel documentation

This class extends the Illuminate\Support\ServiceProvider class. Our class contains two methods - register and boot. The boot method loads event listeners, routes, or any other piece of functionality while the register method only bind things into the service container.

Inside a service provider class, the app container can be accessed via the $app property. So in our PusherLoggerServiceProvider class we will bind an alias pusher-logger to the PusherLogger class. Update the register method like this:

    <?php // File: ./src/PusherLoggerServiceProvider.php

    // [...]

    class PusherLoggerServiceProvider extends ServiceProvider
    {
      // [...]

      public function register()
      {
          $this->app->bind('pusher-logger', function () {
              $config = config('broadcasting.connections.pusher');

              $key = $config['key'] ?? '';
              $secret = $config['secret'] ?? '';
              $app_id = $config['app_id'] ?? '';

              $pusher = new Pusher($key, $secret, $app_id, [
                  'useTLS' => true,
                  'encrypted' => true,
                  'cluster' => $config\['options'\]['cluster'] ?? '',
              ]);

              $beams = new PushNotifications([
                  'secretKey' => $config\['beams'\]['secret_key'] ?? '',
                  'instanceId' => $config\['beams'\]['instance_id'] ?? '',
              ]);

              return new PusherLogger($pusher, $beams);
          });
      }
    }

Above, we are binding pusher-logger to the Closure above. Inside the Closure, we are registering an instance of a PusherLogger class, which we will create later. This class receives an instance of a configured Pusher object, and a configured PushNotifications object. Since we are using Laravel’s service container, it means anytime we try to use the pusher-logger service, we will get a PusherLogger instance with both Pusher and Push Notifications configured.

Next, let us create our second class. The class will be a Facade. A Facade is one of the architecture concepts Laravel provides. It is a static interface to classes that are available in the application's service container, meaning that our Facade classes represent another class bound in the service container.

To create this class, first make a directory named Facades in the src directory and then create the PusherLogger.php file inside it. When you have created the file, paste the following code into the file:

    <?php // File: ./src/Facades/PusherLogger.php

    namespace PackageNamespace\PusherLogger\Facades;

    use Illuminate\Support\Facades\Facade;

    class PusherLogger extends Facade
    {
        protected static function getFacadeAccessor()
        {
            return 'pusher-logger';
        }
    }

In the getFacadeAccessor method of the class above, we returned pusher-logger which corresponds to the alias we bound to the PusherLogger class earlier in the service provider.

Now we can use the PusherLogger Facade as a proxy to the original PusherLogger class with the package logic. Let’s create the original PusherLogger class. In the src directory, create a new file named PusherLogger.php and paste the following code into it:

    <?php // File: ./src/PusherLogger.php

    namespace PackageNamespace\PusherLogger;

    use Pusher\Pusher;
    use Pusher\PushNotifications\PushNotifications;

    class PusherLogger
    {
        /**
         * @var \Pusher\Pusher
         */
        protected $pusher;

        /**
         * @var \Pusher\PushNotifications\PushNotifications
         */
        protected $beams;

        /**
         * @var string
         */
        protected $event;

        /**
         * @var string
         */
        protected $channel;

        /**
         * @var string
         */
        protected $message;

        /**
         * @var string
         */
        protected $level;

        /**
         * @var array
         */
        protected $interests = [];

        // Log levels
        const LEVEL_INFO  = 'info';
        const LEVEL_DEBUG = 'debug';
        const LEVEL_ERROR = 'error';

        /**
         * PusherLogger constructor.
         *
         * @param \Pusher\Pusher $pusher
         * @param \Pusher\PushNotifications\PushNotifications $beams
         */
        public function __construct(Pusher $pusher, PushNotifications $beams)
        {
            $this->beams = $beams;

            $this->pusher = $pusher;
        }
    }

In the snippet above, we declared some variables we will use later in the class. We also declared the class constructor to receive instances of the Pusher object and the PushNotifications object just as we did in the service provider binding above.

We also have some properties and constants for the class. We can use the constants outside the class when specifying the log level. This would make it easy to change the values later on if we wanted to.

In the same class, let’s define a some methods, which will be how we will set the other protected class properties. In the same file, paste the following code:

    // File: ./src/PusherLogger.php
    // [...]

    /**
     * Sets the log message.
     *
     * @param  string $message
     * @return self
     */
    public function setMessage(string $message): self
    {
        $this->message = $message;

        return $this;
    }

    /**
     * Sets the log level.
     *
     * @param  string $level
     * @return self
     */
    public function setLevel(string $level): self
    {
        $this->level = $level;

        return $this;
    }

    /**
     * Sets the Pusher channel.
     *
     * @param  string $channel
     * @return self
     */
    public function setChannel(string $channel): self
    {
        $this->channel = $channel;

        return $this;
    }

    /**
     * Sets the event name.
     *
     * @param  string $event
     * @return self
     */
    public function setEvent(string $event): self
    {
        $this->event = $event;

        return $this;
    }

    /**
     * Set the interests for Push notifications.
     *
     * @param  array $interests
     * @return self
     */
    public function setInterests(array $interests): self
    {
        $this->interests = $interests;

        return $this;
    }

    // [...]

Above, we have defined some similar methods. They just set the corresponding protected class properties and then return the class instance so they are chainable.

Next, we will add other helper methods to be used in the package. Add the following methods to the PusherLogger.php class:

    // File: ./src/PusherLogger.php
    // [...]

    /**
     * Quickly log a message.
     *
     * @param string $message
     * @param string $level
     * @return self
     */
    public static function log(string $message, string $level): self
    {
        return app('pusher-logger')
            ->setMessage($message)
            ->setLevel($level);
    }

    /**
     * Dispatch a log message.
     *
     * @return bool
     */
    public function send(): bool
    {
        $this->pusher->trigger($this->channel, $this->event, $this->toPushHttp());

        if ($this->level === static::LEVEL_ERROR) {
            $this->beams->publishToInterests($this->interests, $this->toPushBeam());
        }

        return true;
    }

    // [...]

The first method is a quick shorthand we can use when dispatching log messages and the second method is the method that dispatches log messages to the Pusher clients. In the send method, we are checking to see if the log level is an error level. If it is, we will also send a push notification so the administrator can be aware that an error has occurred.

When creating a log, we need to set the channel, events and interests (when using Pusher Beams) in which the log would be sent to. Here’s an example of how we can use the logger:

    use PackageNamespace\PusherLogger\PusherLogger;

    PusherLogger::log('Winter is Coming', PusherLogger::LEVEL_WARNING)
            ->setEvent('log-event')
            ->setChannel('log-channel')
            ->setInterests(['log-interest'])
            ->send()

The final function in the snippet is the function that sends the data to Pusher. In the function, all logs are sent to Pusher Channels, but error logs are also sent to Pusher Beams so that the client can receive a notification.

While defining the send function, we used two new methods that composes the data to be sent to Pusher Channels and Pusher Beams respectively. Add the methods to the same class like so:

    // File: ./src/PusherLogger.php
    // [...]

    public function toPushHttp()
     {
         return [
             'title' => 'PusherLogger' . ' '. ucwords($this->level),
             'message' => $this->message,
             'loglevel' => $this->level
         ];
     }

     public function toPushBeam()
     {
         return [
             'apns' => [
                 'aps' => [
                     'alert' => [
                         'title' => 'PusherLogger' . ' '. ucwords($this->level),
                         'body' => $this->message,
                         'loglevel' => $this->level
                     ],
                 ],
             ],
             'fcm' => [
                 'notification' => [
                     'title' => 'PusherLogger' . ' '. ucwords($this->level),
                     'body' => $this->message,
                     'loglevel' => $this->level
                 ],
             ],
         ];
    }

Creating a log handler

Laravel uses Monolog, which is a powerful logging package for PHP. We can create custom handlers for Monolog and so let’s do one that will be for our Pusher logger.

Create a new file in the src directory of the package called PusherLoggerHandler.php and paste the following code:

    <?php // File: ./src/PusherLoggerHandler.php

    namespace PackageNamespace\PusherLogger;

    use Monolog\Logger;
    use Monolog\Handler\AbstractProcessingHandler;

    class PusherLoggerHandler extends AbstractProcessingHandler
    {
        protected function write(array $record): void
        {
            $level = strtolower(Logger::getLevelName($record['level']));

            PusherLogger::log($record['message'], $level)
                ->setEvent('log-event')
                ->setChannel('log-channel')
                ->setInterests(['log-interest'])
                ->send();
        }
    }

Above, we have the custom handler that will be hooked into our Laravel Monolog instance. When we do, logs will be automatically pushed to our Pusher application as needed. We will do that in the next part.

That’s all.

Conclusion

In this part of the series, we have been able to set up the logic we need to be able to push logs to Pusher. In the next part of the series, we will integrate this package with our Laravel application and see how everything will work together.

The source code is available on GitHub.

Clone the project repository
  • Android
  • Beams
  • iOS
  • Laravel
  • PHP
  • Vue.js
  • Kotlin
  • Swift
  • Beams
  • Channels

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.