Create custom laravel logs

Trust is good - but logs are better

There are many ways to identify what is going on under the hood of your laravel application. If you want to watch your code step by step then I would highly recommend a debugger for PHP like Xdebug along with the XDebug plugin for VS Code.

You could also use laravel's dd() - or of course also use the good ol' PHP way with var_dump() & print_r() - function which will stop your application and print out any given variable that you passed to the dd() function.

The third option would be logging. Logs can really come in handy for example when your app is retrieving data from external APIs. Sometimes you have to keep track of all of the data that is coming in. Sometimes your app might not work the way you expected it to work and a quick look at the logs might give you a hint to the root of the problem.

In laravel you can create a log entry very easily. Just use any of the methods below anywhere in your code.

use Illuminate\Support\Facades\Log;
 
Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

And just like that you can create a log entry withing your storage/logs/laravel.log file. The laravel.log file is set as the default channel for your logs. Your log file can become convoluted very quickly if you write all of your logs only to one file. That's why you want to create multiple custom log files within your application and seperate them thematically. For example you might want to have an error.log and another log for all your requests to the shopify api and so on and so on.

Understanding laravel logging file

If you take a look at the default settings of the config/logging.php file, you can see the default channel. If you haven't set any other values within your .env file then laravel's default channel is the "stack"-channel. Scrolling down the logging.php file you can see an array of channels that are defined within your app. The first item of this array is - your default channel - the stack-channel and it looks something like this:

'stack' => [
    'driver' => 'stack',
    'channels' => ['single'],
    'ignore_exceptions' => false,
],

So the channel name is obviously stack. The driver name is stack too, but what does that mean?

Well laravel has many drivers to choose from, but in this article we're just concentrating on stack and single. When you choose the stack driver, then you can write your log messages to multiple files, or better said multiple channels. You can define which channels to write your logs to in the 'channels' property, which expects an array of channels as a value. By default the only channel that is used here is the 'single' channel.

So let's take a look at the 'single' channel:

'single' => [
    'driver' => 'single',
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
],

Here we can see that this channel uses the 'single' driver. This tells us that the log message will only be written to one file. Which file? You might ask. Well, that is defined within the 'path' property of the 'single' channel array. And in there we can see, that the logs are written to the laravel.log file - as expected. The level tells us which Log message are actually being written to the log files based on the log level. laravel provides all log levels that are defined in the RFC 5424 specification. That means if you specify a log level of warning within your channel, then the following message wouldn't be printed:

Log::info($message);

because it is a level below warning!

Creating own log channel

So with all this knowledge we can now finally create our own Log channel! In order to do this, we will have to navigate back to our config/logging.php file and add an item to our 'channels' array. In my case it looks something like this:

'channels' => [

    ...,

    'shopify' => [
        'driver' => 'single',
        'path' => storage_path('logs/shopify_API.log'),
    ],

],

As you can see I created a new channel that writes a log file to storage/logs/shopify_API.log. In order to call this channel from anywhere in my application all I have to do is to call the following method:

Log::channel('shopify')->info('API info message', ['user_id' => 1]);

Customizing log message

Now let's step things up a notch and customize our logging message:

In order to do that we will have to create a new class within our app. So let's add a 'Logging' directory within our app directory wehre we add a new php file called CustomizedFormatter.php. The file should look something like this:

<?php

namespace App\Logging;

use Monolog\Formatter\LineFormatter;

class CustomizeFormatter
{
    public function __invoke($logger)
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->setFormatter(new LineFormatter(
                '{%datetime%} %message% // %context%'
            ));
        }
    }
}

As you can see, we define our desired format within the new LineFormatter class. Of course you can choose whatever format you like, I just made up an example.

In our last step we just have to tell our channel to use that custom formatter. That is easily done by going back to your logging.php file. Now we have to add the 'tap' property to our channel, in there we have to define an array. Within that array we pass our newly created CustomizeFormatter class. The channel should look like this:

use App\Logging\CustomizeFormatter;

...

'shopify' => [
    'tap' => [CustomizeFormatter::class],
    'driver' => 'single',
    'path' => storage_path('logs/shopify_API.log'),
],

And just like that you have created a custom log for your laravel application!

shopify invalid session token

Invalid session token - solve the annoying shopify error

If you have been working with shopify apps then you might have come across an error that says that you have an invalid session token. The reason behind this error might come from a race condition. The client tries to get some information from the server before it received a valid session token. Usually the client should have a session token, when making requests but you cannot be a 100% sure all of the time, thus the error is presented.

If you are making requests with axios then your request might look something like this:

// WRONG AND WIll LEAD TO ERROR DUE TO RACE CONDITIONS!!
axios
.get("https://test-application.test/api/endpoint")
.then(
    (response: {
        data: {
            content: string;
        };
    }) => {
            console.log("data:", data);
        );
    }
)
.catch((error) => {
    console.log("ERROR:", error);
});

In order to make sure that there won't be a race condition you can intercept your client's call and add some properties to the headers. All of this can be done very easily with axios. We want to make sure that the session token is always sent to the server, and in order to create a session token you will have to install an npm package from shopify.

You will need some methods from the shopify app-bridge-utils so make sure to install the npm package @shopify/app-bridge-utils:

npm i @shopify/app-bridge-utils
const instance = axios.create();
// Intercept all requests on this axios instance

instance.interceptors.request.use(function (config) {
return getSessionToken(app).then((token) => {
    config.headers["Authorization"] = `Bearer ${token}`;
        return config;
    });
});

instance
.get('https://test-application.test/api/endpoint')
.then(
    (response: {
        data: {
            themeName: string;
            appBlocksEnabled: string;
        };
    }) => {
        console.log("data:", data);
        setThemeName({ name: response.data.themeName });
        setContentLoaded(true);
        setSupportsAppBlocks(
            response.data.appBlocksEnabled === "partly" ||
                "completely"
                ? true
                : false
        );
    }
)
.catch((error) => {
    console.log("ERROR:", error);
});

And now your invalid session token error shouldn't appear anymore.

You can also watch my video right here: