Passed
Push — master ( 25e095...1722f6 )
by Adrien
02:54
created

EventCompleter   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Test Coverage

Coverage 87.76%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 13
eloc 45
c 1
b 0
f 0
dl 0
loc 102
ccs 43
cts 49
cp 0.8776
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getStacktrace() 0 26 4
A process() 0 14 2
A __construct() 0 2 1
A getEnvData() 0 28 3
A removeSensitiveData() 0 10 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Log;
6
7
use Ecodev\Felix\Model\CurrentUser;
8
use Laminas\Log\Processor\ProcessorInterface;
9
10
class EventCompleter implements ProcessorInterface
11
{
12 5
    public function __construct(private readonly string $baseUrl)
13
    {
14 5
    }
15
16
    /**
17
     * Complete a log event with extra data, including stacktrace and any global stuff relevant to the app.
18
     */
19 4
    public function process(array $event): array
20
    {
21 4
        $envData = $this->getEnvData();
22 4
        $event = array_merge($event, $envData);
23
24
        // If we are logging PHP errors, then we include all known information in message
25 4
        if ($event['extra']['errno'] ?? false) {
26 1
            $event['message'] .= "\nStacktrace:\n" . $this->getStacktrace();
27
        }
28
29
        // Security hide clear text password
30 4
        unset($event['extra']['password']);
31
32 4
        return $event;
33
    }
34
35
    /**
36
     * Retrieve dynamic information from environment to be logged.
37
     */
38 4
    private function getEnvData(): array
39
    {
40 4
        $user = CurrentUser::get();
41
42 4
        if (PHP_SAPI === 'cli') {
43
            global $argv;
44 4
            $ip = !empty(getenv('REMOTE_ADDR')) ? getenv('REMOTE_ADDR') : 'script';
45 4
            $url = implode(' ', $argv);
46 4
            $referer = '';
47
        } else {
48
            $ip = $_SERVER['REMOTE_ADDR'] ?? '';
49
            $url = $this->baseUrl . $_SERVER['REQUEST_URI'];
50
            $referer = $_SERVER['HTTP_REFERER'] ?? '';
51
        }
52
53 4
        $request = $_REQUEST;
54 4
        $request = $this->removeSensitiveData($request);
55
56 4
        $envData = [
57 4
            'creator_id' => $user?->getId(),
58 4
            'login' => $user?->getLogin(),
59 4
            'url' => $url,
60 4
            'referer' => $referer,
61 4
            'request' => json_encode($request, JSON_PRETTY_PRINT),
62 4
            'ip' => $ip,
63 4
        ];
64
65 4
        return $envData;
66
    }
67
68
    /**
69
     * Remove password value from GraphQL variables well-known structure.
70
     */
71 4
    protected function removeSensitiveData(array $request): array
72
    {
73 4
        unset($request['password']);
74 4
        foreach ($request as &$r) {
75 2
            if (is_array($r)) {
76 1
                $r = $this->removeSensitiveData($r);
77
            }
78
        }
79
80 4
        return $request;
81
    }
82
83
    /**
84
     * Returns the backtrace excluding the most recent calls to this function, so we only get the interesting parts.
85
     */
86 1
    private function getStacktrace(): string
87
    {
88 1
        ob_start();
89 1
        @debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
90 1
        $trace = ob_get_contents();
91 1
        ob_end_clean();
92
93 1
        if ($trace === false) {
94
            return 'Could not get stacktrace';
95
        }
96
97
        // Remove first items from backtrace as it's this function and previous logging functions which is not interesting
98 1
        $shortenTrace = preg_replace('/^#[0-4]\s+[^\n]*\n/m', '', $trace);
99
100 1
        if ($shortenTrace === null) {
101
            return $trace;
102
        }
103
104
        // Renumber backtrace items.
105 1
        $renumberedTrace = preg_replace_callback('/^#(\d+)/m', fn ($matches) => '#' . ((int) $matches[1] - 5), $shortenTrace);
106
107 1
        if ($renumberedTrace === null) {
108
            return $shortenTrace;
109
        }
110
111 1
        return $renumberedTrace;
112
    }
113
}
114