- 后端
- 工作流自动化
- 流程编排
- 低代码
【免费下载链接】elsa-core
The Workflow Engine for .NET
导读
本文围绕 Elsa(The Workflow Engine for .NET)诊断能力中的“结构化日志持久化”(Structured Log Persistence)专项展开,完整梳理该特性从需求分解、存储抽象重构到 SQLite 持久化落地与关系型提供方扩展的全部实现路径。读者将掌握:结构化日志模块如何把“查询存储”“实时推送”“日志采集”三类职责拆分为可替换契约;如何在不回归既有行为的前提下新增 SQLite 持久化(FluentMigrator 建表、异步批量写队列、优雅停机冲刷、可选保留策略);以及如何按任务清单分阶段验收并在未来接入 SQL Server、PostgreSQL、MySQL 等更多关系型数据库。
该专项的完整设计文档位于specs/005-structured-log-persistence/目录,本文以其中的tasks.md任务清单为骨架,结合spec.md、plan.md、data-model.md、quickstart.md、contracts/persistence-contract.md以及src/modules/下的实际实现展开讲解。
一、特性背景与设计目标
1.1 输入需求
特性的原始需求一句话可以概括为(见 spec.md):
让结构化日志存储成为一个可插拔的关注点:保留内存支持,先提供简单易用的 SQLite 持久化存储,使用 FluentMigrator 管理 Schema,并推迟 OpenTelemetry/导出器范围。
即:存储可插拔、内存默认、SQLite 先行、FluentMigrator 管 Schema、导出器后置。
1.2 关键设计决策(Clarifications)
在规格澄清阶段(2026-05-12 与 2026-05-13 两次会话)敲定的核心决策如下:
| 决策项 | 结论 |
|---|---|
| 是否现在就加 Logstash / Datadog / Splunk / Loki / Seq 等厂商 Sink? | 否,第一刀保持最小范围 |
| 是否现在就加 OTLP 日志导出器? | 否,推迟到诊断 OpenTelemetry 边界更清晰时 |
| 初始存储提供方有哪些? | 保留内存存储,新增 opt-in 的 SQLite 持久化 |
| SQLite 是否做成一次性实现? | 否,作为第一个关系型提供方,便于后续扩展 SQL Server / PostgreSQL / MySQL |
| 是否使用 EF Core? | 否,本特性避免 EF Core 与各提供方的 EF 迁移 |
| Schema 创建与升级用谁? | FluentMigrator 做版本化迁移;热路径用显式 SQL / Dapper 风格访问 |
| SQLite 写入的持久性保证? | 异步批量写入 + 优雅停机冲刷;进程崩溃可能丢失排队未刷的事件 |
| 写队列满了怎么办? | 丢弃最新入队事件并上报丢弃计数/指标,绝不阻塞日志调用、不无限增长内存 |
| SQLite 迁移何时运行? | 默认启动时自动迁移,提供 opt-out |
| 时间戳如何存? | 以 UTC ISO-8601 文本存储 |
| 默认保留策略? | 默认不删除;仅当配置了最大年龄或最大行数时才执行清理 |
1.3 范围边界
- 明确不做:OTLP 日志导出、厂商 Sink、原始控制台流式输出、Trace/Metric 探索(见 quickstart.md 的 Out of scope 与 spec.md 的 Assumptions)。
- 必须保留:脱敏(redaction)必须先于任何存储与实时投递(FR-005);Studio 的 REST 与 SignalR 契约不变(FR-024、US3 验收场景 3)。
二、总体架构:存储 / 实时 / 采集三职责拆分
2.1 为什么必须重构
在持久化之前,IStructuredLogProvider同时承担了“查询最近日志”“订阅实时日志”“发布日志事件”等多个职责。若直接为 SQLite 写一个一次性的 Store,会造成不可扩展的死胡同。因此plan.md的 Phase 2 明确要求:拆分 Store 与 Live-Feed 关注点,同时保留现有IStructuredLogProvider门面(plan.md 的 Summary)。
2.2 核心契约(Phase 2,任务 T009–T011)
设计文档 contracts/persistence-contract.md 定义了四个核心契约,仓库中对应的实现位于 src/modules/Elsa.Diagnostics.StructuredLogs/Contracts/:
IStructuredLogSink:只追加的写入边界,接收单个事件或批量事件,不必支持查询。源码见 IStructuredLogSink.cs。IStructuredLogStore : IStructuredLogSink:可查询的存储边界,负责追加脱敏事件、按StructuredLogFilter查询最近事件、列出StructuredLogSource。源码见 IStructuredLogStore.cs。IStructuredLogLiveFeed:运行时实时投递边界,向 SignalR 订阅者发布新事件、应用订阅过滤器、在订阅队列溢出时报告丢弃摘要,不保证持久性。源码见 IStructuredLogLiveFeed.cs。
在契约层面,三个接口的形态为:
// IStructuredLogSink —— 只追加 public interface IStructuredLogSink { ValueTask WriteAsync(StructuredLogEvent logEvent, CancellationToken cancellationToken = default); ValueTask WriteManyAsync(IReadOnlyCollection<StructuredLogEvent> logEvents, CancellationToken cancellationToken = default); } // IStructuredLogStore —— 可查询存储 public interface IStructuredLogStore : IStructuredLogSink { ValueTask<RecentStructuredLogsResult> QueryAsync(StructuredLogFilter filter, CancellationToken cancellationToken = default); ValueTask<IReadOnlyCollection<StructuredLogSource>> ListSourcesAsync(CancellationToken cancellationToken = default); } // IStructuredLogLiveFeed —— 实时推送 public interface IStructuredLogLiveFeed { ValueTask PublishAsync(StructuredLogEvent logEvent, CancellationToken cancellationToken = default); IAsyncEnumerable<StructuredLogStreamItem> SubscribeAsync(StructuredLogFilter filter, CancellationToken cancellationToken = default); }2.3 门面保持不变:IStructuredLogProvider
REST 端点与 SignalR 订阅管理器继续面向IStructuredLogProvider(T021–T023),它由新的组合型实现DefaultStructuredLogProvider承担(T015,DefaultStructuredLogProvider.cs):内部组合IStructuredLogStore与IStructuredLogLiveFeed,PublishAsync依次写 Store、推 LiveFeed,查询与来源列表透传 Store。默认注册路径在 ServiceCollectionExtensions.cs(T016)。
内存默认实现被拆分为可复用的两块(T012–T013):
InMemoryStructuredLogStore(InMemoryStructuredLogStore.cs):负责有界环形缓冲(RingBuffer)内的最近历史查询、来源列表、容量与过滤行为;InMemoryStructuredLogLiveFeed(InMemoryStructuredLogLiveFeed.cs):负责订阅者分发与丢弃摘要;InMemoryStructuredLogProvider(InMemoryStructuredLogProvider.cs)保留为兼容门面,将请求转发给上述 store 与 liveFeed。
这一拆分使“默认内存行为”与“未来任何持久化 Store”都能复用同一套 REST/SignalR 接入层。
2.4 共享行为约定(README 明确说明)
核心模块 README.md 记录了所有 Store 实现共享的语义:
MaxRecentLogQuerySize是所有IStructuredLogStore的默认Take与上限钳制(由StructuredLogsOptions.ClampRecentLogQueryTake实现,负值按 0 处理);- 查询排序固定为
Timestamp, ReceivedAt, SourceId, Sequence, Id; ListSources优先使用进程内源注册表提供身份元数据,SourceHeartbeatTimeout到期后标记为Stale;QueryAsync.DroppedEvents表示内存环形缓冲溢出计数;关系型写队列丢弃通过存储诊断上报,不复用该字段;- 大小写不同的相等过滤并非跨实现可移植:内存为
OrdinalIgnoreCase,SQL 取决于数据库排序规则。
三、分阶段任务地图(tasks.md 全文骨架)
tasks.md 将整个实现组织为 6 个阶段、3 个用户故事,共 73 项任务(T001–T073),且全部标记为已完成([X])。任务命名格式为:
[ID] [P?] [Story] Description[P]:满足前置条件后可与其它标记任务并行;[Story]:来自 spec.md 的用户故事标签;- 每项任务都包含要创建或修改的主文件路径。
3.1 Phase 1:搭建(Setup,T001–T008)
目的:为关系型与 SQLite 持久化建立包骨架、包引用与解决方案条目。
| 任务 | 产出 |
|---|---|
| T001–T002 | 创建Elsa.Diagnostics.StructuredLogs.Persistence.Relational与.Persistence.Sqlite两个模块项目 |
| T003–T004 | 创建关系型单元测试项目与 SQLite 集成测试项目 |
| T005–T006 | 创建两个模块的 Shell 特性骨架(StructuredLogRelationalPersistenceFeature、SqliteStructuredLogPersistenceFeature) |
| T007 | 将新项目加入Elsa.sln |
| T008 | 在 Directory.Packages.props 中集中管理 FluentMigrator 与 SQLite 依赖版本 |
注意 T008 体现的是“版本集中管理”约定:新增的 FluentMigrator、Dapper 等依赖版本统一收口到中央Directory.Packages.props(见 spec.md 的 Assumptions)。
3.2 Phase 2:基础存储重构(Foundational,T009–T016)
目的:拆分 Store 与 Live-Feed 关注点,同时保持IStructuredLogProvider门面不变。
关键约束(Critical):在内存默认实现仍能通过既有结构化日志测试之前,不得开始任何 SQLite 故事工作(tasks.md 第 35 行)。这一“先保持不回归、再谈新功能”的顺序是 MVP 策略的基石。
本阶段即 2.2/2.3 节所述的内容:新增三个契约(T009–T011),将内存行为重构进 Store/LiveFeed(T012–T013),保留兼容门面(T014),新增组合门面(T015),更新 DI 注册(T016)。Checkpoint:既有结构化日志行为在新抽象背后被完整保留。
3.3 Phase 3:US1 —— 保留既有内存行为(P1,MVP)
- 目标:只配置
UseStructuredLogs的主机获得与之前完全相同的有界内存最近查询、来源列表、实时流、脱敏与丢弃事件行为(T021–T023 确保 REST 最近日志端点、来源端点与 SignalR 订阅都继续走IStructuredLogProvider)。 - 独立测试:不配置持久化、启用结构化日志、发射若干
ILogger记录,验证最近查询、过滤器、来源列表、实时订阅、脱敏与丢弃摘要。 - 对应测试任务:T017(默认注册兼容性)、T018–T020(内存最近查询 / 来源列表 / 实时丢弃事件)。
- Checkpoint:US1 完全可用且可独立测试。验证命令见 任务 T024:
dotnet test test/unit/Elsa.Diagnostics.StructuredLogs.UnitTests/Elsa.Diagnostics.StructuredLogs.UnitTests.csproj。
3.4 Phase 4:US2 —— SQLite 持久化存储(P2)
- 目标:主机可选择 SQLite 存储;默认启动迁移;脱敏日志跨重启持久化;可查询持久化记录;优雅停机冲刷排队写入;可显式配置保留策略。
- 独立测试:配置 SQLite 存储、发射事件、用同一数据库文件刷新或重建服务,验证持久化最近查询、过滤、迁移行为、队列溢出行为、时间戳存储与保留行为。
- 对应测试任务(T025–T032):空库迁移、跨提供方重建持久化、持久化脱敏、过滤覆盖(level/category/source/workflow/correlation/trace/time/limit)、写队列冲刷与
DroppedWriteCount溢出、启动迁移 opt-out、ISO-8601 时间戳存储、保留(opt-in 清理与默认不删除)。 - 对应实现任务(T033–T054):关系型记录模型、关系型选项、连接工厂与方言契约、Schema 迁移器契约、SQL 构建器、JSON/时间戳映射器、FluentMigrator 迁移、关系型 Store、有界写缓冲、保留清理服务、服务注册扩展、关系型与 SQLite 的 Feature/ShellFeature 注册、SQLite 连接工厂/方言/迁移器/启动服务/流式配置扩展。
3.5 Phase 5:US3 —— 关系型持久化可扩展(P3)
- 目标:未来的关系型提供方可以复用共享的关系型 Store,只需提供连接工厂、方言与 FluentMigrator Runner 配置,无需改动核心结构化日志模块或 Studio 契约。
- 独立测试:审查代码边界并运行使用假方言/连接服务的关系型测试(T056–T059):SQL 构建器(假方言)、映射器(JSON 与 UTC ISO-8601 时间戳)、迁移元数据/版本、以及证明
Elsa.Diagnostics.StructuredLogs无 SQLite 依赖的核心边界测试。 - 对应实现任务(T060–T062):将提供方专属 SQL 留在核心模块之外、SQLite 专属服务留在 SQLite 包内、补充 关系型提供方 README。
- Checkpoint:SQLite 是第一个关系型提供方,而不是一条一次性的持久化路径。
3.6 Phase 6:文档与打磨(T064–T073)
更新结构化日志 README 的存储模式指引(T064)、SQLite 持久化 README(T065)、Speckit quickstart 实现说明(T066)、仅当样例显式选择 SQLite 时才接线样例主机(T067,Program.cs),然后依次执行四个构建/测试命令(T068–T072)。T073 用rg "OpenTelemetry|OTLP|Datadog|Logstash|Splunk|Loki|Seq" src/modules/Elsa.Diagnostics.StructuredLogs* specs/005-structured-log-persistence确认只剩范围外的文档引用。
四、数据模型与表结构
4.1 领域实体(data-model.md)
data-model.md 定义了本特性的关键实体:
StructuredLogStore:可查询存储边界(追加、按StructuredLogFilter查询、列来源,不负责订阅者背压)。StructuredLogLiveFeed:实时投递边界(发布新事件、应用订阅过滤器、报告溢出丢弃摘要,不保证持久性)。StructuredLogSink:只追加边界,接受单条或批量事件,可被 Store 内部使用、也可被未来的导出器包使用。RelationalStructuredLogRecord:StructuredLogEvent的关系型表示,包含Id、Sequence、Timestamp(UTC ISO-8601 文本)、ReceivedAt、Level、Category、EventId、EventName、Message、MessageTemplate、ExceptionJson、ScopesJson、PropertiesJson、TraceId、SpanId、CorrelationId、TenantId、WorkflowDefinitionId、WorkflowInstanceId、SourceId。RelationalStructuredLogSourceRecord:可选的关系型来源记录(机器名、进程、Kubernetes/容器元数据、StartedAt/LastSeen、状态)。StructuredLogRetentionOptions:MaxAge、MaxRows、CleanupInterval、RunCleanupOnStartup;默认不删除任何记录。StructuredLogWriteQueueOptions:Capacity、BatchSize、FlushInterval、ShutdownFlushTimeout、DroppedWriteCount;溢出时丢弃新事件,绝不阻塞日志调用或无限分配内存。RelationalStructuredLogDialect:标识符引用、参数前缀、Limit/Take 语法、时间戳存储与比较、自由文本谓词构造、供 FluentMigrator 分支用的提供方名。StructuredLogMigrationRunner:运行 FluentMigrator 迁移、空库建表、按版本顺序升级、SQLite 默认启动执行且可关闭、文档化共享数据库的多实例启动锁约束。
4.2 关系型记录与建表迁移
关系型记录模型在 RelationalStructuredLogRecord.cs(T033)。SQLite 使用共享关系型迁移创建表(T040,M001_CreateStructuredLogTables.cs):
- 表名
StructuredLogEvents,迁移版本号[Migration(2026051301)]; - 标量过滤字段建为可查询列:
Id(String(64) 主键)、Sequence(Int64)、Timestamp/ReceivedAt(String(40) 非空,存 UTC ISO-8601 文本)、Level(Int32)、Category(String(512))、EventId、EventName; - 复杂负载存 JSON 文本:
Message、MessageTemplate、ExceptionJson、ScopesJson、PropertiesJson(String(int.MaxValue)); - Elsa 上下文列:
TenantId、WorkflowDefinitionId、WorkflowInstanceId、CorrelationId、SourceId; - 建立 10 个索引:
Timestamp、ReceivedAt(降序)、Level、Category、SourceId、TenantId、WorkflowDefinitionId、WorkflowInstanceId、CorrelationId、TraceId。
这与>// StructuredLogWriteQueueOptions Capacity = 10_000 // 队列容量 BatchSize = 100 // 每次刷写的最大事件数 FlushInterval = 1s // 后台刷写最大间隔 ShutdownFlushTimeout = 10s // 优雅停机冲刷超时 // StructuredLogRetentionOptions MaxAge = null // 未配置则不按时间删除 MaxRows = null // 未配置则不按行数删除 CleanupOnStartup = false
SQLite 选项默认值(SqliteStructuredLogOptions.cs):ConnectionString = "Data Source=elsa-structured-logs.db",RunMigrationsOnStartup = true。
5.3 保留清理服务(T043)
StructuredLogRetentionService.cs 实现IStructuredLogRetentionService.CleanupAsync:
- 只有
MaxAge或MaxRows被配置时才执行删除(FR-020,默认零删除); - 按
MaxAge用ReceivedAt < Cutoff删除过期记录(BuildDeleteOlderThan); - 按
MaxRows用“ReceivedAt DESC, Sequence DESC, Id DESC排序后取第 N 行之后的 Id”删除超量记录(BuildDeleteRowsBeyondMax); - 由 SQLite 启动服务决定是否在启动时立即执行(
CleanupOnStartup)。
5.4 SQLite 启动服务(T051)
SqliteStructuredLogStartupService.cs 实现IHostedService与IStartupTask:
- 用
SemaphoreSlim保证幂等执行(_executed标志); RunMigrationsOnStartup == true时调用IStructuredLogSchemaMigrator.MigrateAsync;Relational.Retention.CleanupOnStartup == true时调用retentionService.CleanupAsync。
注册入口在 SqliteStructuredLogsModuleExtensions.cs(T052):AddSqliteStructuredLogPersistence注册连接工厂、方言、Schema 迁移器、启动服务(HostedService +IStartupTask),并把SqliteStructuredLogOptions.Relational拷贝到RelationalStructuredLogOptions(Copy方法逐项同步 WriteQueue 与 Retention 五个选项),最后调用AddRelationalStructuredLogPersistence()挂上共享关系型持久化注册(T044)。
六、配置实战:内存默认与 SQLite 持久化
6.1 默认内存存储(零配置)
不配置任何持久化提供方即可使用有界内存最近历史与实时 SignalR 流(来自 quickstart.md):
services.AddElsa(elsa => { elsa.UseStructuredLogs(options => { options.RecentLogCapacity = 5_000; options.MaxRecentLogQuerySize = 1_000; }); });相关选项均定义在 StructuredLogsOptions.cs:RecentLogCapacity = 5_000、SubscriberChannelCapacity = 1_000、MaxRecentLogQuerySize = 1_000、SourceHeartbeatTimeout = 30s,以及一组默认脱敏名称/正则(authorization、token、password、secret、api-key、cookie、connection-string与 Bearer/Key 类正则)。
6.2 SQLite 持久化(opt-in)
安装Elsa.Diagnostics.StructuredLogs.Persistence.Sqlite包后,在UseStructuredLogs中通过UseSqliteStorage显式选择 SQLite(来自 quickstart.md 与 SQLite README):
services.AddElsa(elsa => { elsa.UseStructuredLogs(structuredLogs => { structuredLogs.UseSqliteStorage("Data Source=elsa-structured-logs.db", sqlite => { sqlite.RunMigrationsOnStartup = true; sqlite.Relational.WriteQueue.Capacity = 10_000; sqlite.Relational.WriteQueue.BatchSize = 100; }); }); });随后映射既有端点与 Hub:
app.UseStructuredLogs();Studio 无需任何 API 改动,继续使用以下既有契约:
/diagnostics/structured-logs/recent—— 最近日志查询/diagnostics/structured-logs/sources—— 来源列表/diagnostics/structured-logs/storage—— 存储诊断/elsa/hubs/diagnostics/structured-logs—— SignalR 实时流
注意
UseSqliteStorage还提供无连接字符串的重载(默认Data Source=elsa-structured-logs.db),源码见 SqliteStructuredLogsModuleExtensions.cs。
6.3 保留策略配置
SQLite 默认不删除任何持久化记录。需要限制存储增长时显式配置(quickstart.md):
sqlite.Relational.Retention.MaxAge = TimeSpan.FromDays(14); sqlite.Relational.Retention.MaxRows = 250_000; sqlite.Relational.Retention.CleanupOnStartup = true;6.4 迁移策略说明
SQLite 存储默认在启动时运行 FluentMigrator 迁移(RunMigrationsOnStartup = true),这是 SQLite 的推荐默认。生产环境如选择在部署工具中一次性准备 Schema,可设false。对于未来的共享关系型数据库(SQL Server / PostgreSQL),spec 明确要求文档化“多实例启动锁”考虑(FR-030、quickstart 的 Migrations 节):生产部署可能希望只在部署阶段运行一次迁移,而不是由每个应用实例各自执行。
6.5 写入缓冲行为摘要
SQLite 写入通过有界后台队列批量落盘:
- 优雅停机:
ShutdownFlushTimeout(默认 10s)内尽力冲刷排队事件; - 进程崩溃:排队但未刷写的事件可能丢失(FR-016);
- 队列满:丢弃最新事件,递增
DroppedWriteCount,并输出警告摘要;日志调用不阻塞、内存有界。
七、测试策略与验收
7.1 测试矩阵
任务清单将测试与用户故事绑定,确保每个增量可独立验证(tasks.md 的 Tests 说明):
| 故事 | 测试任务 | 覆盖点 | 测试文件(均已存在) |
|---|---|---|---|
| US1 | T017–T020 | 默认注册兼容、内存最近查询、来源列表、实时丢弃事件 | Elsa.Diagnostics.StructuredLogs.UnitTests |
| US2 | T025–T032 | 空库迁移、跨提供方重建持久化、持久化脱敏、过滤、队列冲刷与溢出、迁移 opt-out、ISO-8601 时间戳、保留 | Elsa.Diagnostics.StructuredLogs.Persistence.Sqlite.IntegrationTests 下的SqliteStructuredLogMigrationTests.cs、SqliteStructuredLogStoreTests.cs、SqliteStructuredLogFilterTests.cs、SqliteStructuredLogWriteQueueTests.cs、SqliteStructuredLogTimestampTests.cs、SqliteStructuredLogRetentionTests.cs、SqliteStructuredLogSourceTests.cs |
| US3 | T056–T059 | SQL 构建器(假方言)、映射器(JSON + UTC ISO-8601)、迁移元数据/版本、核心模块无 SQLite 依赖 | Elsa.Diagnostics.StructuredLogs.Persistence.Relational.UnitTests |
7.2 与成功标准的对应
spec.md 的 Success Criteria(SC-001 至 SC-012)与上述测试一一对应:既有内存测试继续通过(SC-001)、SQLite 跨重启持久化(SC-002)、过滤覆盖八类字段(SC-003)、FluentMigrator 空库建表(SC-004)、保留仅按配置清理(SC-005)、优雅停机冲刷(SC-006)、队列满丢弃并上报计数(SC-007)、启动迁移默认/可关(SC-008)、UTC ISO-8601 文本一致存储与过滤(SC-009)、默认零删除(SC-010)、文档双模式说明(SC-011)、核心模块零 SQLite 引用(SC-012)。
7.3 验证命令
任务清单中的构建/测试命令可直接执行:
dotnet build src/modules/Elsa.Diagnostics.StructuredLogs/Elsa.Diagnostics.StructuredLogs.csproj dotnet build src/modules/Elsa.Diagnostics.StructuredLogs.Persistence.Sqlite/Elsa.Diagnostics.StructuredLogs.Persistence.Sqlite.csproj dotnet test test/unit/Elsa.Diagnostics.StructuredLogs.UnitTests/Elsa.Diagnostics.StructuredLogs.UnitTests.csproj dotnet test test/unit/Elsa.Diagnostics.StructuredLogs.Persistence.Relational.UnitTests/Elsa.Diagnostics.StructuredLogs.Persistence.Relational.UnitTests.csproj dotnet test test/integration/Elsa.Diagnostics.StructuredLogs.Persistence.Sqlite.IntegrationTests/Elsa.Diagnostics.StructuredLogs.Persistence.Sqlite.IntegrationTests.csproj7.4 手动验收步骤(quickstart 提供)
- 启动开启 SQLite 结构化日志存储的 Elsa Server;
- 发射不同 level、category、workflow ID、correlation ID 的若干
ILogger记录; - 在 Studio 查询最近日志并验证各过滤器生效;
- 用同一 SQLite 数据库文件重启主机;
- 再次查询最近日志,确认重启前的事件仍可读;
- 确认时间戳以 UTC ISO-8601 值存储与过滤;
- 在测试环境调低保留设置,验证旧行/超量行被清理;
- 在测试环境灌满写队列,验证最新事件被丢弃且丢弃计数可见。
八、依赖关系、并行策略与实施顺序
8.1 阶段依赖
tasks.md 的 Dependencies 一节明确了依赖链:
- Phase 1:无依赖;
- Phase 2:依赖 Phase 1,阻塞所有用户故事;
- Phase 3(US1):依赖 Phase 2,是 MVP;
- Phase 4(US2):依赖 Phase 2,应在 US1 验证之后进行;
- Phase 5(US3):依赖 US2 产出的关系型与 SQLite 代码;
- Phase 6:依赖所选故事的实现。
8.2 用户故事依赖
- US1(P1):第一个可执行切片,在存储重构后保持既有行为;
- US2(P2):在新存储边界之上增加持久化 SQLite 存储;
- US3(P3):为未来关系型存储固化提供方边界。
8.3 并行机会
任务清单明确标注的可并行任务组:
- T001–T006(包骨架);
- T009–T011(契约);
- US1 测试 T017–T020(Phase 2 之后);
- US2 测试 T025–T032(项目骨架存在之后);
- US3 测试 T056–T059(关系型契约存在之后);
- 文档任务 T064–T066(实现 API 稳定之后)。
8.4 实施策略:MVP 先行、增量交付
MVP First:先完成 Phase 1 + Phase 2 → 只完成 Phase 3 → 运行既有测试确认零回归 → 存储边界稳定后再进入 SQLite。
Incremental Delivery:US1(内存兼容)→ US2(SQLite 持久化:迁移、队列、时间戳、保留)→ US3(关系型扩展)→ Polish(文档、样例、构建、测试)。
九、边界与注意事项
9.1 边缘场景(spec.md Edge Cases)
设计文档明确列出了实现必须考虑的边缘情况:
- 持久化写入不得阻塞
ILogger调用方:SQLite 允许崩溃丢失排队未刷事件; - 写队列满:丢弃最新事件并上报丢失计数,而非阻塞或无限增长;
- 主机可能在迁移完成前发射日志(启动顺序错误);
- 多应用实例可能并发对共享关系型数据库执行迁移;
- SQLite 文件路径可能缺失、相对或指向无写权限目录;
- 进程可能在事件进入后台队列后、刷写前崩溃;
- JSON 字段上的自由文本过滤可能是提供方相关、初始为近似匹配;
- 保留清理可能与最近查询或实时订阅竞争;
- Schema 变更必须尽量保留已持久化的事件。
9.2 实现约束(tasks.md Notes)
- 本特性不实现 OTLP 或厂商 Sink;
- 不为结构化日志持久化引入 EF Core;
- 保持“先脱敏再存储”;
- Studio 的 REST 与 SignalR 契约不变;
- 优先使用小而显式的 SQL,而非精巧的通用查询抽象(除非实现压力证明必要)。
9.3 清单核对
checklists/requirements.md 的 8 项要求全部勾选通过:内存默认、SQLite 为唯一初始持久化提供方、存储/实时/门面职责分离、脱敏先行、FluentMigrator 而非 EF Core、未来关系型提供方不破坏 Studio 契约、含保留与优雅停机冲刷、显式推迟 OpenTelemetry 与厂商导出器。
十、总结
从 tasks.md 的 73 项任务可以看出,Elsa 的结构化日志持久化特性走的是“先抽象、再落地、后扩展”的工程路径:Phase 2 的存储/实时/采集三契约拆分让IStructuredLogProvider门面与 REST/SignalR 契约完全稳定;US2 以 SQLite 作为第一个关系型提供方验证了 FluentMigrator 迁移、有界异步写队列、UTC ISO-8601 时间戳与 opt-in 保留的完整闭环;US3 通过方言与连接工厂隔离保证了 SQL Server / PostgreSQL / MySQL 等后续提供方可以复用共享关系型 Store。对想要为 Elsa 主机接入持久化日志、或自行扩展更多关系型数据库的开发者,本文提供的配置示例、源码路径与测试命令可以直接复用。
- 后端
- 工作流自动化
- 流程编排
- 低代码
【免费下载链接】elsa-core
The Workflow Engine for .NET
相关推荐
Elsa 结构化日志持久化:存储契约设计与 SQLite 落地实战
Elsa 结构化日志持久化:存储契约设计与 SQLite 落地实战 导读 本文围绕 elsa core 仓库 specs/005 structured log
后端工作流自动化流程编排低代码Elsa 结构化日志持久化快速上手:从零配置内存存储到 SQLite 持久化存储
Elsa 结构化日志持久化快速上手:从零配置内存存储到 SQLite 持久化存储 导读 本文基于 specs/005 structured log persis
后端工作流自动化流程编排低代码Elsa Workflows 结构化日志持久化设计解析:从内存缓冲区到 SQLite 的存储抽象与写入可靠性
Elsa Workflows 结构化日志持久化设计解析:从内存缓冲区到 SQLite 的存储抽象与写入可靠性 导读 本文基于 Elsa 开源仓库中的结构化日志持
后端工作流自动化流程编排低代码
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考