Skip to content

Commit 8671729

Browse files
Merge pull request #649 from CleanTalk/loggin_queue_upd.ag
loggin_queue_upd.ag
2 parents f900982 + c2ebac4 commit 8671729

9 files changed

Lines changed: 700 additions & 60 deletions

File tree

inc/fw-update.php

Lines changed: 104 additions & 48 deletions
Large diffs are not rendered by default.

lib/CleantalkSP/Common/Logger.php

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
<?php
2+
3+
namespace CleantalkSP\Common;
4+
5+
use RuntimeException;
6+
7+
/**
8+
* Abstract base class for logging functionality.
9+
* Provides core logging structure and methods for record handling,
10+
* time management, and log limit enforcement.
11+
*/
12+
abstract class Logger
13+
{
14+
/**
15+
* @var string Fully qualified class name of the record provider
16+
*/
17+
protected $record_provider;
18+
19+
/**
20+
* @var string Name of the time provider function (e.g., 'time', 'microtime')
21+
*/
22+
protected $time_provider;
23+
24+
/**
25+
* @var int Maximum number of log entries allowed before blocking new writes
26+
*/
27+
protected static $log_limit = 300;
28+
29+
/**
30+
* Is limit reached statically
31+
* @var bool
32+
*/
33+
private $limits_reached = false;
34+
35+
/**
36+
* Constructor.
37+
*
38+
* @param string|LoggerRecord|null $record_provider Record provider class name or instance
39+
* @param string $time_provider Callable function name that returns a timestamp
40+
*
41+
* @throws RuntimeException
42+
*/
43+
public function __construct($record_provider = null, $time_provider = 'time')
44+
{
45+
if ($record_provider === null) {
46+
$this->record_provider = LoggerRecord::class;
47+
} elseif ($record_provider instanceof LoggerRecord) {
48+
$this->record_provider = get_class($record_provider);
49+
} elseif (
50+
is_string($record_provider) &&
51+
class_exists($record_provider) &&
52+
is_a($record_provider, LoggerRecord::class, true)
53+
) {
54+
$this->record_provider = $record_provider;
55+
} else {
56+
throw new RuntimeException('Invalid record provider');
57+
}
58+
59+
$this->time_provider = $time_provider;
60+
}
61+
62+
/**
63+
* Persist a log record to storage.
64+
*
65+
* @param LoggerRecord $record The log record to store
66+
* @return void
67+
* @psalm-suppress PossiblyUnusedMethod
68+
*/
69+
abstract public static function updateStorage($record);
70+
71+
/**
72+
* Retrieve all log records from storage.
73+
*
74+
* @return LoggerRecord[] Array of log records
75+
* @psalm-suppress PossiblyUnusedMethod
76+
*/
77+
abstract public static function loadStorage();
78+
79+
/**
80+
* Remove all log records from storage.
81+
*
82+
* @return void
83+
* @psalm-suppress PossiblyUnusedMethod
84+
*/
85+
abstract public static function clearStorage();
86+
87+
/**
88+
* Instantiate and return a new log record object.
89+
*
90+
* @return LoggerRecord New log record instance
91+
* @throws RuntimeException
92+
*/
93+
protected function getLogRecordProvider()
94+
{
95+
if (!class_exists($this->record_provider)) {
96+
throw new RuntimeException('Invalid record provider');
97+
}
98+
99+
return new $this->record_provider();
100+
}
101+
102+
/**
103+
* Get formatted timestamp using the configured time provider.
104+
*
105+
* @return string Formatted date string (Y-m-d H:i:s) or 'invalid time' on error
106+
* @psalm-suppress PossiblyUnusedMethod
107+
*/
108+
protected function getTime()
109+
{
110+
// Call the configured time function (e.g., 'time', 'microtime')
111+
if (function_exists($this->time_provider)) {
112+
$timestamp = $this->time_provider === 'microtime'
113+
? microtime(true)
114+
: call_user_func($this->time_provider);
115+
} else {
116+
$timestamp = time();
117+
}
118+
119+
// Convert integer timestamp to readable format
120+
$time = null;
121+
122+
if (is_int($timestamp)) {
123+
$time = date('Y-m-d H:i:s', $timestamp);
124+
} elseif (is_float($timestamp)) {
125+
$time = date('Y-m-d H:i:s', (int)$timestamp);
126+
}
127+
128+
// Fallback for invalid timestamp values
129+
if (!is_string($time)) {
130+
$time = 'invalid time';
131+
}
132+
133+
return $time;
134+
}
135+
136+
/**
137+
* Write a log entry to storage.
138+
* Respects the log limit - if limit is reached, no new entry is written.
139+
*
140+
* @param mixed $msg Main log message (will be JSON encoded)
141+
* @param mixed $object Optional additional data to append to the message (will be JSON encoded)
142+
* @return void
143+
* @psalm-suppress PossiblyUnusedMethod
144+
*/
145+
public function writeLog($msg, $object = null)
146+
{
147+
try {
148+
// Do not write if log limit has been reached
149+
if ($this->limits_reached || $this->logLimitReached()) {
150+
$this->limits_reached = true;
151+
throw new \Exception('Limit reached.');
152+
}
153+
154+
// Encode main message with error suppression
155+
$msg = is_string($msg) ? $msg : @json_encode($msg);
156+
if (false === $msg) {
157+
$msg = 'JSON_ENCODE_ERROR';
158+
}
159+
160+
// Create and populate log record
161+
$record = $this->getLogRecordProvider();
162+
$record->time = $this->getTime();
163+
164+
// Encode additional data if provided
165+
if (null !== $object) {
166+
$object = @json_encode($object);
167+
if (false === $object) {
168+
$object = 'JSON_ENCODE_ERROR';
169+
}
170+
}
171+
172+
// Assemble final message
173+
$record->message = $msg;
174+
$record->message .= !empty($object) ? ', additional data: ' . $object : '';
175+
176+
// Delegate storage to concrete implementation
177+
static::updateStorage($record);
178+
} catch (\Exception $e) {
179+
// do nothing
180+
}
181+
}
182+
183+
/**
184+
* Retrieve all logs as an array of formatted strings.
185+
*
186+
* @return array Array of strings in format "time : message", empty array if no logs
187+
* @psalm-suppress PossiblyUnusedMethod
188+
*/
189+
public static function getLogAsStringsArray()
190+
{
191+
$storage_log = static::loadStorage();
192+
193+
// Validate loaded data
194+
if (empty($storage_log) || !is_array($storage_log)) {
195+
return [];
196+
}
197+
198+
// Format each record
199+
$out = [];
200+
foreach ($storage_log as $log_record) {
201+
if ($log_record instanceof LoggerRecord) {
202+
$out[] = $log_record->time . ' : ' . $log_record->message;
203+
} elseif (is_array($log_record) && isset($log_record[0], $log_record[1])) {
204+
$time = is_int($log_record[0]) ? $log_record[0] : 'unknown time';
205+
$out[] = $time . ' : ' . $log_record[1] ? $log_record[1] : 'unknown message';
206+
} else {
207+
$out[] = 'logger_error';
208+
}
209+
}
210+
211+
return $out;
212+
}
213+
214+
/**
215+
* Check whether the log storage has reached its maximum capacity.
216+
*
217+
* @return bool True if log limit is reached, false otherwise
218+
*/
219+
private function logLimitReached()
220+
{
221+
$log_count = count(static::loadStorage());
222+
return $log_count >= static::$log_limit;
223+
}
224+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace CleantalkSP\Common;
4+
5+
/**
6+
* Simple data container for a single log entry.
7+
* Used by Logger implementations to structure log data.
8+
*/
9+
class LoggerRecord
10+
{
11+
/**
12+
* @var string Timestamp of the log entry (formatted as 'Y-m-d H:i:s')
13+
*/
14+
public $time = '';
15+
16+
/**
17+
* @var string Log message content (may include JSON-encoded data)
18+
*/
19+
public $message = '';
20+
}

lib/CleantalkSP/Common/Queue.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,22 +129,15 @@ public function executeStage()
129129

130130
if ($stage_to_execute) {
131131
if (is_callable($stage_to_execute['name'])) {
132-
spbc_security_firewall_update_log('Is callable:' . var_export($stage_to_execute['name'], true));
133-
134132
++$stage_to_execute['tries'];
135133

136134
if (! empty($stage_to_execute['args'])) {
137135
$result = $stage_to_execute['name']($stage_to_execute['args']);
138-
spbc_security_firewall_update_log('Executed stage: ' . var_export($stage_to_execute['name'], true) . ' with args ' . var_export($stage_to_execute['args'], true));
139136
} else {
140137
$result = $stage_to_execute['name']();
141-
spbc_security_firewall_update_log('Executed stage: ' . var_export($stage_to_execute['name'], true) . ' with no args');
142138
}
143139

144-
spbc_security_firewall_update_log('Stage result ' . var_export($result, true));
145-
146140
if (isset($result['error'])) {
147-
spbc_security_firewall_update_log('Stage error ' . var_export($result['error'], true));
148141
$stage_to_execute['status'] = 'NULL';
149142
$stage_to_execute['error'][] = $result['error'];
150143
if (isset($result['update_args']['args'])) {
@@ -158,8 +151,6 @@ public function executeStage()
158151
return $result;
159152
}
160153

161-
spbc_security_firewall_update_log('Stage has errors, proceed to RC ' . var_export($this->rc_name, true));
162-
163154
$rc_result = \CleantalkSP\SpbctWP\RemoteCalls::performToHost(
164155
$this->rc_name,
165156
array(
@@ -169,8 +160,6 @@ public function executeStage()
169160
array('async')
170161
);
171162

172-
spbc_security_firewall_update_log('Stage RC result' . var_export($rc_result, true));
173-
174163
return $rc_result;
175164
}
176165

0 commit comments

Comments
 (0)