Hi folks. After a long and productive call with Benni, I want to offer yet another plan for modernizing logging in TYPO3.
The root issues to resolve are:
- Until last week, we were misusing PSR-3. That was largely resolved in this issue.
- Much of the system is not actually using PSR-3. It’s using the old
sys_log
table directly for both reads and writes, which bypasses all of the logging mechanisms. See this post on the places that do so, which are not all clear on what they do. - The current log system is complex to configure, and relies very heavily on class names for creating “virtual channels.” Yet class names are not a great key in practice, and their configuration is incompatible with using constructor dependency injection for loggers.
- There are systems in core now that really do need log-esque information to be available in the UI, but not all logs necessarily need to be in the UI. (That’s a per-site decision.)
- The “type” field is a legacy that is largely unuseful, as it’s nominally extensible but in practice is a fixed set of integer values, which are therefore much less readable and decipherable.
- A logging system is a commodity. There’s little value to TYPO3 in having its own, in practice, other than one already exists. But then we have to maintain it, and it is weaker than others that already exist.
The proposed solution is to effectively replicate what Symfony does for its logging “channels”, with a TYPO3 spin on it to keep the sys_log
table working mostly the same as it does now.
The steps involved would be as follows:
-
Add a
channel
field tosys_log
, as a string field. Existing “types” would get mapped to new string channel names. There’s only 3-4 places that currently write to that table, so that can just be done manually in each of those locations for now. (cf: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69656 and https://review.typo3.org/c/Packages/TYPO3.CMS/+/69684) -
Update
BackendUserAuthentication::writeLog
andDataHandler
get their own dedicated log channels. (cf: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69656) -
Update ext-belog to use the new
channel
field as a filterable field, instead oftype
.type
becomes deprecated. (cf: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69663) -
Add Monolog as a dependency. Wire that into the container in a similar fashion as Symfony does. That is, each logger “channel” gets its own DI entry, and each one can have its own set of writers and processors.
-
Write a new Monolog writer/handler that writes to the
sys_log
table. This may or may not also involve additional Processors, TBD. -
Write a Compiler Pass that has a hard coded list of channels that always have the new
sys_log
writer configured, and it cannot be disabled. That list is something we can debate later but it would include at least login attempts, page writes, and other “editor actions,” which are the most important to show up in the UI. -
Update
LogManager
to be Container Aware; then, ifgetLogger()
is called with a known channel, return that Monolog channel instance instead. Otherwise, do what it does now. -
Update the DI system to allow services to request a specific logger channel through the variable name, as Symfony does. (I’m not certain exactly how that works right now, but presumably we can just copy their code for it, whatever it is.)
-
Add a default channel logger service that gets used if a channel isn’t specified. This logger would accept a
channel
context key and forward the log message to the appropriate channel. -
Convert key systems to take a logger via constructor DI rather than
LoggerAware
, so they get the new channel-based system. This includesBackendUserAuthentication
and friends, so they stop writing directly tosys_log
. -
Over time, convert everything else to use constructor DI for logging rather than
LoggerAware
. Some of this may wait for v12, simply because then we can require PHP 8.0 where constructor property promotion will make it mechanically easier to do. (Or not; it’s not hard, just will take some typing.) -
At some point in the future (v12 or later), drop the old logger implementation.
-
Revisit
sys_log
and possibly change it to something more purpose-built. TBD, this is future scope.
The net result when the dust settles:
- More services are using clean DI, which makes them more mockable and testable. A trivial mock logger is easy to write, and the in-progress
fig/log-util
package will likely include one we can just use as well. - We no longer have to maintain a logging implementation of our own. Instead, we use one with widespread support and a zillion handlers/writers already available for a wide array of backends.
- We have a logging system that is more straightforward to configure, and familiar to developers from other systems
The list of channels is naturally configurable, but I would propose the following out-of-the-box.
- default (effectively “other”, and what 3rd party libs would likely use. Would force sys_log_writer)
- user (login, logout, bad password, etc… Would force sys_log_writer)
- file (file system related issues.)
- php (any PHP notice/warning/errors we can catch)
- deprecated (both E_USER_DEPRECATED and E_DEPRECATED)
- content (page created, edited, deletec, etc.)
- security (possible security issue, rare)
The existing SysLog::Type entries would map as follows:
- DB → content
- File → file
- Cache → default (it’s only used once, and shouldn’t be its own thing)
- Extension → default (use cases can/should get rewritten to a proper log call)
- Error → php (these all come from error handlers right now)
- Setting → default (this is only used once, in system setup)
- Login → user
For reference, these are the channels I found used by default by other systems:
Symfony:
- main
- console
Drupal:
- default
- php
- image
- cron
- file
- form
- security
- $module_name