Skip to content

Commit

Permalink
feat: add context support for logger
Browse files Browse the repository at this point in the history
  • Loading branch information
albertcht committed Sep 9, 2024
1 parent ba9aec7 commit b02dd7e
Show file tree
Hide file tree
Showing 5 changed files with 492 additions and 25 deletions.
18 changes: 18 additions & 0 deletions src/log/src/Events/MessageLogged.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace SwooleTW\Hyperf\Log\Events;

class MessageLogged
{
/**
* Create a new event instance.
*/
public function __construct(
public string $level,
public string $message,
public array $context = []
) {
}
}
64 changes: 61 additions & 3 deletions src/log/src/LogManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Closure;
use Hyperf\Collection\Collection;
use Hyperf\Context\Context;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Stringable\Str;
use InvalidArgumentException;
Expand All @@ -23,6 +24,7 @@
use Monolog\Processor\ProcessorInterface;
use Monolog\Processor\PsrLogMessageProcessor;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Stringable;
use SwooleTW\Hyperf\Support\Environment;
Expand Down Expand Up @@ -80,7 +82,8 @@ public function build(array $config): LoggerInterface
public function stack(array $channels, ?string $channel = null): LoggerInterface
{
return new Logger(
$this->createStackDriver(compact('channels', 'channel'))
$this->createStackDriver(compact('channels', 'channel')),
$this->app->get(EventDispatcherInterface::class)
);
}

Expand All @@ -107,7 +110,7 @@ protected function get(?string $name, ?array $config = null): LoggerInterface
{
try {
return $this->channels[$name] ?? with($this->resolve($name, $config), function ($logger) use ($name) {
return $this->channels[$name] = $this->tap($name, new Logger($logger));
return $this->channels[$name] = $this->tap($name, new Logger($logger, $this->app->get(EventDispatcherInterface::class)));
});
} catch (Throwable $e) {
return tap($this->createEmergencyLogger(), function ($logger) use ($e) {
Expand Down Expand Up @@ -153,7 +156,8 @@ protected function createEmergencyLogger(): LoggerInterface
);

return new Logger(
new Monolog('hyperf', $this->prepareHandlers([$handler]))
new Monolog('hyperf', $this->prepareHandlers([$handler])),
$this->app->get(EventDispatcherInterface::class)
);
}

Expand Down Expand Up @@ -406,6 +410,60 @@ protected function formatter(): \Monolog\Formatter\FormatterInterface
return new LineFormatter(null, $this->dateFormat, true, true, true);
}

/**
* Share context across channels and stacks.
*
* @return $this
*/
public function shareContext(array $context): self
{
foreach ($this->channels as $channel) {
$channel->withContext($context);
}

Context::override('__logger.shared_context', function ($currentContext) use ($context) {
return array_merge($currentContext ?: [], $context);
});

return $this;
}

/**
* The context shared across channels and stacks.
*/
public function sharedContext(): array
{
return (array) Context::get('__logger.shared_context', []);
}

/**
* Flush the log context on all currently resolved channels.
*
* @return $this
*/
public function withoutContext(): self
{
foreach ($this->channels as $channel) {
if (method_exists($channel, 'withoutContext')) {
$channel->withoutContext();
}
}

return $this;
}

/**
* Flush the shared context.
*
* @return $this
*/
public function flushSharedContext(): self
{
Context::destroy('__logger.shared_context');

return $this;
}

/**
* Get fallback log channel name.
*/
Expand Down
103 changes: 96 additions & 7 deletions src/log/src/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@

namespace SwooleTW\Hyperf\Log;

use Closure;
use Hyperf\Context\Context;
use Hyperf\Contract\Arrayable;
use Hyperf\Contract\Jsonable;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Stringable;
use SwooleTW\Hyperf\Log\Events\MessageLogged;

class Logger implements LoggerInterface
{
/**
* Any context to be added to logs.
*/
protected array $context = [];

/**
* Create a new log writer instance.
*/
public function __construct(
protected LoggerInterface $logger
protected LoggerInterface $logger,
protected ?EventDispatcherInterface $dispatcher = null
) {
}

Expand Down Expand Up @@ -136,8 +137,76 @@ protected function writeLog($level, string|Stringable $message, array $context):
{
$this->logger->{$level}(
$message = $this->formatMessage($message),
$context = $context
$context = array_merge($this->getContext(), $context)
);

$this->fireLogEvent($level, $message, $context);
}

/**
* Add context to all future logs.
*
* @return $this
*/
public function withContext(array $context = []): self
{
Context::override('__logger.context', function ($currentContext) use ($context) {
return array_merge($currentContext ?: [], $context);
});

return $this;
}

/**
* Flush the existing context array.
*
* @return $this
*/
public function withoutContext(): self
{
Context::destroy('__logger.context');

return $this;
}

/**
* Get the existing context array.
*
* @return array<string, mixed>
*/
public function getContext(): array
{
return (array) Context::get('__logger.context', []);
}

/**
* Register a new callback handler for when a log event is triggered.
*
* @throws RuntimeException
*/
public function listen(Closure $callback): void
{
if (! isset($this->dispatcher)) {
throw new RuntimeException('Events dispatcher has not been set.');
}

if (! method_exists($this->dispatcher, 'listen')) {
throw new RuntimeException('Events dispatcher does not implement the listen method.');
}

/* @phpstan-ignore-next-line */
$this->dispatcher->listen(MessageLogged::class, $callback);
}

/**
* Fires a log event.
*/
protected function fireLogEvent(string $level, string $message, array $context = []): void
{
// If the event dispatcher is set, we will pass along the parameters to the
// log listeners. These are useful for building profilers or other tools
// that aggregate all of the log messages for a given "request" cycle.
$this->dispatcher?->dispatch(new MessageLogged($level, $message, $context));
}

/**
Expand Down Expand Up @@ -169,6 +238,26 @@ public function getLogger(): LoggerInterface
return $this->logger;
}

/**
* Get the event dispatcher instance.
*/
public function getEventDispatcher(): EventDispatcherInterface
{
return $this->dispatcher;
}

/**
* Set the event dispatcher instance.
*
* @return $this
*/
public function setEventDispatcher(EventDispatcherInterface $dispatcher): self
{
$this->dispatcher = $dispatcher;

return $this;
}

/**
* Dynamically proxy method calls to the underlying logger.
*
Expand Down
Loading

0 comments on commit b02dd7e

Please sign in to comment.