Create a live logs dashboard for Laravel
You will need PHP 7.2+ and Composer installed on your machine.
In this tutorial, we’ll make use of these concepts to display our Laravel logs on a dashboard in realtime using Pusher Channels.
In an earlier tutorial, we looked at how to use the new log stacks and channels introduced in Laravel 5.6.
Prerequisites
Setting up
We’ll start off with an existing project so we can observe how the logs work. We’ll use a small stock application I built. You can clone the project from GitHub by running:
git clone https://github.com/shalvah/stockt.git realtime-laravel-logs
You can also download the source directly from this link.
Then cd
into the project folder and install dependencies:
composer install
Next, copy the .env.example
to a new file called .env
. Run the following command to generate an application encryption key:
php artisan key:generate
Lastly, create a file called database.sqlite
in the database
directory and run the following command to set up and populate the database:
php artisan migrate --seed
Configuring the Pusher handler
Run the following command to add the monolog-pusher
package:
composer require shalvah/monolog-pusher
This package will be doing the heavy lifting. Laravel’s logging system is powered by Monolog, which uses handlers to define where a log gets sent to. For instance, the FileHandler
sends logs to a file, the EmailHandler
sends logs to a specified email address, and so forth. You can view a list of available handlers here. This package provides us with a PusherHandler
which sends logs to a Pusher channel.
We’re going to configure Laravel’s log component to use Monolog, with the PusherHandler
as its handler. To do this, we’ll register a new channel that uses the monolog
driver. Open up your config/logging.php
and add the following to the channels
array:
// config/logging.php
'realtime' => [
'driver' => 'monolog',
'handler' => \Shalvah\MonologPusher\PusherHandler::class,
'with' => [
'pusher' => [
env('PUSHER_APP_KEY'),
env('PUSHER_APP_SECRET'),
env('PUSHER_APP_ID'),
[
'cluster' => env('PUSHER_APP_CLUSTER')
]
],
'level' => \Monolog\Logger::DEBUG
],
],
Note: “realtime” is just an arbitrary name we chose for our log channel. We could equally use “chimichanga”.
The configuration is pretty easy to understand. We specify the driver
as monolog
, and the handler
as the PusherHandler
. In the with
array, we specify the parameters to be passed to the constructor of the handler
, which in this case are our Pusher
credentials and the minimum log level we want this handler to catch.
You’ll then need to add your Pusher app credentials to the .env
file:
PUSHER_APP_ID=your-app-id
PUSHER_APP_KEY=your-app-key
PUSHER_APP_SECRET=your-app-secret
PUSHER_APP_CLUSTER=your-app-cluster
Viewing logs
We need to add a page to our app where we can view our logs. Let’s add a new route:
// routes/web.php
Route::view('logs', 'logs')->middleware('auth');
This route will render the view logs.blade.php
whenever we visit /logs
. Create the file resources/views/logs.blade.php
with the following content:
@extends('layouts.app')
@section('content')
<div class="container">
<h2>Logs</h2>
<div class="list-group" id="logs">
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://js.pusher.com/4.2/pusher.min.js"></script>
<script>
function displayLog(data) {
let $log = $('<div>').addClass('list-group-item')
.html(`<pre><code>${JSON.stringify(data, null, 4)}</code></pre>`);
$('#logs').prepend($log);
}
</script>
<script>
var socket = new Pusher("your-app-key", {
cluster: 'your-app-cluster',
});
socket.subscribe('{{ config('app.env') }}')
.bind('log', displayLog);
</script>
@endsection
Don’t forget to replace your-app-key
and your-app-cluster
with their real values.
Here, we subscribe to the Pusher channel and listen for the log
event. When there is a new log, we format it and display it on the page.
Note: the name of the Laravel log channel (“realtime”) is different from the name of the Monolog log channel. Laravel uses the name of the current app environment (
config('app.env')
) as the name of the Monolog log channel. This is what also gets used as the Pusher channel.
Now let’s test it out. We’ll add a few logging calls at random places in our app.
Open up the ProductsController
and modify the index
method so it looks like this:
// app/Http/Controllers/ProductController.php
public function index()
{
Log::channel('realtime')->info(request()->user()->name.' is viewing all products!');
return view('products.index', ['products' => Product::all()]);
}
Also, modify the `show` method so it looks like this:
// app/Http/Controllers/ProductController.php
public function show()
{
Log::channel('realtime')->debug(request()->user()->name." is viewing the product with ID {$product->id}!");
return view('products.show', ['product' => $product]);
}
Don’t forget to import the Log
class at the top via use Illuminate\Support\Facades\Log;
.
By calling Log::channel('realtime')
, we’re explicitly telling Laravel to broadcast this particular log message over the realtime
channel we created earlier.
Let’s see our realtime logs in action. Start the app by running php artisan serve
.
Log in at http://localhost:8000/login with admin@stockt.test as email and secret as password. Now open up the logs page (http://localhost:8000/logs) in one tab and the products page (http://localhost:8000/products) in another. Try to navigate across a few products. You should see the logs page update similar to this:
Making Pusher our default logger
You’ll notice that every time we wanted to use the realtime logger, we had to call Log::channel('realtime')
first. We can get rid of this step by making the realtime
logger our default logger. To do this, set the LOG_CHANNEL
variable in your .env
file to realtime
.
LOG_CHANNEL=realtime
Laravel sometimes caches app config, so you might need to run
php artisan config:clear
after making a change to your.env
file for the change to reflect
Now, in our code snippets above we can simply write:
Log::info(request()->user()->name." is viewing all products!");
And logs will get sent to Pusher.
Combining loggers using a stack
Supposing we want to see our logs in realtime, but we also want to persist them to a file or database. Laravel 5.6 allows us to do this by using a stack. A stack combines two or more channels into one.
You can create a stack dynamically by using the stack
method:
Log::stack(['single', 'realtime'])->info("Someone is viewing all products!");
‘single’ is the name of the default Laravel channel that writes to a single file,
storage/logs/laravel.log
Alternatively, you can configure a stack in your config/logging.php
, by adding a new entry in the channels
array, this time using the stack
driver:
// config/logging.php
'realtime-stack' => [
'driver' => 'stack',
'channels' => ['single', 'realtime'],
],
Now, when we send log messages to this stack, they will be sent to both Pusher and the regular log file.
Conclusion
In this article, we’ve been able to send our application’s logs in realtime to our frontend using Pusher Channels. This is especially useful in applications where there is a need for realtime monitoring of actions. You can check out the source code of the completed application on GitHub.
29 August 2018
by Shalvah Adebayo