Logging

In Flow logging is implemented according to the PSR-3 Standard. This means you can use any logging facility that implements this interface. The concrete implementation can easily be configured with the Object Management of Flow. By default, Flow comes with an PSR-3 implementation in the Neos.Flow.Log package that can be configured with one or more storage backends and supports log rotation. The supported backends currently consist of a FileBackend, (Ansi)ConsoleBackend, JsonFileBackend and a NullBackend.

Default loggers

By default Flow comes with four loggers, the so called “system logger”, “security logger”, “sql logger” and “i18n logger”. As the names imply, the first is responsible for logging general system level messages and is used by default if an instance of the LoggerInterface is injected. The second for logging security related information. The SQL logger needs to be enabled first via Neos.Flow.persistence.doctrine.sqlLogger setting and will create a log of all database queries, so this can become a big performance penalty and should only be used for debugging purposes. Last but not least is the i18n logger which will log away all messages related to the translation framework, for example when the XLIFF translation sources are badly formatted. Under the hood they all use a FileBackend for storing the messages, but that can be configured differently via the settings keys Neos.Flow.log.psr3.Neos\Flow\Log\PsrLoggerFactory.*.

Loggers can be used to record events that happen in an application, for example a failed login attempt or a caught exception. To make use of a logger, the Psr\Log\LoggerInterface can be injected, that refers to the SystemLogger which – by default – persists any log message in the file system:

      use Psr\Log\LoggerInterface;
...

      /**
       * @Flow\Inject(name="Neos.Flow:SystemLogger")
       * @var LoggerInterface
       */
      protected $systemLogger;

      /**
       * @Flow\Inject(name="Neos.Flow:SecurityLogger")
       * @var LoggerInterface
       */
      protected $securityLogger;

      /**
       * @Flow\Inject(name="Neos.Flow:SqlLogger")
       * @var LoggerInterface
       */
      protected $sqlLogger;

      /**
       * @Flow\Inject(name="Neos.Flow:I18nLogger")
       * @var LoggerInterface
       */
      protected $i18nLogger;

This is achieved via the virtual objects configuration that allows to configure a single class in multiple versions with different constructor arguments and assign a name for this configuration, which can be referenced in the @Flow\Inject annotation.

If you just need a default logger and don’t really care for the specific type of logger, you can also skip the (name="Neos.Flow:...") part and you will receive an instance of the SystemLogger by default:

/**
 * @Flow\Inject
 * @var LoggerInterface
 */
protected $logger;

Alternatively, if you prefer to keep your class free of framework specific annotations (@Flow\Inject(...)), you could as well just inject the specific configuration of the logger via the Objects.yaml like this:

Acme\Your\Class:
  properties:
    systemLogger:
      object:
        name: 'Neos.Flow:SystemLogger'

Exception logging

One of the primary use case for logging is exceptions that need to be stored for later inspection. You might want to just append the exception message (and maybe the stack trace) as a message to your logger instance. However, this is not recommended for multiple reasons. Most notably the exception stack trace is very verbose and clutters the log and you might also want to log further information about the environment in which the exception happened, like the HTTP request. But alone from a conceptual perspective, storing an exception is slightly different from logging information about what your application is doing. A log message is a text, while an exception trace is basically a document. That’s why Flow provides a so called ThrowableStorageInterface that you can implement and use for storing exception messages side by side to your logs. The most important method of this interface is the logThrowable() method, which takes a Throwable class and an array of additional data and is supposed to return a string message that should end up in your primary logger. This message should then refer to the location where the exception is stored, so you can find it later on.

By default, Flow comes with a FileStorage implementation, which will write the exception to the file system with a unique name and information about the request and PHP process. You might already have stumbled across such an exception (though hopefully not!) inside the Data/Logs/Exceptions directory of your Flow installation:

Exception: Argument 1 passed to Neos\Flow\Http\Middleware\MiddlewaresChain_Original::__construct() must be of the type string, array given

10 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::__construct(array|0|, array|3|)
9 call_user_func_array("parent::__construct", array|2|)
8 Neos\Flow\Http\Middleware\MiddlewaresChain::__construct(array|0|, array|3|)
7 Neos\Flow\Http\Middleware\MiddlewaresChainFactory_Original::create(array|3|, array|0|)
6 call_user_func_array(array|2|, array|2|)
5 Neos\Flow\ObjectManagement\ObjectManager::buildObjectByFactory("Neos\Flow\Http\Middleware\MiddlewaresChain")
4 Neos\Flow\ObjectManagement\ObjectManager::get("Neos\Flow\Http\Middleware\MiddlewaresChain")
3 Neos\Flow\Http\RequestHandler::resolveDependencies()
2 Neos\Flow\Http\RequestHandler::handleRequest()
1 Neos\Flow\Core\Bootstrap::run()


HTTP REQUEST:
127.0.0.1:8081keep-aliveno-cacheno-cacheimageMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.361image/webp,image/apng,image/*,*/*;q=0.8same-originno-corshttp://127.0.0.1:8081/flow/welcomegzip, deflate, brde-DE,de;q=0.9,en-US;q=0.8,en;q=0.7

PHP PROCESS:
Inode:
PID: 2296
UID: 1
GID: 1
User:

Note

As of Flow 7.1 you can configure if the HTTP Request should be logged in the default ThrowableStorage via the boolean setting Neos.Flow.log.throwables.renderRequestInformation. This could be a requirement in order to prevent storing personally identifiable information that is submitted in the request headers or the URL.

In order to log such exceptions yourself you have to inject both a ThrowableStorageInterface as well as a LoggerInterface at a place where you can reach them from your try/catch block. This would roughly look as follows:

use Neos\Flow\Log\ThrowableStorageInterface;
use Psr\Log\LoggerInterface;

...

/**
 * @Flow\Inject
 * @var ThrowableStorageInterface
 */
protected $throwableStorage;

/**
 * @Flow\Inject
 * @var LoggerInterface
 */
protected $logger;

...

public function trySomething()
{
        try {
                ...
        } catch (\Throwable $exception) {
                $logMessage = $this->throwableStorage->logThrowable($exception);
                $this->logger->error($logMessage, LogEnvironment::fromMethodName(__METHOD__));
        }
}

The LogEnvironment::fromMethodName(__METHOD__) is a helper that builds an additional data array for the log in the structure of:

[
                'FLOW_LOG_ENVIRONMENT' => [
                                'packageKey' => PackageKeyFromClassName($className),
                                'className' => $className,
                                'methodName' => $functionName
                ]
]

This is used so the log contains helpful information about where the log is coming from. It derives the package key from the namespace of the method (__METHOD__) the log is called from. Of course you can freely customize the additional context and everything in the array will be serialized and formatted into your log with the backends provided through the Neos.Flow.Log package. Just don’t use the FLOW_LOG_ENVIRONMENT key, as that is used internally and only accepts the three keys above.