Skip to content

Commit ee9422d

Browse files
authored
chore: revamp OTEL audit log (#454)
* chore: revamp OTEL audit log * chore: add custom audit logger per entity ( SummitMemberSchedule )
1 parent 7f96355 commit ee9422d

File tree

13 files changed

+504
-261
lines changed

13 files changed

+504
-261
lines changed

app/Audit/AuditContext.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php namespace App\Audit;
2+
/**
3+
* Copyright 2025 OpenStack Foundation
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
**/
14+
class AuditContext
15+
{
16+
public function __construct(
17+
public ?int $userId = null,
18+
public ?string $userEmail = null,
19+
public ?string $userFirstName = null,
20+
public ?string $userLastName = null,
21+
public ?string $uiApp = null,
22+
public ?string $uiFlow = null,
23+
public ?string $route = null,
24+
public ?string $httpMethod = null,
25+
public ?string $clientIp = null,
26+
public ?string $userAgent = null,
27+
) {}
28+
}

app/Audit/AuditEventListener.php

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
<?php
2-
3-
namespace App\Audit;
4-
1+
<?php namespace App\Audit;
52
/**
6-
* Copyright 2022 OpenStack Foundation
3+
* Copyright 2025 OpenStack Foundation
74
* Licensed under the Apache License, Version 2.0 (the "License");
85
* you may not use this file except in compliance with the License.
96
* You may obtain a copy of the License at
@@ -15,7 +12,7 @@
1512
* limitations under the License.
1613
**/
1714

18-
use App\Audit\AuditLogOtlpStrategy;
15+
use App\Audit\Interfaces\IAuditStrategy;
1916
use Doctrine\ORM\Event\OnFlushEventArgs;
2017
use Illuminate\Support\Facades\App;
2118
use Illuminate\Support\Facades\Log;
@@ -29,30 +26,34 @@ class AuditEventListener
2926

3027
public function onFlush(OnFlushEventArgs $eventArgs): void
3128
{
29+
if (app()->environment('testing')){
30+
return;
31+
}
3232
$em = $eventArgs->getObjectManager();
3333
$uow = $em->getUnitOfWork();
3434
// Strategy selection based on environment configuration
3535
$strategy = $this->getAuditStrategy($em);
36-
3736
if (!$strategy) {
3837
return; // No audit strategy enabled
3938
}
4039

40+
$ctx = $this->buildAuditContext();
41+
4142
try {
4243
foreach ($uow->getScheduledEntityInsertions() as $entity) {
43-
$strategy->audit($entity, [], AuditLogOtlpStrategy::EVENT_ENTITY_CREATION);
44+
$strategy->audit($entity, [], IAuditStrategy::EVENT_ENTITY_CREATION, $ctx);
4445
}
4546

4647
foreach ($uow->getScheduledEntityUpdates() as $entity) {
47-
$strategy->audit($entity, $uow->getEntityChangeSet($entity), AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE);
48+
$strategy->audit($entity, $uow->getEntityChangeSet($entity), IAuditStrategy::EVENT_ENTITY_UPDATE, $ctx);
4849
}
4950

5051
foreach ($uow->getScheduledEntityDeletions() as $entity) {
51-
$strategy->audit($entity, [], AuditLogOtlpStrategy::EVENT_ENTITY_DELETION);
52+
$strategy->audit($entity, [], IAuditStrategy::EVENT_ENTITY_DELETION, $ctx);
5253
}
5354

5455
foreach ($uow->getScheduledCollectionUpdates() as $col) {
55-
$strategy->audit($col, [], AuditLogOtlpStrategy::EVENT_COLLECTION_UPDATE);
56+
$strategy->audit($col, [], IAuditStrategy::EVENT_COLLECTION_UPDATE, $ctx);
5657
}
5758
} catch (\Exception $e) {
5859
Log::error('Audit event listener failed', [
@@ -78,9 +79,36 @@ private function getAuditStrategy($em)
7879
]);
7980
}
8081
}
81-
82+
8283
// Use database strategy (either as default or fallback)
8384
return new AuditLogStrategy($em);
85+
}
86+
87+
private function buildAuditContext(): AuditContext
88+
{
89+
$resourceCtx = app(\models\oauth2\IResourceServerContext::class);
90+
$userExternalId = $resourceCtx->getCurrentUserId();
91+
$member = null;
92+
if ($userExternalId) {
93+
$memberRepo = app(\models\main\IMemberRepository::class);
94+
$member = $memberRepo->findOneBy(["user_external_id" => $userExternalId]);
95+
}
96+
97+
//$ui = app()->bound('ui.context') ? app('ui.context') : [];
98+
99+
$req = request();
84100

101+
return new AuditContext(
102+
userId: $member?->getId(),
103+
userEmail: $member?->getEmail(),
104+
userFirstName: $member?->getFirstName(),
105+
userLastName: $member?->getLastName(),
106+
uiApp: $ui['app'] ?? null,
107+
uiFlow: $ui['flow'] ?? null,
108+
route: $req?->path(),
109+
httpMethod: $req?->method(),
110+
clientIp: $req?->ip(),
111+
userAgent: $req?->userAgent(),
112+
);
85113
}
86114
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php namespace App\Audit;
2+
/**
3+
* Copyright 2025 OpenStack Foundation
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
**/
14+
use App\Audit\ConcreteFormatters\ChildEntityFormatters\ChildEntityFormatterFactory;
15+
use App\Audit\ConcreteFormatters\EntityCollectionUpdateAuditLogFormatter;
16+
use App\Audit\ConcreteFormatters\EntityCreationAuditLogFormatter;
17+
use App\Audit\ConcreteFormatters\EntityDeletionAuditLogFormatter;
18+
use App\Audit\ConcreteFormatters\EntityUpdateAuditLogFormatter;
19+
use App\Audit\Interfaces\IAuditStrategy;
20+
21+
class AuditLogFormatterFactory implements IAuditLogFormatterFactory
22+
{
23+
24+
private array $config;
25+
26+
public function __construct()
27+
{
28+
// cache the config so we don't hit config() repeatedly
29+
$this->config = config('audit_log', []);
30+
}
31+
32+
public function getStrategyClass(object $subject, string $event_type): ?IAuditLogFormatter
33+
{
34+
$class = get_class($subject);
35+
$cls = $this->config['entities'][$class]['strategy'] ?? null;
36+
return !is_null($cls) ? new $cls($event_type):null;
37+
}
38+
39+
public function make($subject, $eventType): ?IAuditLogFormatter
40+
{
41+
$formatter = null;
42+
switch ($eventType) {
43+
case IAuditStrategy::EVENT_COLLECTION_UPDATE:
44+
$child_entity = null;
45+
if (count($subject) > 0) {
46+
$child_entity = $subject[0];
47+
}
48+
if (is_null($child_entity) && count($subject->getSnapshot()) > 0) {
49+
$child_entity = $subject->getSnapshot()[0];
50+
}
51+
$child_entity_formatter = $child_entity != null ? ChildEntityFormatterFactory::build($child_entity) : null;
52+
$formatter = new EntityCollectionUpdateAuditLogFormatter($child_entity_formatter);
53+
break;
54+
case IAuditStrategy::EVENT_ENTITY_CREATION:
55+
$formatter = $this->getStrategyClass($subject, $eventType);
56+
if(is_null($formatter)) {
57+
$formatter = new EntityCreationAuditLogFormatter();
58+
}
59+
break;
60+
case IAuditStrategy::EVENT_ENTITY_DELETION:
61+
$formatter = $this->getStrategyClass($subject, $eventType);
62+
if(is_null($formatter)) {
63+
$child_entity_formatter = ChildEntityFormatterFactory::build($subject);
64+
$formatter = new EntityDeletionAuditLogFormatter($child_entity_formatter);
65+
}
66+
break;
67+
case IAuditStrategy::EVENT_ENTITY_UPDATE:
68+
$formatter = $this->getStrategyClass($subject, $eventType);
69+
if(is_null($formatter)) {
70+
$child_entity_formatter = ChildEntityFormatterFactory::build($subject);
71+
$formatter = new EntityUpdateAuditLogFormatter($child_entity_formatter);
72+
}
73+
break;
74+
}
75+
return $formatter;
76+
}
77+
}

0 commit comments

Comments
 (0)