Lines 100.00% 11 / 11
Functions and Methods 100.00% 3 / 3
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
BotTokenProcessor 100.00% 11 / 11 100.00% 3 / 3 4 100.00% 1 / 1
 __invoke 100.00% 5 / 5 100.00% 1 / 1 1
 mask 100.00% 1 / 1 100.00% 1 / 1 1
 maskAll 100.00% 5 / 5 100.00% 1 / 1 2
1<?php
2
3declare(strict_types=1);
4
5namespace App\Log;
6
7use Monolog\Attribute\AsMonologProcessor;
8use Monolog\LogRecord;
9use Monolog\Processor\ProcessorInterface;
10
11/**
12 * Masks Telegram bot tokens in log records: Telegram's API has the token in the URL
13 * (https://api.telegram.org/bot<token>/sendMessage), which the HTTP client logs. Masks the
14 * message and every string in the context and extra, in every channel and handler, so neither
15 * the log files nor the chat messages of ChatLogHandler show it.
16 */
17#[AsMonologProcessor]
18final class BotTokenProcessor implements ProcessorInterface
19{
20    private const string TOKEN = '~\bbot\d+:[A-Za-z0-9_-]+~';
21
22    public function __invoke(LogRecord $record): LogRecord
23    {
24        return $record->with(
25            message: self::mask($record->message),
26            context: $this->maskAll($record->context),
27            extra: $this->maskAll($record->extra),
28        );
29    }
30
31    public static function mask(string $text): string
32    {
33        return \preg_replace(self::TOKEN, 'bot***', $text) ?? $text;
34    }
35
36    /**
37     * @param array<mixed> $values
38     *
39     * @return array<mixed>
40     */
41    private function maskAll(array $values): array
42    {
43        \array_walk_recursive($values, static function (mixed &$value): void {
44            if (\is_string($value)) {
45                $value = self::mask($value);
46            }
47        });
48
49        return $values;
50    }
51}