locastic/loggastic

Symfony 的 ElasticSearch 活动日志

安装: 4,353

依赖项: 0

建议者: 0

安全: 0

星标: 33

关注者: 9

分支: 4

开放问题: 3

类型:symfony-bundle

v1.1.4-beta 2024-09-18 19:33 UTC

README

Loggastic

Loggastic 是为跟踪对象及其关系的变化而设计的。该库建立在 Symfony 框架 之上,使得实现活动日志并将其存储在 Elasticsearch 上以便快速浏览日志变得容易。

每个跟踪的实体在 ElasticSearch 中将有两个索引

  1. entity_name_activity_log -> 保存对一个对象执行的 CRUD 操作。此外,编辑操作还会保存之前和之后值。
  2. 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_defaultactivity_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