lorenzo / audit-stash
灵活且稳固的 cakephp 审计日志跟踪插件
Requires
- php: >=8.1
- ext-json: *
- cakephp/orm: ^5.0.0
Requires (Dev)
- cakephp/cakephp: ^5.0.0
- cakephp/cakephp-codesniffer: ^5.0
- cakephp/elastic-search: ^4.0.0
- cakephp/migrations: ^4.0.0
- friendsofcake/crud: ^7.0.0
- phpunit/phpunit: ^10.1.0
Suggests
- cakephp/elastic-search: The default persister engine for audit-stash is elastic search and requires this plugin
- friendsofcake/crud: audit-stash provides Crud Action classes for displaying audit logs
README
此插件为您的应用中任何 Table 类实现了 "审计跟踪",即记录任何特定表的实体创建、修改或删除的能力。
默认情况下,此插件将审计日志存储到 Elasticsearch,因为我们发现它是一个出色的追加数据流存储引擎,并为查找历史数据中的更改提供了真正强大的功能。
尽管我们建议将日志存储在 Elasticsearch 中,但此插件足够通用,您可以根据需要实现自己的持久化策略。
安装
您可以使用 composer 将此插件安装到您的 CakePHP 应用程序中,并在应用程序根目录中执行以下行。
composer require lorenzo/audit-stash
bin/cake plugin load AuditStash
要使用默认存储引擎(ElasticSearch),您需要安装官方的 elastic-search
插件,执行以下行
composer require cakephp/elastic-search
bin/cake plugin load Cake/ElasticSearch
配置
Elastic Search
现在您需要将数据源配置添加到您的 config/app.php
文件中
'Datasources' => [ 'auditlog_elastic' => [ 'className' => 'Cake\ElasticSearch\Datasource\Connection', 'driver' => 'Cake\ElasticSearch\Datasource\Connection', 'host' => '127.0.0.1', // server where elasticsearch is running 'port' => 9200 ], ... ]
表格 / 正规数据库
如果您想使用常规数据库,或者可以使用 CakePHP ORM API 使用的引擎,则可以使用此插件提供的表格持久化。
为此,您需要相应地配置 AuditStash.persister
选项。在您的 config/app_local.php
文件中添加以下配置
'AuditStash' => [ 'persister' => \AuditStash\Persister\TablePersister::class, ],
默认情况下,此插件将尝试将日志存储在名为 audit_logs
的表中,通过具有别名 AuditLogs
的表类,您可以在应用程序中创建/覆盖它,如果需要的话。
您可以在本插件的 config/migration
文件夹中找到一个迁移,您可以将它应用到您的数据库中,这将添加一个名为 audit_logs
的表,其中包含所有默认列 - 或者您可以手动创建该表。之后,您可以烘烤相应的表类。
bin/cake migrations migrate -p AuditStash
bin/cake bake model AuditLogs
表格持久化配置
表格持久化支持各种配置选项,请参阅 其 API 文档 以获取更多信息。通常,配置可以通过其 setConfig()
方法应用。
$this->addBehavior('AuditStash.AuditLog'); $this->behaviors()->get('AuditLog')->persister()->setConfig([ 'extractMetaFields' => [ 'user.id' => 'user_id' ] ]);
使用 AuditStash
在您的任何表类中启用审计日志就像在 initialize()
函数中添加一个行为一样简单。
class ArticlesTable extends Table { public function initialize(array $config = []): void { ... $this->addBehavior('AuditStash.AuditLog'); } }
请记住,每次您更改表格的架构时都要执行命令行命令!
配置行为
AuditLog
行为可以配置为忽略您的表格中的某些字段,默认情况下,它忽略 created
和 modified
字段。
class ArticlesTable extends Table { public function initialize(array $config = []): void { ... $this->addBehavior('AuditStash.AuditLog', [ 'blacklist' => ['created', 'modified', 'another_field_name'] ]); } }
如果您更喜欢,您可以使用 whitelist
。这意味着只有该数组中列出的字段将被行为跟踪。
public function initialize(array $config = []): void { ... $this->addBehavior('AuditStash.AuditLog', [ 'whitelist' => ['title', 'description', 'author_id'] ]); }
如果您有包含敏感信息但仍然想跟踪其更改的字段,您可以使用 sensitive
配置。
public function initialize(array $config = []): void { ... $this->addBehavior('AuditStash.AuditLog', [ 'sensitive' => ['body'] ]); }
存储已登录用户
将触发特定表格更改的用户标识符存储在表中通常很有用。为此目的,AuditStash
提供了 RequestMetadata
监听器类,它可以存储当前 URL、IP 和已登录用户。您需要在应用程序的 AppController::beforeFilter()
方法中添加此监听器。
use AuditStash\Meta\RequestMetadata; ... class AppController extends Controller { public function beforeFilter(EventInterface $event) { ... $eventManager = $this->fetchTable()->getEventManager(); $eventManager->on( new RequestMetadata( request: $this->getRequest(), user: $this->getRequest()->getAttribute('identity')?->getIdentifier() ) ); } }
上述代码假定您将从控制器触发表格操作,使用控制器默认的Table类。如果您计划在同一个控制器中使用其他Table类进行保存或删除,建议您全局绑定监听器。
use AuditStash\Meta\RequestMetadata; use Cake\Event\EventManager; ... class AppController extends Controller { public function beforeFilter(EventInterface $event) { ... EventManager::instance()->on( new RequestMetadata( request: $this->getRequest(), user: $this->getRequest()->getAttribute('identity')?->getIdentifier() ) ); } }
在日志中存储额外信息
AuditStash
还能够在每个已记录的事件中存储任意数据。您可以使用ApplicationMetadata
监听器或创建自己的监听器。如果您选择使用ApplicationMetadata
,您的日志将包含存储的app_name
键和您可能提供的任何额外信息。您可以在应用程序中的任何位置配置此监听器,例如在bootstrap.php
文件中,或者在AppController中直接配置。
use AuditStash\Meta\ApplicationMetadata; use Cake\Event\EventManager; EventManager::instance()->on(new ApplicationMetadata('my_blog_app', [ 'server' => $theServerID, 'extra' => $somExtraInformation, 'moon_phase' => $currentMoonPhase ]));
实现自己的元数据监听器就像将监听器附加到AuditStash.beforeLog
事件一样简单。例如
EventManager::instance()->on('AuditStash.beforeLog', function (EventInterface $event, array $logs): void { foreach ($logs as $log) { $log->setMetaInfo($log->getMetaInfo() + ['extra' => 'This is extra data to be stored']); } });
实现您自己的持久化策略
出于某些合理的原因,您可能希望为您的审计日志使用不同的持久化引擎。幸运的是,此插件允许您实现自己的存储引擎。这就像实现PersisterInterface
接口一样简单。
use AuditStash\PersisterInterface; class MyPersister implements PersisterInterface { public function logEvents(array $auditLogs): void { foreach ($auditLogs as $log) { $eventType = $log->getEventType(); $data = [ 'timestamp' => $log->getTimestamp(), 'transaction' => $log->getTransactionId(), 'type' => $log->getEventType(), 'primary_key' => $log->getId(), 'source' => $log->getSourceName(), 'parent_source' => $log->getParentSourceName(), 'original' => json_encode($log->getOriginal()), 'changed' => $eventType === 'delete' ? null : json_encode($log->getChanged()), 'meta' => json_encode($log->getMetaInfo()) ]; $storage = new MyStorage(); $storage->save($data); } } }
最后,您需要配置AuditStash
以使用您的新持久化器。在config/app.php
文件中添加以下行
'AuditStash' => [ 'persister' => 'App\Namespace\For\Your\Persister' ]
或者如果您是独立使用
\Cake\Core\Configure::write('AuditStash.persister', 'App\Namespace\For\Your\DatabasePersister');
配置包含您持久化器完全命名空间类名的类。
处理事务性查询
有时,您可能希望将多个数据库更改包裹在一个事务中,以便在过程中某个部分失败时可以回滚。有两种实现方式。最简单的是将您的保存策略更改为使用afterSave
而不是afterCommit
。在您的应用程序配置中,例如在config/app.php
'AuditStash' => [ 'saveType' => 'afterSave', ]
如果使用afterSave,这样就完成了。您应该了解这两个之间的区别,因为存在一些缺点:https://book.cakephp.com.cn/4/en/orm/table-objects.html#aftersave
如果您使用的是默认的afterCommit,为了在事务中创建审计日志,需要进行一些额外的设置。首先创建文件src/Model/Audit/AuditTrail.php
,内容如下
<?php declare(strict_types=1); namespace App\Model\Audit; use Cake\Utility\Text; use SplObjectStorage; class AuditTrail { protected SplObjectStorage $_auditQueue; protected string $_auditTransaction; public function __construct() { $this->_auditQueue = new SplObjectStorage; $this->_auditTransaction = Text::uuid(); } public function toSaveOptions(): array { return [ '_auditQueue' => $this->_auditQueue, '_auditTransaction' => $this->_auditTransaction ]; } }
在任何您希望使用Connection::transactional()
的地方,您需要在文件顶部包含以下内容
use App\Model\Audit\AuditTrail; use Cake\Event\Event; use \ArrayObject
然后,您的交易应该看起来类似于以下示例中的BookmarksController
$trail = new AuditTrail(); $success = $this->Bookmarks->connection()->transactional(function () use ($trail) { $bookmark = $this->Bookmarks->newEntity(); $bookmark1->save($data1, $trail->toSaveOptions()); $bookmark2 = $this->Bookmarks->newEntity(); $bookmark2->save($data2, $trail->toSaveOptions()); ... $bookmarkN = $this->Bookmarks->newEntity(); $bookmarkN->save($dataN, $trail->toSaveOptions()); }); if ($success) { $event = new Event('Model.afterCommit', $this->Bookmarks); $this->Bookmarks->->behaviors()->get('AuditLog')->afterCommit( $event, $result, new ArrayObject($auditTrail->toSaveOptions()) ); }
这将保存您的对象的全部审计信息,以及任何关联数据的审计信息。请注意,$result
必须是一个对象的实例。不要更改文本“Model.afterCommit”。
测试
默认情况下,测试套件不会运行Elastic。如果您是Elastic用户并希望针对本地实例进行测试,则需要设置环境变量
elastic_dsn="Cake\ElasticSearch\Datasource\Connection://127.0.0.1:9200?driver=Cake\ElasticSearch\Datasource\Connection" vendor/bin/phpunit