locastic / loggastic
Symfony 的 ElasticSearch 活动日志
Requires
- php: >=8.1
- ext-simplexml: *
- doctrine/annotations: ^1.14
- doctrine/doctrine-bundle: ^2.8
- doctrine/orm: ^2.14
- elasticsearch/elasticsearch: ^7.1
- symfony/dotenv: 6.4.*
- symfony/expression-language: 6.4.*
- symfony/finder: ^6.4
- symfony/framework-bundle: ^6.4
- symfony/messenger: 6.4.*
- symfony/property-info: 6.4.*
- symfony/runtime: 6.4.*
- symfony/security-bundle: ^6.4
- symfony/serializer: 6.4.*
- symfony/validator: 6.4.*
- symfony/yaml: 6.4.*
Requires (Dev)
- symfony/browser-kit: 6.4.*
- symfony/console: 6.4.*
- symfony/css-selector: 6.4.*
- symfony/phpunit-bridge: ^6.4
- symfony/test-pack: ^1.1
Conflicts
- symfony/translations-contracts: <2.0
This package is auto-updated.
Last update: 2024-09-18 19:37:05 UTC
README
Loggastic
Loggastic 是为跟踪对象及其关系的变化而设计的。该库建立在 Symfony 框架 之上,使得实现活动日志并将其存储在 Elasticsearch 上以便快速浏览日志变得容易。
每个跟踪的实体在 ElasticSearch 中将有两个索引
entity_name_activity_log
-> 保存对一个对象执行的 CRUD 操作。此外,编辑操作还会保存之前和之后值。entity_name_current_data_tracker
-> 保存用于比较编辑操作中更改的最新对象值。这使我们能够只在activity_log
索引中存储修改字段的之前和之后值。
系统要求
Elasticsearch 版本 7.17
安装
composer require locastic/loggastic
使实体可记录
要使实体可记录,您需要执行以下步骤
1. 在实体中添加 Loggable 属性
将 Locastic\Loggastic\Annotation\Loggable
注解添加到实体中,并定义序列化组名称
<?php namespace App\Entity; use Locastic\Loggastic\Annotation\Loggable; #[Loggable(groups: ['blog_post_log'])] class BlogPost { // ... }
如果您正在使用 YAML
locastic_loggable: - { class: 'App\Entity\BlogPost', groups: [ 'blog_post_log' ] }
或 XML
<?xml version="1.0" ?> <locastic_loggable_classes xmlns="https://locastic.com/schema/metadata/loggable" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://locastic.com/schema/metadata/loggable https://locastic.com/schema/metadata/loggable.xsd" > <loggable_class class="App\Entity\BlogPost"> <group name="blog_post_log"/> </loggable_class> </locastic_loggable_classes>
2. 将序列化组添加到要记录的字段
使用 Loggable 属性配置中定义的序列化组在要跟踪的字段上。您也可以将它们添加到关系及其字段中。
<?php namespace App\Entity; use Locastic\Loggastic\Annotation\Loggable; use Symfony\Component\Serializer\Annotation\Groups; #[Loggable(groups: ['blog_post_log'])] class BlogPost { private int $id; #[Groups(groups: ['blog_post_log'])] private string $title; #[Groups(groups: ['blog_post_log'])] private ArrayCollection $tags; // ... }
记录关系字段示例
<?php namespace App\Entity; use Locastic\Loggastic\Annotation\Loggable; use Symfony\Component\Serializer\Annotation\Groups; class Tag { private int $id; #[Groups(groups: ['blog_post_log'])] private string $name; #[Groups(groups: ['blog_post_log'])] private DateTimeImmutable $createdAt; // ... }
注意:您还可以使用 注解、xml 和 yaml!示例即将推出。
3. 运行创建 ElasticSearch 中索引的命令
bin/console locastic:activity-logs:create-loggable-indexes
如果您数据库中已有一些数据,请确保使用以下命令填充当前数据跟踪器
bin/console locastic:activity-logs:populate-current-data-trackers
4. 显示活动日志
以下是显示活动日志在 twig 或作为 Api 端点的示例
a) 在 Twig 中显示日志 Locastic\Loggastic\DataProvider\ActivityLogProviderInterface
服务包含一些有用的方法来获取活动日志数据
public function getActivityLogsByClass(string $className, array $sort = []): array; public function getActivityLogsByClassAndId(string $className, $objectId, array $sort = []): array; public function getActivityLogsByIndexAndId(string $index, $objectId, array $sort = []): array;
使用它们从 Elasticsearch 获取数据并在您的视图中显示。在 twig 中显示结果的示例
Activity logs for Blog Posts: <br> {% for log in activityLogs %} {{ log.action }} {{ log.objectType }} with {{ log.objectId }} ID at {{ log.loggedAt|date('d.m.Y H:i:s') }} by {{ log.user.username }} {% endfor %}
输出将类似于以下内容
Activity logs for Blog Posts:
Created BlogPost with 1 ID at 01.01.2023 12:00:00 by admin
Updated BlogPost with 1 ID at 02.01.2023 08:30:00 by admin
Deleted BlogPost with 1 ID at 01.01.2023 12:00:00 by admin
b) 使用 ApiPlatform 在 api 端点显示日志 要在 ApiPlatform 端点显示 Loggastic 活动日志,您可以使用 ApiPlatform 的 Elasticsearch 集成:https://api-platform.com/docs/core/elasticsearch/
在 ApiPlatform 端点显示活动日志的示例
#[ApiResource( operations: [ new Get(provider: ItemProvider::class), new GetCollection(provider: CollectionProvider::class), ], order: ["loggedAt" => "DESC"], stateOptions: new Options(index: '*_activity_log'), )] class ActivityLog extends BaseActivityLog { #[ApiProperty(identifier: true)] protected ?string $id = null; }
您可以使用现有的 ApiPlatform 过滤器轻松筛选结果:https://api-platform.com/docs/core/filters/。如果您想在不同字段中获取不同的响应,请使用序列化组或创建一个自定义 DTO。
使用 *_activity_log
索引将返回所有活动日志。如果您只想返回一个实体的日志,请使用确切的索引名称。例如,如果您只想显示 BlogPost
实体的日志,请在 stateOptions
配置中使用 blog_post_activity_log
索引。
就这样!
现在您已经设置了基本的活动日志。每次数据库中记录的实体发生更改时,活动日志将被保存到 Elasticsearch。
自定义指南
现在您已经完成了基本设置,您可以根据需要添加一些附加选项并自定义库。
配置参考
默认配置
# config/packages/loggastic.yaml locastic_loggastic: # directory paths containing loggable classes or xml/yaml files loggable_paths: - '%kernel.project_dir%/Resources/config/loggastic' - '%kernel.project_dir%/src/Entity' # Turn on/off the default Doctrine subscriber default_doctrine_subscriber: true # Turn on/off collection identifier extractor # if set to `true` objects identifiers in collections will be used as array keys # if set to `false` default numeric array keys will be used identifier_extractor: true # ElasticSearch config elastic_host: 'localhost:9200' elastic_date_detection: true #https://elastic.ac.cn/guide/en/elasticsearch/reference/current/date-detection.html elastic_dynamic_date_formats: "strict_date_optional_time||epoch_millis||strict_time" # ElasticSearch index mapping for ActivityLog. https://elastic.ac.cn/guide/en/elasticsearch/reference/current/indices-create-index.html#mappings activity_log: elastic_properties: id: type: keyword action: type: text loggedAt: type: date objectId: type: text objectType: type: text objectClass: type: text dataChanges: type: text user: type: object properties: username: type: text # ElasticSearch index mapping for CurrentDataTracker current_data_tracker: elastic_properties: dateTime: type: date objectId: type: text objectType: type: text objectClass: type: text data: type: text
异步保存日志
活动日志使用Symfony messenger组件,并支持异步处理。如果您想使其异步,请在messenger配置中添加以下消息
framework: messenger: routing: 'Locastic\Loggastic\Message\PopulateCurrentDataTrackersMessage': async 'Locastic\Loggastic\Message\CreateActivityLogMessage': async 'Locastic\Loggastic\Message\DeleteActivityLogMessage': async 'Locastic\Loggastic\Message\UpdateActivityLogMessage': async
重要提示!
为了防止数据损坏,每个可记录对象应只使用一个消费者。
优化messenger以处理大量数据
如果您有大量数据,可能需要多个消费者来处理消息。在这种情况下,您可以配置不同的消息传输方式,并为每个消息使用不同的消费者。第一步是配置传输方式。以下是针对activity_logs_default
和activity_logs_product
队列的AMQP和Doctrine传输配置示例
AMQP传输配置示例
framework: messenger: transports: activity_logs_default: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' options: exchange: name: activity_logs_default queues: activity_logs_default: ~ activity_logs_product: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' options: queues: activity_logs_product: ~ exchange: name: activity_logs_product routing: 'Locastic\Loggastic\Message\PopulateCurrentDataTrackersMessage': activity_logs_default 'Locastic\Loggastic\Message\CreateActivityLogMessage': activity_logs_default 'Locastic\Loggastic\Message\DeleteActivityLogMessage': activity_logs_default 'Locastic\Loggastic\Message\UpdateActivityLogMessage': activity_logs_default
Doctrine传输配置示例
framework: messenger: transports: activity_logs_default: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' options: queue_name: activity_logs_default activity_logs_product: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' options: queue_name: activity_logs_product routing: 'Locastic\Loggastic\Message\PopulateCurrentDataTrackersMessage': activity_logs_default 'Locastic\Loggastic\Message\CreateActivityLogMessage': activity_logs_default 'Locastic\Loggastic\Message\DeleteActivityLogMessage': activity_logs_default 'Locastic\Loggastic\Message\UpdateActivityLogMessage': activity_logs_default
下一步是装饰ActivityLogDispatcher
,并为将消息发送到传输添加您自己的逻辑。在这个例子中,我们将所有消息发送到activity_logs_default
传输,除了针对Product
实体的消息,这些消息被发送到activity_logs_product
传输
<?php namespace App\MessageDispatcher; use App\Entity\Product; use Locastic\Loggastic\Message\ActivityLogMessageInterface; use Locastic\Loggastic\MessageDispatcher\ActivityLogMessageDispatcherInterface; use Symfony\Component\DependencyInjection\Attribute\AsDecorator; #[AsDecorator(ActivityLogMessageDispatcherInterface::class)] class ActivityLogMessageDispatcher implements ActivityLogMessageDispatcherInterface { public function __construct(private readonly ActivityLogMessageDispatcherInterface $decorated) { } public function dispatch(ActivityLogMessageInterface $activityLogMessage, ?string $transportName = null): void { if ($activityLogMessage->getClassName() === Product::class) { $this->decorated->dispatch($activityLogMessage, 'activity_logs_product'); return; } $this->decorated->dispatch($activityLogMessage, $transportName); } }
根据您的项目需求,您可以有更多传输,并根据您自己的逻辑将消息发送到它们。
处理关系
有时您希望记录对某个实体所做的更改以及与该实体的相关实体。例如,如果您使用Doctrine监听器,您将只能得到实际发生更改的实体。假设您想记录具有与ProductVariant
关系的Product
更改。在编辑表单中,只有来自ProductVariant
的字段被更改。即使您在Product
上运行persist()
方法,在这种情况下也只会显示ProductVariant
在Doctrine监听器中。对于这种情况,您可以在ProductVariant
上使用Locastic\Loggastic\Loggable\LoggableChildInterface
<?php namespace App\Entity; use Locastic\Loggastic\Loggable\LoggableChildInterface; class ProductVariant implements LoggableChildInterface { private Product $product; public function getProduct(): Product { return $this->product; } public function logTo(): ?object { return $this->getProduct(); } // ... }
现在对ProductVariant
所做的每次更改都将记录到Product
中。
自定义保存活动日志的事件监听器
您可以使用Locastic\Loggastic\Logger\ActivityLoggerInterface
服务将项目更改保存到Elasticsearch
<?php namespace App\Service; class SomeService { public function __construct(private readonly ActivityLoggerInterface $activityLogger) { } public function logItem($item): void { $this->activityLogger->logCreatedItem($item, 'custom_action_name'); $this->activityLogger->logDeletedItem($item->getId(), get_class($item), 'custom_action_name'); $this->activityLogger->logUpdatedItem($item, 'custom_action_name'); } }
根据您的应用程序逻辑,您需要找到触发日志保存的最佳位置。
在大多数情况下,这可以是Doctrine事件监听器,该监听器在每次数据库更改时触发。Loggastic提供了一个内置的默认使用的Doctrine监听器。如果您想禁用它,可以将loggastic.doctrine_listener_enabled
配置参数设置为false
# config/packages/loggastic.yaml loggastic: doctrine_listener_enabled: false
如果您使用的是ApiPlatform,一个很好的选择是使用它的POST_WRITE事件:https://api-platform.com/docs/core/events/#custom-event-listeners
对于Sylius项目,您可以使用Resource bundle事件:https://docs.sylius.com/en/1.12/book/architecture/events.html
在没有数据更改的情况下保存活动日志
有时您想在没有数据更改的情况下保存活动日志。例如,如果您想记录订单确认电子邮件已发送或某些PDF已下载。
您可以通过将第三个参数设置为true来实现这一点
$this->activityLogger->logUpdatedItem($item, 'Order confirmation sent', true);
贡献
如果您有改进此捆绑包的想法,请随时贡献。如果您有问题或发现了某些错误,请提交一个问题。
支持
想要我们帮助您使用此捆绑包或任何ApiPlatform/Symfony项目?请给我们发邮件至info@locastic.com