Add Wolfgang.Etl.ErrorPolicies package (lockstep 0.21.0) - #347
Conversation
New second package built from this repo, versioned in lockstep with Wolfgang.Etl.Abstractions (same <Version>, one tag — like TestKit + TestKit.Xunit). Hosts the shared generic ItemErrorPolicy factory for the family's OnError hook so the policies are defined once instead of per format: - Skip / Abort (Func<ItemErrorContext, ItemErrorAction> properties) - SkipAndLog(ILogger) - SkipAndDeadLetter / SkipDeadLetterAndLog, each overloaded for a caller-owned ICollection<ItemErrorContext> or a ChannelWriter<ItemErrorContext> The core Abstractions assembly keeps its minimal deps; only this package takes Microsoft.Extensions.Logging.Abstractions (all TFMs) and System.Threading.Channels (netFx / netstandard2.0 only — in-box on net5.0+). Bumps Abstractions to 0.21.0 for the lockstep release; PackageValidation baseline stays at last-published 0.20.0. No release.yaml change needed — it already packs every src/ project, validates the tag against any src <Version>, and pushes all nupkgs. 14 unit tests pass net462 -> net10.0; full-matrix build clean; pack emits the correct per-TFM dependency groups (Abstractions 0.21.0 lockstep dep on every TFM). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
BenchmarkDotNet
Details
| Benchmark suite | Current: 3d506c0 | Previous: 5e5e958 | Ratio |
|---|---|---|---|
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_NoProgress(RecordCount: 1000) |
31393.145802815754 ns (± 197.45291732008621) |
31583.873189290363 ns (± 115.28689686427228) |
0.99 |
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_WithProgress(RecordCount: 1000) |
34512.90861002604 ns (± 247.65949195033753) |
35160.96792602539 ns (± 104.53835631316898) |
0.98 |
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_NoProgress(RecordCount: 100000) |
3066479.3606770835 ns (± 12488.839573303052) |
3107534.1640625 ns (± 17584.772051305485) |
0.99 |
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_WithProgress(RecordCount: 100000) |
3367265.0989583335 ns (± 2229.19415989582) |
3448047.61328125 ns (± 4145.027564723044) |
0.98 |
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.FluentPipeline(RecordCount: 1000) |
29232.722712198894 ns (± 80.0839446999554) |
29626.396423339844 ns (± 122.33354576748134) |
0.99 |
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.ManualComposition(RecordCount: 1000) |
28957.610438028973 ns (± 22.5270248379915) |
28745.347229003906 ns (± 56.763752733836895) |
1.01 |
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.BaseClassComposition(RecordCount: 1000) |
67740.15051269531 ns (± 250.4486726511416) |
69584.28100585938 ns (± 220.36328762445214) |
0.97 |
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.FluentPipeline(RecordCount: 100000) |
2894727.7265625 ns (± 1317.622871382007) |
2835179.5182291665 ns (± 5152.038095929195) |
1.02 |
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.ManualComposition(RecordCount: 100000) |
2913623.6536458335 ns (± 716.0158873807541) |
2925295.2018229165 ns (± 3396.484964473814) |
1.00 |
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.BaseClassComposition(RecordCount: 100000) |
6695888.294270833 ns (± 7163.478585605702) |
6793926.755208333 ns (± 24027.364375768884) |
0.99 |
This comment was automatically generated by workflow using github-action-benchmark.
There was a problem hiding this comment.
🟡 Not ready to approve
The new package/docs repeatedly describe an "OnError" hook, but the Abstractions API names the policy hook OnItemError, so the current wording is misleading and should be corrected for accuracy.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR introduces a new lockstep-versioned package, Wolfgang.Etl.ErrorPolicies, to centralize reusable item-error policy delegates (skip/abort/log/dead-letter) so downstream format-specific ETL libraries don’t duplicate the same On*Error policy implementations. It also bumps Wolfgang.Etl.Abstractions to 0.21.0 to keep both packages aligned for release.
Changes:
- Added new
Wolfgang.Etl.ErrorPoliciespackage (multi-TFM) withItemErrorPolicy+ cachedLoggerMessagedelegates. - Added a new unit test project and coverage for the policy factory methods across TFMs.
- Bumped
Wolfgang.Etl.Abstractionsversion to0.21.0and updated solution + changelog for the new package.
File summaries
| File | Description |
|---|---|
| tests/Wolfgang.Etl.ErrorPolicies.Tests.Unit/Wolfgang.Etl.ErrorPolicies.Tests.Unit.csproj | Adds new multi-targeted unit test project for the ErrorPolicies package. |
| tests/Wolfgang.Etl.ErrorPolicies.Tests.Unit/ItemErrorPolicyTests.cs | Adds tests for Skip/Abort/Log/DeadLetter policy behaviors and null-guarding. |
| src/Wolfgang.Etl.ErrorPolicies/Wolfgang.Etl.ErrorPolicies.csproj | Introduces the new packable library project and its package dependencies/metadata. |
| src/Wolfgang.Etl.ErrorPolicies/PublicAPI.Unshipped.txt | Declares the initial public API surface for the new package. |
| src/Wolfgang.Etl.ErrorPolicies/PublicAPI.Shipped.txt | Establishes shipped API baseline header for first release. |
| src/Wolfgang.Etl.ErrorPolicies/ItemErrorPolicyLog.cs | Adds cached LoggerMessage delegates to minimize per-item logging allocations. |
| src/Wolfgang.Etl.ErrorPolicies/ItemErrorPolicy.cs | Implements the ready-made policy factory methods (skip/abort/log/dead-letter). |
| src/Wolfgang.Etl.Abstractions/Wolfgang.Etl.Abstractions.csproj | Bumps Abstractions version to 0.21.0 for lockstep release. |
| ETL-Abstractions.sln | Adds the new library + test projects to the solution. |
| CHANGELOG.md | Documents the new ErrorPolicies package and its surface area in Unreleased. |
Review details
Suppressed comments (1)
src/Wolfgang.Etl.ErrorPolicies/ItemErrorPolicy.cs:35
- This comment refers to leaving "OnError" unset, but the core Abstractions API uses OnItemError (protected virtual) as the policy hook. Aligning the terminology here avoids confusion.
/// A policy that re-throws the failure and stops the run. Equivalent to leaving <c>OnError</c>
/// unset, provided for symmetry and explicitness.
- Files reviewed: 10/10 changed files
- Comments generated: 3
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
# Conflicts: # CHANGELOG.md
Makes ErrorPolicies' assign-a-policy design real. ExtractorBase / LoaderBase /
TransformerBase gain:
public Func<ItemErrorContext, ItemErrorAction> ErrorPolicy { get; init; }
non-null, defaulting to a fail-fast AbortPolicy, throw-on-null. The base
OnItemError now delegates to it, so `ErrorPolicy = ItemErrorPolicy.SkipAndLog(...)`
works on any stage with no per-type property or override — while overriding
OnItemError is still available for stage-internal logic. Supersedes the earlier
"no public base property" decision; the rationale comment is rewritten and the
resume caveat (Skip = swallow-and-stop on formats that can't resume) documented.
- ErrorPolicy on the two/three-type-parameter bases (convenience bases inherit it)
- PublicAPI.Unshipped entries; +6 base tests (default fail-fast, assigned policy is
used by the base OnItemError, null throws) across all three stages
- ItemErrorPolicy package docs + csproj description + CHANGELOG updated from the
override-and-invoke wording back to the assignable `ErrorPolicy = ...` form
Full-matrix build clean (0/0); Abstractions PackageValidation passes vs 0.20.0
(additive); Abstractions unit suite 463 green, ErrorPolicies 14 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update: added
|
The channel dead-letter policies use non-blocking TryWrite because the OnItemError hook is synchronous. On a full bounded channel that drops the failure record. SkipDeadLetterAndLog(ChannelWriter, ILogger) now logs a distinct warning (EventId 2, ItemDeadLetterDropped) when the write is dropped, so the loss is never silent — throwing would convert an error-sink overflow into a full pipeline abort, which is worse. The logger-less SkipAndDeadLetter(ChannelWriter) overload keeps the documented silent drop (no logger to escalate to; caller controls overflow via BoundedChannelFullMode). Adds a bounded-channel-full test and CHANGELOG note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a second package built from this repo,
Wolfgang.Etl.ErrorPolicies, versioned in lockstep withWolfgang.Etl.Abstractions(same<Version>, one tag — the TestKit + TestKit.Xunit pattern). It hosts a shared genericItemErrorPolicyfactory for the family'sOnErrorhook, so the ready-made error policies are defined once instead of duplicated per format library.Why a separate package (decision: "C′")
The policies are format-agnostic and pair with the
#84ItemError*types already in Abstractions. But two of them needMicrosoft.Extensions.Logging.Abstractions(SkipAndLog) andSystem.Threading.Channels(the channel dead-letter overloads). Rather than push those deps onto the deliberately-lean core (every Abstractions consumer would inherit them), they live only in this package. The core assembly's dependency set is unchanged; the six format leaves reference nothing new (OnErrorisFunc<ItemErrorContext, ItemErrorAction>?— pure Abstractions types); end users opt intoErrorPoliciesfor the ready-mades or write their own lambda.Surface (
ItemErrorPolicy)Skip/Abort—Func<ItemErrorContext, ItemErrorAction>propertiesSkipAndLog(ILogger)SkipAndDeadLetter(ICollection<ItemErrorContext>)/SkipAndDeadLetter(ChannelWriter<ItemErrorContext>)SkipDeadLetterAndLog(ICollection<…>, ILogger)/SkipDeadLetterAndLog(ChannelWriter<…>, ILogger)Dead-letter sinks are caller-owned (bounded by the caller); the channel overload is the thread-safe concurrent-producer path (
TryWrite). TheICollectionoverloads add without locking — documented as safe for a single serial stage.Dependencies (this package only)
Wolfgang.Etl.Abstractions 0.21.0(lockstep, all TFMs)Microsoft.Extensions.Logging.Abstractions 10.0.10(all TFMs)System.Threading.Channels 10.0.10(net462/net472/net48/net481/netstandard2.0 only — in-box net5.0+)Release mechanics
<Version>0.20.0 → 0.21.0 (lockstep); PackageValidation baseline stays at last-published 0.20.0. New package has no baseline on its first release (add next cycle).release.yamlchange needed: it already packs everysrc/project, validates the tag against any src<Version>, and pushes all*.nupkg.Verification (local, full matrix net462 → net10.0)
dotnet packemits correct per-TFM dependency groups (verifiedAbstractions 0.21.0lockstep dep on every TFM; Channels only on netFx/ns2.0)🤖 Generated with Claude Code