Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
89.72% |
96 / 107 |
|
50.00% |
5 / 10 |
CRAP | |
0.00% |
0 / 1 |
RdsStorage | |
89.72% |
96 / 107 |
|
50.00% |
5 / 10 |
17.31 | |
0.00% |
0 / 1 |
getTableName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
log | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
logMultiple | |
86.96% |
20 / 23 |
|
0.00% |
0 / 1 |
3.02 | |||
searchInstances | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
tableColumns | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
install | |
96.15% |
50 / 52 |
|
0.00% |
0 / 1 |
3 | |||
getInsertChunkSize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createInsert | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
getUserLogin | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
3.58 | |||
getUserService | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2016 (original work) Open Assessment Technologies SA; |
19 | * |
20 | * @author Alexander Zagovorichev <zagovorichev@1pt.com> |
21 | */ |
22 | |
23 | namespace oat\taoEventLog\model\eventLog; |
24 | |
25 | use oat\generis\model\user\UserRdf; |
26 | use oat\oatbox\user\User; |
27 | use oat\oatbox\user\UserService; |
28 | use oat\taoEventLog\model\LogEntity; |
29 | use Doctrine\DBAL\Schema\SchemaException; |
30 | use oat\taoEventLog\model\storage\AbstractRdsStorage; |
31 | use Throwable; |
32 | |
33 | /** |
34 | * Class RdsStorage |
35 | * @package oat\taoEventLog\model\storage |
36 | */ |
37 | class RdsStorage extends AbstractRdsStorage |
38 | { |
39 | public const EVENT_LOG_TABLE_NAME = 'event_log'; |
40 | |
41 | public const SERVICE_ID = 'taoEventLog/eventLogStorage'; |
42 | |
43 | public const OPTION_INSERT_CHUNK_SIZE = 'insertChunkSize'; |
44 | |
45 | public const EVENT_LOG_ID = self::ID; |
46 | public const EVENT_LOG_EVENT_NAME = 'event_name'; |
47 | public const EVENT_LOG_ACTION = 'action'; |
48 | public const EVENT_LOG_USER_ID = 'user_id'; |
49 | public const EVENT_LOG_USER_LOGIN = 'user_login'; |
50 | public const EVENT_LOG_USER_ROLES = 'user_roles'; |
51 | public const EVENT_LOG_OCCURRED = 'occurred'; |
52 | public const EVENT_LOG_PROPERTIES = 'properties'; |
53 | |
54 | private const DEFAULT_INSERT_CHUNK_SIZE = 100; |
55 | |
56 | private UserService $userService; |
57 | |
58 | /** |
59 | * @return string |
60 | */ |
61 | public function getTableName() |
62 | { |
63 | return self::EVENT_LOG_TABLE_NAME; |
64 | } |
65 | |
66 | /** |
67 | * @param LogEntity $logEntity |
68 | * @return bool |
69 | */ |
70 | public function log(LogEntity $logEntity) |
71 | { |
72 | $result = $this->getPersistence()->insert($this->getTableName(), $this->createInsert($logEntity)); |
73 | |
74 | return $result === 1; |
75 | } |
76 | |
77 | public function logMultiple(LogEntity ...$logEntities): bool |
78 | { |
79 | $inserts = array_map( |
80 | fn (LogEntity $logEntity): array => $this->createInsert($logEntity), |
81 | $logEntities |
82 | ); |
83 | |
84 | try { |
85 | $persistence = $this->getPersistence(); |
86 | |
87 | $persistence->transactional(function () use ($inserts, $persistence) { |
88 | $insertCount = count($inserts); |
89 | $insertChunkSize = $this->getInsertChunkSize(); |
90 | |
91 | foreach (array_chunk($inserts, $insertChunkSize) as $index => $chunk) { |
92 | $this->logDebug( |
93 | sprintf( |
94 | 'Processing chunk %d/%d with %d log entries', |
95 | $index + 1, |
96 | ceil($insertCount / $insertChunkSize), |
97 | count($chunk) |
98 | ) |
99 | ); |
100 | |
101 | $persistence->insertMultiple($this->getTableName(), $chunk); |
102 | } |
103 | }); |
104 | |
105 | return true; |
106 | } catch (Throwable $exception) { |
107 | $this->logError('Error when inserting log entries: ' . $exception->getMessage()); |
108 | |
109 | return false; |
110 | } |
111 | } |
112 | |
113 | /** |
114 | * @param array $params |
115 | * @deprecated use $this->search() instead |
116 | * @return array |
117 | */ |
118 | public function searchInstances(array $params = []) |
119 | { |
120 | return $this->search($params); |
121 | } |
122 | |
123 | /** |
124 | * @inheritdoc |
125 | */ |
126 | public static function tableColumns() |
127 | { |
128 | return [ |
129 | self::EVENT_LOG_ID, |
130 | self::EVENT_LOG_USER_ID, |
131 | self::EVENT_LOG_USER_LOGIN, |
132 | self::EVENT_LOG_USER_ROLES, |
133 | self::EVENT_LOG_EVENT_NAME, |
134 | self::EVENT_LOG_ACTION, |
135 | self::EVENT_LOG_OCCURRED, |
136 | self::EVENT_LOG_PROPERTIES |
137 | ]; |
138 | } |
139 | |
140 | /** |
141 | * @inheritdoc |
142 | */ |
143 | public static function install($persistence) |
144 | { |
145 | /** @var AbstractSchemaManager $schemaManager */ |
146 | $schemaManager = $persistence->getDriver()->getSchemaManager(); |
147 | |
148 | /** @var Schema $schema */ |
149 | $schema = $schemaManager->createSchema(); |
150 | $fromSchema = clone $schema; |
151 | |
152 | try { |
153 | $table = $schema->createTable(self::EVENT_LOG_TABLE_NAME); |
154 | $table->addOption('engine', 'MyISAM'); |
155 | |
156 | $table->addColumn( |
157 | self::EVENT_LOG_ID, |
158 | "integer", |
159 | ["notnull" => true, "autoincrement" => true, 'unsigned' => true] |
160 | ); |
161 | $table->addColumn( |
162 | self::EVENT_LOG_EVENT_NAME, |
163 | "string", |
164 | ["notnull" => true, "length" => 255, 'comment' => 'Event name'] |
165 | ); |
166 | $table->addColumn( |
167 | self::EVENT_LOG_ACTION, |
168 | "string", |
169 | ["notnull" => false, "length" => 1000, 'comment' => 'Current action'] |
170 | ); |
171 | $table->addColumn( |
172 | self::EVENT_LOG_USER_ID, |
173 | "string", |
174 | ["notnull" => false, "length" => 255, 'default' => '', 'comment' => 'User identifier'] |
175 | ); |
176 | $table->addColumn( |
177 | self::EVENT_LOG_USER_LOGIN, |
178 | 'string', |
179 | ['notnull' => false, 'length' => 255, 'default' => null, 'comment' => 'User login'] |
180 | ); |
181 | $table->addColumn( |
182 | self::EVENT_LOG_USER_ROLES, |
183 | "text", |
184 | ["notnull" => true, 'default' => '', 'comment' => 'User roles'] |
185 | ); |
186 | $table->addColumn(self::EVENT_LOG_OCCURRED, "datetime", ["notnull" => true]); |
187 | $table->addColumn( |
188 | self::EVENT_LOG_PROPERTIES, |
189 | "text", |
190 | ["notnull" => false, 'default' => '', 'comment' => 'Event properties in json'] |
191 | ); |
192 | |
193 | $table->setPrimaryKey([self::EVENT_LOG_ID]); |
194 | $table->addIndex([self::EVENT_LOG_EVENT_NAME], 'idx_event_name'); |
195 | $table->addIndex([self::EVENT_LOG_ACTION], 'idx_action', [], ['lengths' => [164]]); |
196 | $table->addIndex([self::EVENT_LOG_USER_ID], 'idx_user_id'); |
197 | $table->addIndex([self::EVENT_LOG_USER_LOGIN], 'idx_user_login'); |
198 | $table->addIndex([self::EVENT_LOG_OCCURRED], 'idx_occurred'); |
199 | } catch (SchemaException $e) { |
200 | \common_Logger::i('Database Schema for EventLog already up to date.'); |
201 | } |
202 | |
203 | $queries = $persistence->getPlatForm()->getMigrateSchemaSql($fromSchema, $schema); |
204 | foreach ($queries as $query) { |
205 | $persistence->exec($query); |
206 | } |
207 | } |
208 | |
209 | private function getInsertChunkSize(): int |
210 | { |
211 | return $this->getOption(self::OPTION_INSERT_CHUNK_SIZE, self::DEFAULT_INSERT_CHUNK_SIZE); |
212 | } |
213 | |
214 | private function createInsert(LogEntity $logEntity): array |
215 | { |
216 | return [ |
217 | self::EVENT_LOG_EVENT_NAME => $logEntity->getEvent()->getName(), |
218 | self::EVENT_LOG_ACTION => $logEntity->getAction(), |
219 | self::EVENT_LOG_USER_ID => $logEntity->getUser()->getIdentifier(), |
220 | self::EVENT_LOG_USER_LOGIN => $this->getUserLogin($logEntity->getUser()), |
221 | self::EVENT_LOG_USER_ROLES => implode(',', $logEntity->getUser()->getRoles()), |
222 | self::EVENT_LOG_OCCURRED => $logEntity->getTime()->format(self::DATE_TIME_FORMAT), |
223 | self::EVENT_LOG_PROPERTIES => json_encode($logEntity->getData()), |
224 | ]; |
225 | } |
226 | |
227 | private function getUserLogin(User $user): ?string |
228 | { |
229 | $login = current($user->getPropertyValues(UserRdf::PROPERTY_LOGIN)); |
230 | |
231 | if (!$login) { |
232 | $generisUser = $this->getUserService()->getUser($user->getIdentifier()); |
233 | |
234 | $login = current($generisUser->getPropertyValues(UserRdf::PROPERTY_LOGIN)); |
235 | } |
236 | |
237 | return $login ?: null; |
238 | } |
239 | |
240 | private function getUserService(): UserService |
241 | { |
242 | if (!isset($this->userService)) { |
243 | $this->userService = $this->getServiceManager()->get(UserService::SERVICE_ID); |
244 | } |
245 | |
246 | return $this->userService; |
247 | } |
248 | } |