Passed
Push — master ( 89f24e...8c4dad )
by Sylvain
08:05
created

EventCompleter   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Test Coverage

Coverage 83.67%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 49
c 1
b 0
f 0
dl 0
loc 110
ccs 41
cts 49
cp 0.8367
rs 10
wmc 14

5 Methods

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