Often when working on an M2 project customers use different 3rd party services for inventory management, shipping management etc. Usually if the 3rd party service only uses API calls to read data from an M2 instance there’s shouldn’t be any trouble, but when someone wants to make an update using the M2 API on a production server, you’d better be ready to know what they’re doing. If something goes wrong the client will hold us responsible…. unless we can prove otherwise.

In comes the REST API logging code to save the day. One of our team leads came up with this idea when some of our products were suspiciously “messed up” out of nowhere on a development server. So basically, we can add a small M2 module to log every REST API call to an M2 setup. This should log the source of the call, the target url and the PUT/POST data used to make the call.

So let’s get started. First create a module in the RocketWeb\RestLog namespace. Folder structure should look something like this:

Folder structure

Every Magento 2 module starts with some boilerplate code, so let’s get the boring parts out of the way:

// RocketWeb/RestLog/composer.json
{
    "name": "rocketweb/module-restlog",
    "description": "",
    "license": "proprietary",
    "minimum-stability": "dev",
    "require": {},
    "type": "magento2-module",
    "version": "1.0.0",
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "RocketWeb\\RestLog\\": ""
        }
    }
}


<?php
// RocketWeb/RestLog/registration.php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'RocketWeb_RestLog',
    __DIR__
);


And less boring, but still not of any real interest, the all mighty module.xml file:

<?xml version="1.0"?>
<!-- app/code/RocketWeb/RestLog/etc/module.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="RocketWeb_RestLog" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Webapi"/>
        </sequence>
    </module>
</config>


Now we can focus on the important parts. First, we should create a separate log file for REST requests. We don’t want to mix the REST calls into system.log. In order to do this we need to create app\code\RocketWeb\RestLog\Model\Logger\Handler.php:

<?php

namespace RocketWeb\RestLog\Model\Logger;

use Magento\Framework\Filesystem\DriverInterface;

/**
 * Class Handler
 * @package RocketWeb\RestLog\Model\Logger
 */
class Handler extends \Magento\Framework\Logger\Handler\Base
{
    /**
     * Handler constructor.
     * @param DriverInterface $filesystem
     * @param null $filePath
     */
    public function __construct(DriverInterface $filesystem, $filePath = null)
    {
        parent::__construct($filesystem, $filePath);
        $this->getFormatter()->ignoreEmptyContextAndExtra(true);
    }

    /**
     * Logging level
     * @var int
     */
    protected $loggerType = \Monolog\Logger::INFO;

    /**
     * File name
     * @var string
     */
    protected $fileName = '/var/log/rest_api.log';
}


And app\code\RocketWeb\RestLog\Model\Logger\Logger.php:

<?php

namespace RocketWeb\RestLog\Model\Logger;

/**
 * Class Logger
 * @package RocketWeb\RestLog\Model\Logger
 */
class Logger extends \Monolog\Logger
{
}


Yep, that’s correct. This is just an empty class. We will use it as a virtual type in di.xml. We only need it to inherit from the native \Monolog\Logger class. This will make sense soon enough, but if you want to know more you can familiarize yourself with Virtual Types using the official Magento 2 docs.

Finally, our last piece of code is the logging code itself. Create the file app/code/RocketWeb/RestLog/Plugin/RestApiLog.php with the following content:

<?php

namespace RocketWeb\RestLog\Plugin;

/**
 * Class RestApiLog
 * @package RocketWeb\RestLog\Plugin
 */
class RestApiLog
{
    /**
     * @var \RocketWeb\RestLog\Model\Logger\Logger
     */
    protected $logger;

    /**
     * Plugin constructor.
     * @param \RocketWeb\RestLog\Model\Logger\Logger $logger
     */
    public function __construct(\RocketWeb\RestLog\Model\Logger\Logger $logger)
    {
        $this->logger = $logger;
    }

    public function beforeDispatch(
        \Magento\Webapi\Controller\Rest $subject,
        \Magento\Framework\App\RequestInterface $request
    )
    {
        $this->logger->info('SOURCE: ' . $request->getClientIp());
        $this->logger->info('METHOD: ' . $request->getMethod());
        $this->logger->info('PATH: ' . $request->getPathInfo());
        $this->logger->info('CONTENT: ' . $request->getContent() . PHP_EOL);
    }
}


We can safely call the above methods on the RequestInterface because Magento actually injects the Magento\Framework\App\Request\Http class whenever the RequestInterface class is used. Since the Http class inherits from \Magento\Framework\HTTP\PhpEnvironment\Request we’re good to go. I think there’s other class you could inject in order to retrieve the IP address of a request, but why use more classes when one will suffice?

I know I said this was the last piece of the code, but I choose my words carefully. I said code, not markup 😊. We still have a very important .xml file to create. This is where the virtual class type I mentioned previously is created:

<?xml version="1.0"?>
<!-- RocketWeb/RestLog/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Webapi\Controller\Rest">
        <plugin name="rest-api-log" type="RocketWeb\RestLog\Plugin\RestApiLog"/>
    </type>
    <type name="RocketWeb\RestLog\Model\Logger\Handler">
        <arguments>
            <argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File
        </arguments>
    </type>
    <type name="RocketWeb\RestLog\Model\Logger\Logger">
        <arguments>
            <argument name="name" xsi:type="string">RocketWeb_RestLog
            <argument name="handlers"  xsi:type="array">
                <item name="system" xsi:type="object">RocketWeb\RestLog\Model\Logger\Handler
            </argument>
        </arguments>
    </type>
</config>


The <plugin> tag is important because it tells Magento 2 to call the \RocketWeb\RestLog\Plugin\RestApiLog::beforeDispatch method before calling \Magento\Webapi\Controller\Rest::dispatch. Hence the name “beforeDispatch”. Since the dispatch method is the main entry point for REST API calls, this ensures we can log any request write before any Magento 2 business logic is executed.

Once you’re done you can enable the module from the command line and start testing the new logging functionality. The new log file should be found at app /var/log/rest_api.log and it should look similar to this:

Log file example