XF 2.3 Addon Logging Solution

QuackieMackie

Active member
The more addons I work on, the more I find myself needing logging, and creating it for each addon was just not something I wanted to do / maintain. So I made my own solution until it eventually hopefully gets added into the core functionality, a library that will contain all those bits I find myself needing at some point across multiple projects.

Currently this addon has only been used on my dev enviroment, but it won't be long before I'll be moving it to my site.

If anyone wants to take a look, wants to use it, they are welcome to it. As most of my projects are, you can find it on my sites github repo here:
View attachment CentralLogExample.Mp4
 
Or you could use Monolog which is pretty much an industry standard with the ability to link into a huge number of external systems for log tracking.
I could’ve, yep. :) Maybe in the future I’ll use a third-party library, but for my projects it felt like overkill.

Plus, the last time I worked with a third-party library in XenForo, I ended up rewriting a bunch of it to make it work, so I’m a little hesitant to go down that road again.
 
This is extremely helpful!! I was considering a similar solution myself, especially with the recent bot issues flooding our error logs

Have you considered adding admin permissions to access this page? Either per-addon, or just in general?

I am definitely going to use this in my addon, appreciate you sharing this to the community!
 
This is extremely helpful!! I was considering a similar solution myself, especially with the recent bot issues flooding our error logs

Have you considered adding admin permissions to access this page? Either per-addon, or just in general?

I am definitely going to use this in my addon, appreciate you sharing this to the community!
Adding dynamic permissions seems a little tricky. I don't think i'd be able to do this inside the library.
I think it would require the addon making logs to create a permission, but that would be possible and i'll look into adding it.

Adding permissions and bits will come soon :) I've just about finished with the looks of everything, just a few bits left I want to add such as log retention, log clearing, and exporting.
 
As pointed out by @Sim there already is an Add-on for logging, so I am not sure if reinventing the wheel is a great idea here.

At very least, please implement this to be PSR-3 compatible, especially as XenForo already ships the interfaces anyway.

If you do this, you might also want to cooperate with @Xon to get the foundation (at least logging stubs) supported via Standard Library, this way there would be at least a chance to somewhat wider adoption :)

You shoud also probably rename a bunch of phrases for compliance with resource standards rule # 25.
 
Last edited:
At very least, please implement this to be PSR-3 compatible, especially as XenForo already ships the interfaces anyway.
Thank you I haven't heard of this, I'll aim for that next.
I'm still learning the ropes for PHP and i've only worked with it is while using Xenforo so being told bits like this is helpful.

so I am not sure if reinventing the wheel is a great idea here.
Your correct, it's not needed for me to reinvent the wheel and that is by no means my intention.
I just wanted to give it a go myself rather than relying on someone elses addon. As I said i'm new to php and creating addons like what I did here helps me understand it a bit better.

I might have to take some time to read through php concepts as I seem to be missing a few fundamentals.

You shoud also probably rename a bunch of phrases for compliance with resource standards rule # 25.
I need to go over and change those still.. I am terrible for remembering this.

I'll have to look into what stubs are as well, i've never heard of this.

You've been very helpful :)
 
Last edited:
Yeah, IMHO that's a lot better but there are still two issues:

Your approach creates a tight coupling between consumers and your logging infrastructure.
If I wanted to handle for example EMERGENCY log events differently (send a SMS, post in a Slack channel, ...) I wouldn't be able to do this directly as your logger class isn't extenable via XenForo class proxy.

Also logging requires quite a bit of boilerplate code, ideally this should be easy, short and straightforward.

So I'd probably do smth. like

Code event listener app_setup
PHP:
$app->container()->factory('addOnLogger', function (string $defaultAddOnId, array $params, Container $c)
{
    $loggerClass = \XF::extendClass(AddonLogger::class);

    return new $loggerClass($c['em'], $defaultAddOnId);
});

Facade for easy access
PHP:
namespace Sylphian\Library\Logger;

use Stringable;

final class Logger
{
    public static function withDefaultAddOnId(string $defaultAddOnId): AddonLogger
    {
        $logger = \XF::app()->container()->create('addOnLogger', $defaultAddOnId);
    
        return $logger;
    }

    public static function log(mixed $level, string|Stringable $message, array $context = []): void
    {
        $logger = \XF::app()->container()->create('addOnLogger', null);
    
        $logger->log($logLevel, $message, $context);
    }

    public function emergency(string|Stringable $message, array $context = []): void
    {
        self::log(LogLevel::EMERGENCY, $context);
    }

    public function alert(string|Stringable $message, array $context = []): void
    {
        self::log(LogLevel::ALERT, $context);
    }

    public function critical(string|Stringable $message, array $context = []): void
    {
        self::log(LogLevel::CRITICAL, $context);
    }

    public function error(string|Stringable $message, array $context = []): void
    {
        self::log(LogLevel::ERROR, $context);
    }

    public function warning(string|Stringable $message, array $context = []): void
    {
        self::log(LogLevel::WARNING, $context);
    }

    public function notice(string|Stringable $message, array $context = []): void
    {
        self::notice(LogLevel::NOTICE, $context);
    }

    public function info(string|Stringable $message, array $context = []): void
    {
        self::notice(LogLevel::INFO, $context);
    }

    public function debug(string|Stringable $message, array $context = []): void
    {
        self::notice(LogLevel::DEBUG, $context);
    }
}

This would allow to just call
PHP:
Logger::log('Some message');
Logger::withDefaultAddOnId('Foo')->info('some info message');
 
Last edited:
Nice job :)

Alternative Usage​

While the static Logger class provides the most convenient way to use the logging system, you can also directly use the AddonLogger class for more advanced usage patterns.

Creating an AddonLogger Instance​

You can create an instance of the AddonLogger class directly:

PHP:
use Sylphian\Library\Logger\AddonLogger;

$logger = new AddonLogger(\XF::em(), 'Vendor/Addon');

The constructor takes two parameters:
  1. The entity manager instance
  2. (Optional) A default add-on ID to use for all logs from this instance

This should be updated to
PHP:
$logger = \XF::app()->container()->create('addOnLogger', 'Vendor/Addon');

or

PHP:
$logger = Logger::withAddOnId('Vendor/Addon');
as directly instantiating the class bypasses potential XFCP class extensions (and object caching).

If you also want to simplify & harden this you could probably adjust AddonLogger

PHP:
protected function __construct(Manager $em, ?string $defaultAddonId = null)
{
    $this->em = $em;
    $this->defaultAddonId = $defaultAddonId;
}

public static function create(?string $defaultAddonId = null): static
{
    $loggerClass = \XF::extendClass(self::class);
    return new $loggerClass(\XF::em(), $defaultAddonId);
}

and use
PHP:
AddonLogger::create('Vendor/Addon');
to instantiate.

This pattern would prevent (accidental) direct instantiation (also requires modifying the factory).
 
I'm not too experienced with addons, so I'll let someone else comment

But got a few additional questions/suggestions

1. Has this been tested for 2.x? Or just the version you were making it on?
2. It may be a good idea to only show the errors present IF there is any of those errors
1757877475789.webp (As in these, if error amount == 0, do not display)
3. Potentially consider another permission to hide debug logs? Or anything that is sensitive. If I were you, I'd consider allowing the addon to specify what logs are "sensitive" (typically debug) and then an admin permission called "View sensitive logs"
 
Has this been tested for 2.x? Or just the version you were making it on?
I have just written this for 2.3.7 I will be keeping it up to date with my server meaning the latest version of Xenforo and I won't be testing other versions as of yet. As far as i'm aware though this should work for all 2.3 versions and possibly further back. But I don't have experience with Xenforo's setup before 2.3
It may be a good idea to only show the errors present IF there is any of those errors
This has been completed already :)

Potentially consider another permission to hide debug logs? Or anything that is sensitive. If I were you, I'd consider allowing the addon to specify what logs are "sensitive" (typically debug) and then an admin permission called "View sensitive logs"
Debug logs are handled by any addon using this, but in my add-on, I did add a setting to enable debug logs to actually occur, or if they should be skipped.

I do plan on adding some type of filter options, but I haven't put the time into figuring that out yet. That would just be options like type filtering, possibly a keyword filtering, and a date filter though.

Are you suggesting a universal permission or a permission for each add-on.


No sorry I misunderstood what you were saying, correct me if i'm wrong, but it would have a multi select drop down with the types, and you'd want to be able to specify some logs such as debug, or emergency are classed as 'sensitive'. This option would be connected to a universal admin permission that would be assignable so admins can either view / no view those sensitive types.

I can understand the want for this, but what about options, if an admin is able to edit the options for the library, I'd also need a way of permission checking that.

The idea is interesting, but for now I'm not really sure this is a needed concept yet. Also having debug messages in production is bad practice in my opinion, so if an addon has debug messages left in the code, if should be made toggable by that add-on, so in a production enviroment this can be turned off.
 
Last edited:
Andy made one.
Ah, I see what you mean. In that case, what AndyB would need to do is update his code to log using this format instead:
PHP:
Logger::log(LogLevel::INFO, $searchQuery, [
    'Search constraints' => $searchConstraints,
    'Result count' => $results
]);
Once that’s in place, my add-on would automatically handle storing these logs, and it would let you view them in a table of their own. It also uses page navigation, so there wouldn’t be a need for a separate setting to limit how many results are shown.



By default, my add-on logs very little on its own. The only time it produces logs is when the cleanup cron job runs, and even then, it only generates debug logs if that setting is enabled.

The main purpose of the library is to give other add-ons a shared logging system. Instead of each add-on creating its own log table, developers can simply hook into the library’s Logger class, and it takes care of everything. This cuts down on redundant code and makes managing logs much simpler.
 
Would this be able to log member search queries ? (xenforo doesn't natively log searches. )
If you build an Add-on to to that - sure, why not.

But IMHO this wouldn't make much sense as it's a different usecase; this project is meant for system logging.

The idea is interesting, but for now I'm not really sure this is a needed concept yet. Also having debug messages in production is bad practice in my opinion, so if an addon has debug messages left in the code, if should be made toggable by that add-on, so in a production enviroment this can be turned off.
Hmm, not sure if I like the idea that every consumer should have its own setting to control logging severity.

Sure, this allows a lot flexibility - but it also complicates logging:
Instead of just calling Logger::... whenever there is something to log, the consumer whould have to check its log level setting before making the call.
So this would either mean a lot of if everywhere or a custom wrapper to check the setting before calling your code.

Wouldn't this somewhat defeat the purpose of easy to use, centralized logging?

I therefore think it might be better to have a log level setting within your code that controls the max. level that will be logged (capped to Info if debug mode is not enabled).
 
Back
Top Bottom