Skip to content

Commit ed84ada

Browse files
committed
Merge branch 'release/1.1.0'
2 parents 7ad98e8 + eb39f63 commit ed84ada

File tree

5 files changed

+265
-24
lines changed

5 files changed

+265
-24
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11+
## [1.1.0] - 2025-03-11
12+
13+
- Added command for retrying jobs.
14+
- Checked http codes when sending packets to loki.
15+
1116
## [1.0.1] - 2025-03-06
1217

1318
- Handled webform elements not present in submission data
@@ -78,7 +83,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7883
- First version of the module
7984
- Added submodule to log user CUD events.
8085

81-
[Unreleased]: https://github.com/OS2web/os2web_audit/compare/1.0.1...HEAD
86+
[Unreleased]: https://github.com/OS2web/os2web_audit/compare/1.1.0...HEAD
87+
[1.1.0]: https://github.com/OS2web/os2web_audit/compare/1.0.1...1.1.0
8288
[1.0.1]: https://github.com/OS2web/os2web_audit/compare/1.0.0...1.0.1
8389
[1.0.0]: https://github.com/OS2web/os2web_audit/compare/0.2.2...1.0.0
8490
[0.2.2]: https://github.com/OS2web/os2web_audit/compare/0.2.1...0.2.2

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ composer require os2web/os2web_audit
2626
drush pm:enable os2web_audit
2727
```
2828

29-
### Drush
29+
## Drush
30+
31+
### Test audit log
3032

3133
The module provides a Drush command named audit:log. This command enables you
3234
to log a test message to the configured logger. The audit:log command accepts a
@@ -39,6 +41,24 @@ and once as an error message.
3941
drush audit:log 'This is a test message'
4042
```
4143

44+
### Retry jobs
45+
46+
The module also comes with methods for retrying failed jobs in the
47+
`os2web_audit` queue.
48+
49+
```shell
50+
drush audit:retry-failed-jobs
51+
```
52+
53+
Per default, it simply retries all failed jobs however it comes with
54+
the following options:
55+
56+
```shell
57+
--id[=ID] Retry a specific job by ID (e.g. 1245.)
58+
--ignore-state Retry job regardless of state. This only effects the --id option.
59+
--limit[=LIMIT] Retry (up to) a limited number of jobs. Minimum: 1, Maximum: 5000, Default 1000.
60+
```
61+
4262
## Usage
4363

4464
The module exposes a simple `Logger` service which can log an `info` and `error`

src/Drush/Commands/Commands.php renamed to src/Drush/Commands/LogMessageCommand.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33
namespace Drupal\os2web_audit\Drush\Commands;
44

5-
use Drush\Attributes\Command;
65
use Drupal\os2web_audit\Service\Logger;
6+
use Drush\Attributes\Argument;
7+
use Drush\Attributes\Command;
78
use Drush\Commands\DrushCommands;
9+
use Drush\Exceptions\CommandFailedException;
810
use Symfony\Component\DependencyInjection\Attribute\Autowire;
9-
use Drush\Attributes\Argument;
1011
use Symfony\Component\DependencyInjection\ContainerInterface;
11-
use Drush\Exceptions\CommandFailedException;
1212

1313
/**
1414
* Simple command to send log message into audit log.
1515
*/
16-
class Commands extends DrushCommands {
16+
class LogMessageCommand extends DrushCommands {
1717

1818
/**
1919
* Commands constructor.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
namespace Drupal\os2web_audit\Drush\Commands;
4+
5+
use Drupal\Core\Database\Connection;
6+
use Drupal\advancedqueue\Job;
7+
use Drush\Attributes\Command;
8+
use Drush\Attributes\Option;
9+
use Drush\Commands\DrushCommands;
10+
use Symfony\Component\DependencyInjection\ContainerInterface;
11+
12+
/**
13+
* Simple command to send log message into audit log.
14+
*/
15+
class RetryFailedQueueCommand extends DrushCommands {
16+
17+
private const OS2WEB_AUDIT_QUEUE_ID = 'os2web_audit';
18+
private const ADVANCEDQUEUE_TABLE = 'advancedqueue';
19+
20+
/**
21+
* Commands constructor.
22+
*
23+
* @param \Drupal\Core\Database\Connection $connection
24+
* The database connection.
25+
*/
26+
public function __construct(
27+
protected Connection $connection,
28+
) {
29+
parent::__construct();
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public static function create(ContainerInterface $container): self {
36+
return new static(
37+
$container->get('database'),
38+
);
39+
}
40+
41+
/**
42+
* Retries all failed jobs in the os2web_audit queue.
43+
*
44+
* @param array<string, mixed> $options
45+
* The options array.
46+
*/
47+
#[Command(name: 'audit:retry-failed-jobs')]
48+
#[Option(name: 'id', description: "Retry a specific job by ID (e.g. 1245.)")]
49+
#[Option(name: 'ignore-state', description: 'Retries job regardless of state. This only effects the --id option.')]
50+
#[Option(name: 'limit', description: "Retry (up to) a limited number of jobs. Minimum: 1, Maximum: 5000, Default 1000.")]
51+
public function retryFailedJobs($options = ['id' => NULL, 'ignore-state' => FALSE, 'limit' => NULL]): void {
52+
53+
if (TRUE === $options['id']) {
54+
$this->writeln('Please specify a job ID, e.g. --id=1245.');
55+
return;
56+
}
57+
elseif (is_string($options['id'])) {
58+
$this->retryJob((int) $options['id'], $options['ignore-state']);
59+
return;
60+
}
61+
62+
if (TRUE === $options['limit']) {
63+
// We use the default 1000.
64+
$this->retryJobs(1000);
65+
return;
66+
}
67+
elseif (is_string($options['limit'])) {
68+
$this->retryJobs((int) $options['limit']);
69+
return;
70+
}
71+
72+
$this->retryAllFailedJobs();
73+
74+
}
75+
76+
/**
77+
* Retries all failed jobs in os2web_audit.
78+
*/
79+
private function retryAllFailedJobs(): void {
80+
try {
81+
$this->connection->update('advancedqueue')
82+
->fields(['state' => Job::STATE_QUEUED])
83+
->condition('queue_id', 'os2web_audit')
84+
->condition('state', Job::STATE_FAILURE)
85+
->execute();
86+
87+
$this->output()->writeln('Successfully retried all failed jobs.');
88+
}
89+
catch (\Exception $e) {
90+
$this->output()->writeln($e->getMessage());
91+
}
92+
93+
}
94+
95+
/**
96+
* Retries jobs in the os2web_audit queue.
97+
*/
98+
private function retryJobs(int $limit): void {
99+
if ($limit < 1 || $limit > 5000) {
100+
$this->output()->writeln('Limit should be an integer between 1 and 5000.');
101+
return;
102+
}
103+
104+
try {
105+
$ids = $this->connection->select(self::ADVANCEDQUEUE_TABLE, 'a')
106+
->fields('a', ['job_id'])
107+
->condition('queue_id', self::OS2WEB_AUDIT_QUEUE_ID)
108+
->condition('state', Job::STATE_FAILURE)
109+
->range(0, $limit)
110+
->execute()
111+
->fetchCol();
112+
113+
$this->connection->update(self::ADVANCEDQUEUE_TABLE)
114+
->fields(['state' => Job::STATE_QUEUED])
115+
->condition('queue_id', self::OS2WEB_AUDIT_QUEUE_ID)
116+
->condition('state', Job::STATE_FAILURE)
117+
->condition('job_id', $ids, 'IN')
118+
->execute();
119+
120+
$this->output()->writeln('Successfully retried failed jobs.');
121+
}
122+
catch (\Exception $e) {
123+
$this->output()->writeln($e->getMessage());
124+
}
125+
}
126+
127+
/**
128+
* Retries failed job in the os2web_audit queue.
129+
*/
130+
private function retryJob(int $id, bool $ignoreState): void {
131+
132+
try {
133+
// Check that job exists by fetching its state.
134+
$query = $this->connection->select(self::ADVANCEDQUEUE_TABLE, 'a')
135+
->fields('a', ['state'])
136+
->condition('job_id', $id);
137+
138+
$result = $query->execute()->fetchAssoc();
139+
140+
if (!$result) {
141+
$this->output()->writeln('Job not found.');
142+
return;
143+
}
144+
145+
// State check.
146+
if (!$ignoreState && $result['state'] !== Job::STATE_FAILURE) {
147+
$this->output()->writeln('Job is not in a failed state.');
148+
return;
149+
}
150+
151+
$query = $this->connection->update(self::ADVANCEDQUEUE_TABLE)
152+
->fields(['state' => Job::STATE_QUEUED])
153+
->condition('queue_id', self::OS2WEB_AUDIT_QUEUE_ID)
154+
->condition('job_id', $id);
155+
156+
if (!$ignoreState) {
157+
$query->condition('state', Job::STATE_FAILURE);
158+
}
159+
160+
$result = $query->execute();
161+
162+
if ($result) {
163+
$this->output()->writeln('Successfully retried job.');
164+
}
165+
else {
166+
$this->output()->writeln('Failed retrying job.');
167+
}
168+
169+
}
170+
catch (\Exception $e) {
171+
$this->output()->writeln($e->getMessage());
172+
}
173+
}
174+
175+
}

src/Service/LokiClient.php

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,15 @@ private function sendPacket(array $packet): void {
119119
$payload = json_encode($packet, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
120120
}
121121
catch (\JsonException $e) {
122-
throw new AuditException(
123-
message: 'Payload could not be encoded.',
124-
previous: $e,
125-
pluginName: 'Loki',
126-
);
122+
throw $this->auditException('Payload could not be encoded.', 0, $e);
127123
}
128124

129125
if (NULL === $this->connection) {
130126
$url = sprintf('%s/loki/api/v1/push', $this->entrypoint);
131127
$this->connection = curl_init($url);
132128

133129
if (FALSE === $this->connection) {
134-
throw new ConnectionException(
135-
message: 'Unable to connect to ' . $url,
136-
pluginName: 'Loki',
137-
);
130+
throw $this->connectionException('Unable to initialize curl connection to ' . $url);
138131
}
139132
}
140133

@@ -163,20 +156,67 @@ private function sendPacket(array $packet): void {
163156
$result = curl_exec($this->connection);
164157

165158
if (FALSE === $result) {
166-
throw new ConnectionException(
167-
message: 'Error sending packet to Loki',
168-
pluginName: 'Loki',
169-
);
159+
throw $this->connectionException('Error sending packet to Loki');
170160
}
171161

172162
if (curl_errno($this->connection)) {
173-
throw new AuditException(
174-
message: curl_error($this->connection),
175-
code: curl_errno($this->connection),
176-
pluginName: 'Loki',
177-
);
163+
throw $this->auditException(curl_error($this->connection), curl_errno($this->connection));
164+
}
165+
166+
$code = curl_getinfo($this->connection, CURLINFO_HTTP_CODE);
167+
168+
if (!in_array($code, [200, 204], TRUE)) {
169+
throw $this->auditException('Error sending packet to Loki', $code);
178170
}
179171
}
180172
}
181173

174+
/**
175+
* Creates an audit exception.
176+
*
177+
* @param string $message
178+
* The log message.
179+
* @param int $code
180+
* The error code.
181+
* @param ?\Throwable $previous
182+
* The previous throwable.
183+
* @param string $pluginName
184+
* The plugin name.
185+
*
186+
* @return \Drupal\os2web_audit\Exception\AuditException
187+
* The created exception.
188+
*/
189+
private function auditException(string $message = '', int $code = 0, ?\Throwable $previous = NULL, string $pluginName = 'Loki'): AuditException {
190+
return new AuditException(
191+
message: $message,
192+
code: $code,
193+
previous: $previous,
194+
pluginName: $pluginName,
195+
);
196+
}
197+
198+
/**
199+
* Creates a connection exception.
200+
*
201+
* @param string $message
202+
* The log message.
203+
* @param int $code
204+
* The error code.
205+
* @param ?\Throwable $previous
206+
* The previous throwable.
207+
* @param string $pluginName
208+
* The plugin name.
209+
*
210+
* @return \Drupal\os2web_audit\Exception\ConnectionException
211+
* The created exception.
212+
*/
213+
private function connectionException(string $message = '', int $code = 0, ?\Throwable $previous = NULL, string $pluginName = 'Loki'): ConnectionException {
214+
return new ConnectionException(
215+
message: $message,
216+
code: $code,
217+
previous: $previous,
218+
pluginName: $pluginName,
219+
);
220+
}
221+
182222
}

0 commit comments

Comments
 (0)