LogTrait   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 51
c 2
b 0
f 1
dl 0
loc 161
rs 10
wmc 21

8 Methods

Rating   Name   Duplication   Size   Complexity  
A logResponse() 0 14 2
A initLogger() 0 11 2
A responseBodyCleanup() 0 15 4
A requestBodyCleanup() 0 13 3
A logRequest() 0 14 2
A maskPasswordField() 0 8 3
A requestHeadersCleanup() 0 10 4
A getLogger() 0 3 1
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2018 ChannelWeb Srl, Chialab Srl
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 */
12
13
namespace BEdita\SDK;
14
15
use Monolog\Handler\StreamHandler;
16
use Monolog\Logger;
17
use Psr\Http\Message\RequestInterface;
18
use Psr\Http\Message\ResponseInterface;
19
20
/**
21
 * Basic SDK logging functions
22
 */
23
trait LogTrait
24
{
25
    /**
26
     * internal Logger
27
     *
28
     * @var \Monolog\Logger|null
29
     */
30
    protected ?Logger $logger = null;
31
32
    /**
33
     * Get configured logger, may be null
34
     *
35
     * @return \Monolog\Logger|null
36
     * @codeCoverageIgnore
37
     */
38
    public function getLogger(): ?Logger
39
    {
40
        return $this->logger;
41
    }
42
43
    /**
44
     * Initialize and configure logger
45
     *
46
     * @param array $options Configuration options, 'log_file' key with log file path is mandatory
47
     * @return bool True on successful initialization, false otherwise
48
     */
49
    public function initLogger(array $options): bool
50
    {
51
        // 'path' to log file is mandatory
52
        if (empty($options['log_file'])) {
53
            return false;
54
        }
55
56
        $this->logger = new Logger('be4-php-sdk');
57
        $this->logger->pushHandler(new StreamHandler($options['log_file'], Logger::DEBUG));
58
59
        return true;
60
    }
61
62
    /**
63
     * Perform request log
64
     *
65
     * @param \Psr\Http\Message\RequestInterface $request The request to log
66
     * @return void
67
     */
68
    public function logRequest(RequestInterface $request): void
69
    {
70
        if (!$this->logger) {
71
            return;
72
        }
73
74
        $msg = sprintf(
75
            'Request: %s %s - Headers %s - Body %s',
76
            $request->getMethod(),
77
            $request->getUri(),
78
            $this->requestHeadersCleanup($request),
79
            $this->requestBodyCleanup($request)
80
        );
81
        $this->logger->info($msg);
82
    }
83
84
    /**
85
     * Return request body without sensitive information.
86
     *
87
     * @param \Psr\Http\Message\RequestInterface $request The request to log
88
     * @return string
89
     */
90
    protected function requestBodyCleanup(RequestInterface $request): string
91
    {
92
        $body = $request->getBody()->getContents();
93
        if (empty($body)) {
94
            return '(empty)';
95
        }
96
97
        $data = (array)json_decode($body, true);
98
        foreach (['password', 'old_password', 'confirm-password'] as $field) {
99
            $this->maskPasswordField($data, $field);
100
        }
101
102
        return json_encode($data);
103
    }
104
105
    /**
106
     * Mask password fields in $data.
107
     *
108
     * @param array $data The data
109
     * @param string $field The field
110
     * @return void
111
     */
112
    public function maskPasswordField(array &$data, string $field): void
113
    {
114
        $mask = '***************';
115
        if (!empty($data[$field])) {
116
            $data[$field] = $mask;
117
        }
118
        if (!empty($data['data']['attributes'][$field])) {
119
            $data['data']['attributes'][$field] = $mask;
120
        }
121
    }
122
123
    /**
124
     * Return request headers as string without sensitive information.
125
     *
126
     * @param \Psr\Http\Message\RequestInterface $request The request to log
127
     * @return string
128
     */
129
    protected function requestHeadersCleanup(RequestInterface $request): string
130
    {
131
        $headers = $request->getHeaders();
132
        foreach (['Authorization', 'X-Api-Key'] as $h) {
133
            if (!empty($headers[$h]) && !empty(array_diff($headers[$h], ['']))) {
134
                $headers[$h] = ['***************'];
135
            }
136
        }
137
138
        return json_encode($headers);
139
    }
140
141
    /**
142
     * Perform response log
143
     *
144
     * @param \Psr\Http\Message\ResponseInterface $response The response to log
145
     * @return void
146
     */
147
    public function logResponse(ResponseInterface $response): void
148
    {
149
        if (!$this->logger) {
150
            return;
151
        }
152
153
        $msg = sprintf(
154
            'Response: %s %s - Headers %s - Body %s',
155
            $response->getStatusCode(),
156
            $response->getReasonPhrase(),
157
            json_encode($response->getHeaders()),
158
            $this->responseBodyCleanup($response)
159
        );
160
        $this->logger->info($msg);
161
    }
162
163
    /**
164
     * Return response body without sensitive information.
165
     *
166
     * @param \Psr\Http\Message\ResponseInterface $response The response to log
167
     * @return string
168
     */
169
    protected function responseBodyCleanup(ResponseInterface $response): string
170
    {
171
        $body = $response->getBody()->getContents();
172
        if (empty($body)) {
173
            return '(empty)';
174
        }
175
176
        $data = (array)json_decode($body, true);
177
        foreach (['jwt', 'renew'] as $tok) {
178
            if (!empty($data['meta'][$tok])) {
179
                $data['meta'][$tok] = '***************';
180
            }
181
        }
182
183
        return json_encode($data);
184
    }
185
}
186