When developing a modern application, logging should be at the top of the priority list.

Logging provides a way to visualize your app in both development and production, enabling transparency and visibility. With properly structured logging, modern applications can become easier to maintain as we can proactively identify points of failure and performance bottlenecks in our app.

The Laravel framework comes with a robust logging system that handles all the hurdles involved in configuring a properly structured logging system out of the box. This new logging system introduced in Laravel 6.5 is powerful, and we will explore it in this article.

This article will explore the basics of Laravel logging and why you should use Laravel logging in your next project. We will discuss structured logging and centralized logging in detail. In addition, we will learn how to implement Laravel logging by building a Todo application.

You’ll get more out of this article if you already have the following under your belt:

What Is Laravel Logging?

Laravel logging is all about how Laravel handles logging, or automatic issue reporting, using a viral PHP logging system called Monolog. However, due to Laravel’s philosophy of using popular existing libraries to implement different framework features, Laravel employs Monolog for all its logging needs.

Monolog is a highly flexible and popular PHP logging library that we can configure to send your logs to files, sockets, databases, and other web services. Monolog provides a familiar interface for writing logs from standard text files to advanced third-party log management services. Laravel typically sets up Monolog to use a standard logging configuration file.

For more information about Monolog and its features, check out the official documentation, as that is beyond the scope of this article.

Before we dive into configuring and implementing Laravel logging using Monolog, let’s explore more reasons to use Laravel logging and the different types.

Why Use Laravel Logging?

Why is logging necessary?

The Twelve-Factor App manifesto treats logging as one of the critical concerns of a modern application, as logging is a key to performance and monitoring.

Logs aid in the detailed understanding of errors that happen in production and where they originated. In addition, with proper log structures, it can show the particular user, the action that caused the error, and the possible solution for faster bug fix and maintenance.

Structured logging is a lifesaver in production applications by helping troubleshoot defects and solving problems in production. In addition, you can monitor and collect all your log messages in real-time using specialized logging tools for live analysis and reporting.

For these reasons, you need to make structured logging a top priority in your next modern application project.

Let’s look at the overview of the different logging styles available.

Basics of Laravel Logging

Learning the basics of logging will help you understand how Laravel handles logging and how you can improve your structured logging practices.

Let’s examine two essential concepts in logging to understand better how to implement our logging procedures.

Laravel Structured Logging

In software development, structured logging is implementing a predetermined and consistent message format for application logs. This format allows the messages to be treated as data that can be monitored, manipulated, and visualized much better than the regular text format.

You must implement a structured logging approach in your modern application development because log files are the essential assets for developers when something wrong happens to your application in production.

Since Laravel uses Monolog, developers can quickly implement structured logging by configuring the logger to receive specific types of information, storing the log files in different formats, and sending the logs to various third-party log management services for visualization.

Laravel Centralized Logging

A centralized logging system is where logs are sent to Centralized Log Management (CLM) solutions from multiple sources for easy consolidation and visualization. However, CLM is a specialized logger solution that collects log messages from different sources and consolidates the data for easy processing and visualization.

Aside from data collection, CLM is also expected to support the analysis of log data and clear presentation of the data after analysis.

Structured Logging vs Basic Logging

Let’s examine the difference between structured logging and basic (unstructured) logging and why you should use structured logging in your Laravel project.

Basic Logging

In basic logging, the log files are stored in a raw format with limited data to query and identify individual logs.

When using Basic logging, developers will not be able to use third-party analytical tools to read, view, and analyze logs unless they develop a custom tool or stick with a limited tool that supports their log format.

There are three big reasons to avoid using basic logging:

  1. Centralized log management systems can not work with the data without additional support.
  2. A customized solution is required to read and parse the data of a basic logging solution.
  3. It can be challenging for administrators to read basic logging data since it is raw and unstructured.

Structured Logging

Structured logging saves developers time by using open-source third-party log analytical tools that support standard log structure to read, view, and analyze logs.

Logs are helpful if they contain the correct data listed below, which is what structured logging aims to achieve. We can use the data included in structured logging to create dashboards, graphs, charts, and any other helpful visualization to determine the application’s health.

These are basic examples of the information that we can include in structured log messages. In addition, you can completely customize the data to suit your needs.

Here are some examples of the data you can collect with structured logging:

  1. The port used to execute the function
  2. The date and time the event happened
  3. The customer username or ID
  4. A description of the event (log message)
  5. The protocol used to execute the function
  6. The location of the triggered event (indicate API or running app)
  7. The unique event ID
  8. The type of action triggered (log level)

Logs should contain enough data to easily visualize the solution or the reason for the log event. Also, note that you should not store all types of information, such as passwords or sensitive data in logs.

Now that we’ve glimpsed what Laravel logging is all about, let’s move on to implementing Laravel logging by building an application with logging as a first-class citizen.

How To Implement Laravel Logging With Todo App

Now we’re going to apply what we’ve learned so far by creating a new Laravel project and implementing Laravel logging.

If you haven’t used Laravel before, you can read through what Laravel is or peek at our list of excellent Laravel tutorials to get started.

Setting up Laravel

First, we’re going to create a fresh Laravel instance using the below command. You can look up the official documentation for more.

Open your console and navigate to where you store your PHP projects before running the commands below. Make sure to have Composer installed and configured correctly.

composer create-project laravel/laravel laravel-logging-app
cd laravel-logging-app // Change directory to current Laravel installation
php artisan serve // Start Laravel development server

Configuring and Seeding the Database

Next, we will set up our database, create a new Todo model, and seed 200 fake data for testing.

Open your database client and create a new database. We’ll do the same with the name laravel_logging_app_db and then fill up our .env file with the database credentials:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_logging_app_db
DB_USERNAME=//DB USERNAME HERE
DB_PASSWORD=//DB PASSWORD HERE

Next, we’ll run the following command to create the migration and the Todo model simultaneously:

php artisan make:model Todo -mc

Open the newly created migration found database/migrations/xxx-create-todos-xxx.php and paste in the following codes:

<?php
use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;
class CreateTodosTable extends Migration
{
  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
  {
    Schema::create('todos', function (Blueprint $table) {
      $table->id();
      $table->string('title');
      $table->text('description')->nullable();
      $table->boolean('is_completed')->default(false);
      $table->timestamps();
    });
  }
  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::dropIfExists('todos');
  }
}

You can seed your todos with faker data by learning to seed your databases in Laravel using Faker.

Overview of Monolog

With Laravel Monolog, you can stream and send structured logs to different channels such as emails, Slack, files, sockets, inboxes, databases, and various web services. In Laravel, you can configure logging from a single configuration file located in config/logging.php.

The configuration file comes with predefined log drivers to choose from, and the default driver is a stack that uses the single channel to log to a laravel.log file found in the storage/logs folder. We will demonstrate structured logging by using a couple of the Laravel log drivers.

Laravel provides a handful of methods to interact with Logs, as demonstrated generally in the TodosController.php controller file shortly.

Writing Log Messages in the Controller

Open the newly created TodosController.php controller file found app/Http/Controllers folder and paste in the following codes:


<?php
namespace AppHttpControllers;
use AppModelsTodo;
use IlluminateHttpRequest;
use AppHttpControllersController;
use IlluminateSupportFacadesAuth;
use IlluminateSupportFacadesLog;
class TodosController extends Controller
{
  public function index(Request $request)
  {
    $todos = Todo::all();
    Log::warning('User is accessing all the Todos', ['user' => Auth::user()->id]);
    return view('dashboard')->with(['todos' => $todos]);
  }
  public function byUserId(Request $request)
  {
    $todos = Todo::where('user_id', Auth::user()->id)->get();
    Log::info('User is accessing all his todos', ['user' => Auth::user()->id]);
    return view('dashboard')->with(['todos' => $todos]);
  }
  public function show(Request $request, $id)
  {
    $todo = Todo::find($id);
    Log::info('User is accessing a single todo', ['user' => Auth::user()->id, 'todo' => $todo->id]);
    return view('show')->with(['todo' => $todo]);
  }
  public function update(Request $request, $id)
  {
    # Validations before updating
    $todo = Todo::where('user_id', Auth::user()->id)->where('id', $id)->first();
    Log::warning('Todo found for updating by user', ['user' => Auth::user()->id, 'todo' => $todo]);
    if ($todo) {
      $todo->title = $request->title;
      $todo->desc = $request->desc;
      $todo->status = $request->status == 'on' ? 1 : 0;
      if ($todo->save()) {
        Log::info('Todo updated by user successfully', ['user' => Auth::user()->id, 'todo' => $todo->id]);
        return view('show', ['todo' => $todo]);
      }
      Log::warning('Todo could not be updated caused by invalid todo data', ['user' => Auth::user()->id, 'todo' => $todo->id, 'data' => $request->except('password')]);
      return; // 422
    }
    Log::error('Todo not found by user', ['user' => Auth::user()->id, 'todo' => $id]);
    return; // 401
  }
  public function store(Request $request)
  {
    Log::warning('User is trying to create a single todo', ['user' => Auth::user()->id, 'data' => $request->except('password')]);
    # Validations before updating
    $todo = new Todo;
    $todo->title = $request->title;
    $todo->desc = $request->desc;
    $todo->user_id = Auth::user()->id;
    if ($todo->save()) {
      Log::info('User create a single todo successfully', ['user' => Auth::user()->id, 'todo' => $todo->id]);
      return view('show', ['todo' => $todo]);
    }
    Log::warning('Todo could not be created caused by invalid todo data', ['user' => Auth::user()->id, 'data' => $request->except('password')]);
    return; // 422
  }
  public function delete(Request $request, $id)
  {
    Log::warning('User is trying to delete a single todo', ['user' => Auth::user()->id, 'todo' => $id]);
    $todo = Todo::where('user_id', Auth::user()->id)->where('id', $id)->first();
    if ($todo) {
      Log::info('User deleted a single todo successfully', ['user' => Auth::user()->id, 'todo' => $id]);
      $todo->delete();
      return view('index');
    }
    Log::error('Todo not found by user for deleting', ['user' => Auth::user()->id, 'todo' => $id]);
    return; // 404
  }
}

Within each of the methods in the TodoController, we added the Log facade with a specific log level to define the type of error we want to send. Below is an example of using the

Log facade in the store method.

public function store(Request $request)
{
  Log::warning('User is trying to create a single todo', ['user' => Auth::user()->id, 'data' => $request->except('password')]);
  # Validations before updating
  $todo = new Todo;
  $todo->title = $request->title;
  $todo->desc = $request->desc;
  $todo->user_id = Auth::user()->id;
  if ($todo->save()) {
    Log::info('User create a single todo successfully', ['user' => Auth::user()->id, 'todo' => $todo->id]);
    return view('show', ['todo' => $todo]);
  }
  Log::warning('Todo could not be created caused by invalid todo data', ['user' => Auth::user()->id, 'data' => $request->except('password')]);
  return; // 422
}

Formatting Log Messages

Suppose you’re not comfortable with the default LineFormatter used by Laravel, which does a great job of providing readable and helpful messages.

In that case, you can easily spin up a customized formatter object to fit your use case and use it throughout the application.

The official Monolog documentation gives a complete list of available formatters and can easily create a custom one.

In Laravel, you can easily set any of the drivers to use your custom formatter by adding it to the list like below inside the configuration file located at config/logging.php:

'daily' => [
  'driver' => 'daily',
  'path' => storage_path('logs/laravel.log'),
  'level' => env('LOG_LEVEL', 'debug'),
  'days' => 14,
  'formatter' => MonologFormatterHtmlFormatter::class,
  'formatter_with' => [
    'dateFormat' => 'Y-m-d',
  ]
],

The example above adds a custom MonologFormatterHtmlFormatter to the daily driver using the formatter and formatter_with key in the daily channel configuration to change the format of dates.

Sending Logs to Different Channels

With the help of Monolog, Laravel can send logs to different channels and multiple channels simultaneously.

Let’s demonstrate how to send logs to our Slack channel following these simple steps. Change the default log channel to Slack and add Slack Webhook URL in your .env file.

LOG_CHANNEL=slack
LOG_SLACK_WEBBHOOK_URL= Slack_webhook_url_here

Next, test your configuration by logging a message in your application using the Log facade like the one shown below:

Log::debug("The API instance is on fire caused by:", ['user' => 1])

You can open your Slack channel to check for the error printed in the desired channel you specified when generating the Webhook URL.

Summary

Logging is as important as any other factor of your application, if not more so. That’s why it’s suggested by the Twelve-Factor App manifesto as one of the most critical concerns of any modern application.

With effective logging, you can easily read, view, and visualize errors and defects that happen in your production-ready application. To that end, it’s important that you implement structured logging into your application right from the start of the project.

In this article, we’ve explored Laravel logging and why you should use it in your next project. We discussed both structured logging and centralized logging in detail. In addition, we learned how to implement Laravel logging by building a Todo application.

How do you plan to implement logging into your next app? Let us know in the comment section.

Solomon Eseme

I am a Software Engineer and Content Creator who is geared toward building high-performing and innovative products following best practices and industry standards. I also love writing about it at Masteringbackend.com. Follow me on Twitter, LinkedIn, and About Me