Skip to content

Instantly share code, notes, and snippets.

@aklump
Last active September 4, 2026 21:29
Show Gist options
  • Select an option

  • Save aklump/a90467ce56d3f555c2af6f8c9a11e159 to your computer and use it in GitHub Desktop.

Select an option

Save aklump/a90467ce56d3f555c2af6f8c9a11e159 to your computer and use it in GitHub Desktop.
#ClassLoggerTrait.php# Provides class-scoped logging helpers for Drupal custom classes.
<?php
namespace Drupal\JIG__EXTENSION_NAME\Logger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* Provides class-scoped logging helpers.
*
* Classes using this trait get a class logger channel derived from the
* consuming class name. The channel uses the short class name so Drupal's log
* UI remains easy to scan and filter. Long class names are shortened and given
* a stable hash suffix to stay within watchdog channel length limits while
* reducing collision risk.
*
* These helpers are intentionally static. The logger channel is derived from
* static::class, not object state, and some Drupal extension points such as
* entity callbacks and static utility methods need to log when no object
* instance is available. Instance methods can still call these helpers with
* static::classLogger() or static::logClassException().
*
* The class logger channel name is public so callers can use the same
* class-derived channel with Drupal's logging APIs directly when they need
* behavior not provided by this trait.
*
* The exception helper also provides a single migration point from Drupal 9's
* watchdog_exception() to \Drupal\Core\Utility\Error::logException(). Callers
* should use static::logClassException() instead of calling either API directly
* when class-scoped exception logging is sufficient; the implementation can
* then switch to Drupal's newer exception logging API without changing
* consuming classes.
*
* The exception helper is intentionally lightweight and PSR-3 oriented. It is
* intended for quick, severity-aware exception logging during normal
* application flow. Use \Drupal\Core\Utility\Error::logException() directly
* when Drupal's full exception logging format is required.
*/
trait ClassLoggerTrait {
/**
* Gets the logger channel name for the calling class.
*
* The short class name is used because Drupal's log UI exposes the channel as
* a primary scanning/filtering column. Long names are shortened with a hash
* suffix to avoid watchdog channel length issues while reducing collision
* risk.
*/
public static function classLoggerChannelName(): string {
static $channels = [];
$class = static::class;
if (!isset($channels[$class])) {
$namespace_position = strrpos($class, '\\');
$short_name = $namespace_position === FALSE
? $class
: substr($class, $namespace_position + 1);
if (strlen($short_name) > 32) {
$short_name = substr($short_name, 0, 25) . '_' . substr(hash('crc32b', $class), 0, 6);
}
$channels[$class] = $short_name;
}
return $channels[$class];
}
/**
* Gets a logger instance set to this class's logger channel.
*
* Intentionally do not statically cache the classLogger factory. Drupal's
* container already returns shared and/or optimized service instances, and
* keeping our own copy can make tests/container rebuilds harder to reason
* about for negligible gain.
*/
protected static function classLogger(): LoggerInterface {
return \Drupal::logger(static::classLoggerChannelName());
}
/**
* Logs an exception using the class-derived logger channel.
*
* @param \Throwable $exception
* The exception or error to log.
* @param string $level
* A PSR-3 log level from \Psr\Log\LogLevel.
* @param array $context
* Additional context variables for the log message.
*/
protected static function logClassException(\Throwable $exception, string $level = LogLevel::ERROR, array $context = []): void {
if ($exception instanceof \Exception) {
watchdog_exception(static::classLoggerChannelName(), $exception, NULL, $context, $level);
return;
}
// Drupal 9's watchdog_exception() only accepts \Exception. Log other
// \Throwable instances directly until this can use Error::logException().
$context += [
'exception' => $exception,
'@type' => get_class($exception),
'@message' => $exception->getMessage(),
'@file' => $exception->getFile(),
'@line' => $exception->getLine(),
];
static::classLogger()
->log($level, '@type: @message in @file on line @line', $context);
// TODO Drupal 10+: replace this method body with:
// \Drupal\Core\Utility\Error::logException(static::classLogger(), $exception, \Drupal\Core\Utility\Error::DEFAULT_ERROR_MESSAGE, $context, $level);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment