Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.00% covered (success)
94.00%
94 / 100
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
RdsStorage
94.00% covered (success)
94.00%
94 / 100
57.14% covered (warning)
57.14%
4 / 7
11.03
0.00% covered (danger)
0.00%
0 / 1
 getTableName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 log
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 logMultiple
90.00% covered (success)
90.00%
27 / 30
0.00% covered (danger)
0.00%
0 / 1
3.01
 searchInstances
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 tableColumns
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 install
95.65% covered (success)
95.65%
44 / 46
0.00% covered (danger)
0.00%
0 / 1
3
 getInsertChunkSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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
23namespace oat\taoEventLog\model\eventLog;
24
25use oat\taoEventLog\model\LogEntity;
26use Doctrine\DBAL\Schema\SchemaException;
27use oat\taoEventLog\model\storage\AbstractRdsStorage;
28use Throwable;
29
30/**
31 * Class RdsStorage
32 * @package oat\taoEventLog\model\storage
33 */
34class RdsStorage extends AbstractRdsStorage
35{
36    public const EVENT_LOG_TABLE_NAME = 'event_log';
37
38    public const SERVICE_ID = 'taoEventLog/eventLogStorage';
39
40    public const OPTION_INSERT_CHUNK_SIZE = 'insertChunkSize';
41
42    public const EVENT_LOG_ID = self::ID;
43    public const EVENT_LOG_EVENT_NAME = 'event_name';
44    public const EVENT_LOG_ACTION = 'action';
45    public const EVENT_LOG_USER_ID = 'user_id';
46    public const EVENT_LOG_USER_ROLES = 'user_roles';
47    public const EVENT_LOG_OCCURRED = 'occurred';
48    public const EVENT_LOG_PROPERTIES = 'properties';
49
50    private const DEFAULT_INSERT_CHUNK_SIZE = 100;
51
52    /**
53     * @return string
54     */
55    public function getTableName()
56    {
57        return self::EVENT_LOG_TABLE_NAME;
58    }
59
60    /**
61     * @param LogEntity $logEntity
62     * @return bool
63     */
64    public function log(LogEntity $logEntity)
65    {
66        $result = $this->getPersistence()->insert(
67            $this->getTableName(),
68            [
69                self::EVENT_LOG_EVENT_NAME => $logEntity->getEvent()->getName(),
70                self::EVENT_LOG_ACTION => $logEntity->getAction(),
71                self::EVENT_LOG_USER_ID => $logEntity->getUser()->getIdentifier(),
72                self::EVENT_LOG_USER_ROLES => join(',', $logEntity->getUser()->getRoles()),
73                self::EVENT_LOG_OCCURRED => $logEntity->getTime()->format(self::DATE_TIME_FORMAT),
74                self::EVENT_LOG_PROPERTIES => json_encode($logEntity->getData()),
75            ]
76        );
77
78        return $result === 1;
79    }
80
81    public function logMultiple(LogEntity ...$logEntities): bool
82    {
83        $inserts = array_map(
84            static fn (LogEntity $logEntity): array => [
85                self::EVENT_LOG_EVENT_NAME => $logEntity->getEvent()->getName(),
86                self::EVENT_LOG_ACTION => $logEntity->getAction(),
87                self::EVENT_LOG_USER_ID => $logEntity->getUser()->getIdentifier(),
88                self::EVENT_LOG_USER_ROLES => implode(',', $logEntity->getUser()->getRoles()),
89                self::EVENT_LOG_OCCURRED => $logEntity->getTime()->format(self::DATE_TIME_FORMAT),
90                self::EVENT_LOG_PROPERTIES => json_encode($logEntity->getData()),
91            ],
92            $logEntities
93        );
94
95        try {
96            $persistence = $this->getPersistence();
97
98            $persistence->transactional(function () use ($inserts, $persistence) {
99                $insertCount = count($inserts);
100                $insertChunkSize = $this->getInsertChunkSize();
101
102                foreach (array_chunk($inserts, $insertChunkSize) as $index => $chunk) {
103                    $this->logDebug(
104                        sprintf(
105                            'Processing chunk %d/%d with %d log entries',
106                            $index + 1,
107                            ceil($insertCount / $insertChunkSize),
108                            count($chunk)
109                        )
110                    );
111
112                    $persistence->insertMultiple($this->getTableName(), $chunk);
113                }
114            });
115
116            return true;
117        } catch (Throwable $exception) {
118            $this->logError('Error when inserting log entries: ' . $exception->getMessage());
119
120            return false;
121        }
122    }
123
124    /**
125     * @param array $params
126     * @deprecated use $this->search() instead
127     * @return array
128     */
129    public function searchInstances(array $params = [])
130    {
131        return $this->search($params);
132    }
133
134    /**
135     * @inheritdoc
136     */
137    public static function tableColumns()
138    {
139        return [
140            self::EVENT_LOG_ID,
141            self::EVENT_LOG_USER_ID,
142            self::EVENT_LOG_USER_ROLES,
143            self::EVENT_LOG_EVENT_NAME,
144            self::EVENT_LOG_ACTION,
145            self::EVENT_LOG_OCCURRED,
146            self::EVENT_LOG_PROPERTIES
147        ];
148    }
149
150    /**
151     * @inheritdoc
152     */
153    public static function install($persistence)
154    {
155        /** @var AbstractSchemaManager $schemaManager */
156        $schemaManager = $persistence->getDriver()->getSchemaManager();
157
158        /** @var Schema $schema */
159        $schema = $schemaManager->createSchema();
160        $fromSchema = clone $schema;
161
162        try {
163            $table = $schema->createTable(self::EVENT_LOG_TABLE_NAME);
164            $table->addOption('engine', 'MyISAM');
165
166            $table->addColumn(
167                self::EVENT_LOG_ID,
168                "integer",
169                ["notnull" => true, "autoincrement" => true, 'unsigned' => true]
170            );
171            $table->addColumn(
172                self::EVENT_LOG_EVENT_NAME,
173                "string",
174                ["notnull" => true, "length" => 255, 'comment' => 'Event name']
175            );
176            $table->addColumn(
177                self::EVENT_LOG_ACTION,
178                "string",
179                ["notnull" => false, "length" => 1000, 'comment' => 'Current action']
180            );
181            $table->addColumn(
182                self::EVENT_LOG_USER_ID,
183                "string",
184                ["notnull" => false, "length" => 255, 'default' => '', 'comment' => 'User identifier']
185            );
186            $table->addColumn(
187                self::EVENT_LOG_USER_ROLES,
188                "text",
189                ["notnull" => true, 'default' => '', 'comment' => 'User roles']
190            );
191            $table->addColumn(self::EVENT_LOG_OCCURRED, "datetime", ["notnull" => true]);
192            $table->addColumn(
193                self::EVENT_LOG_PROPERTIES,
194                "text",
195                ["notnull" => false, 'default' => '', 'comment' => 'Event properties in json']
196            );
197
198            $table->setPrimaryKey([self::EVENT_LOG_ID]);
199            $table->addIndex([self::EVENT_LOG_EVENT_NAME], 'idx_event_name');
200            $table->addIndex([self::EVENT_LOG_ACTION], 'idx_action', [], ['lengths' => [164]]);
201            $table->addIndex([self::EVENT_LOG_USER_ID], 'idx_user_id');
202            $table->addIndex([self::EVENT_LOG_OCCURRED], 'idx_occurred');
203        } catch (SchemaException $e) {
204            \common_Logger::i('Database Schema for EventLog already up to date.');
205        }
206
207        $queries = $persistence->getPlatForm()->getMigrateSchemaSql($fromSchema, $schema);
208        foreach ($queries as $query) {
209            $persistence->exec($query);
210        }
211    }
212
213    private function getInsertChunkSize(): int
214    {
215        return $this->getOption(self::OPTION_INSERT_CHUNK_SIZE, self::DEFAULT_INSERT_CHUNK_SIZE);
216    }
217}