1<?php declare(strict_types=1);
2
3/*
4 * This file is part of the Monolog package.
5 *
6 * (c) Jordi Boggiano <j.boggiano@seld.be>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Monolog\Handler;
13
14use Monolog\Formatter\LineFormatter;
15use Monolog\Formatter\FormatterInterface;
16use Monolog\Logger;
17
18/**
19 * Logs to a Redis key using rpush
20 *
21 * usage example:
22 *
23 *   $log = new Logger('application');
24 *   $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod");
25 *   $log->pushHandler($redis);
26 *
27 * @author Thomas Tourlourat <thomas@tourlourat.com>
28 *
29 * @phpstan-import-type FormattedRecord from AbstractProcessingHandler
30 */
31class RedisHandler extends AbstractProcessingHandler
32{
33    /** @var \Predis\Client|\Redis */
34    private $redisClient;
35    /** @var string */
36    private $redisKey;
37    /** @var int */
38    protected $capSize;
39
40    /**
41     * @param \Predis\Client|\Redis $redis   The redis instance
42     * @param string                $key     The key name to push records to
43     * @param int                   $capSize Number of entries to limit list size to, 0 = unlimited
44     */
45    public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true, int $capSize = 0)
46    {
47        if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) {
48            throw new \InvalidArgumentException('Predis\Client or Redis instance required');
49        }
50
51        $this->redisClient = $redis;
52        $this->redisKey = $key;
53        $this->capSize = $capSize;
54
55        parent::__construct($level, $bubble);
56    }
57
58    /**
59     * {@inheritDoc}
60     */
61    protected function write(array $record): void
62    {
63        if ($this->capSize) {
64            $this->writeCapped($record);
65        } else {
66            $this->redisClient->rpush($this->redisKey, $record["formatted"]);
67        }
68    }
69
70    /**
71     * Write and cap the collection
72     * Writes the record to the redis list and caps its
73     *
74     * @phpstan-param FormattedRecord $record
75     */
76    protected function writeCapped(array $record): void
77    {
78        if ($this->redisClient instanceof \Redis) {
79            $mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1;
80            $this->redisClient->multi($mode)
81                ->rpush($this->redisKey, $record["formatted"])
82                ->ltrim($this->redisKey, -$this->capSize, -1)
83                ->exec();
84        } else {
85            $redisKey = $this->redisKey;
86            $capSize = $this->capSize;
87            $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) {
88                $tx->rpush($redisKey, $record["formatted"]);
89                $tx->ltrim($redisKey, -$capSize, -1);
90            });
91        }
92    }
93
94    /**
95     * {@inheritDoc}
96     */
97    protected function getDefaultFormatter(): FormatterInterface
98    {
99        return new LineFormatter();
100    }
101}
102