Enhance logging in FdsSqlOptions and related classes

- Updated FdsSqlOptions to accept an optional ILogger parameter for improved error logging.
- Modified FdsMfr and FdsMfrClient classes to pass the logger instance to FdsSqlOptions.
- Added detailed error logging in various methods to capture SQL execution issues and file handling errors.
- Improved documentation for FdsSqlOptions to clarify logging behavior.
- Updated Archive class to log compression errors, enhancing traceability of failures.
- Adjusted project configuration to suppress specific warnings related to transitive dependencies.
- Added NuGet.config to define package sources for dependency management.
- Updated submodule references for OCORE and related projects.
This commit is contained in:
Stefan
2026-07-03 20:22:05 +02:00
parent 1a3bf30442
commit 882e97509a
57 changed files with 2121 additions and 106 deletions
+66
View File
@@ -0,0 +1,66 @@
# Concepts
This folder holds **living design write-ups** of how a subsystem currently
works: its moving parts, data flow, and how they fit together. Unlike
[`../Decisions`](../Decisions/README.md), concept docs are **not** immutable
— keep them in sync with the implementation as it evolves.
## What belongs here
"How does the notification pipeline work end to end" is a concept doc. "Why
did we choose SignalR over polling for it" is a decision. A single feature
area typically has one concept doc and may reference several decisions that
shaped it.
## File naming
`kebab-case-topic.md` (no numbering — concepts aren't sequential events).
## Required YAML frontmatter
```yaml
---
status: Active # Active | Deprecated
lastUpdated: 2026-07-03
applyTo: # glob(s) — files/areas this concept describes
- "Fuchs/Notifications/**"
relatedDecisions: # filenames in ../Decisions this concept implements
- "0001-domain-events-and-notification-triggers.md"
---
```
**Agents must scan the YAML frontmatter of every file in this folder first**
and only read the full body of concepts whose `applyTo` glob matches the
files they're about to touch, or whose subject is otherwise clearly relevant.
## Body template
```markdown
# Topic
## Summary
One paragraph: what this subsystem does and why it exists.
## How it works
The mechanics — components, data flow, sequencing. Diagrams (ASCII/mermaid)
welcome where they clarify.
## Key files
Bullet list of the primary files/classes involved.
## Related decisions
Links to the ADRs in `../Decisions` that shaped this design.
```
## Rules
- **Keep concepts current.** When you materially change how a documented
subsystem works, update its concept doc in the same change — don't let it
drift from the code.
- **Create a concept doc for new non-trivial subsystems.** If you build
something a future agent would need a paragraph of context to safely
modify, write that paragraph here instead of making them re-derive it from
the diff.
- Concepts describe **current** behavior. If something changes, edit the
doc in place — don't append a changelog inside it (git history is the
changelog).
@@ -0,0 +1,67 @@
---
status: Accepted
date: 2026-07-03
applyTo:
- "Fuchs/Notifications/**"
- "Fuchs/Services/**"
- "Fuchs/Controllers/**"
supersededBy: ""
---
# 0001 — Domain events (success and failure) trigger user-understandable notifications
## Context
Business operations (invoice creation, sending, marking sent, reminders,
banking import) happen server-side, often outside a synchronous request the
user is watching (background jobs, long-running sends). Users had no
reliable way to learn that an operation they cared about — or one that
failed — actually happened, short of refreshing lists or checking logs.
## Decision
Every meaningful business outcome, success **and** failure, is modeled as a
`DomainEvent` (`Fuchs/Notifications/DomainEvent.cs`) with:
- a `DomainEventType` enum value identifying what happened,
- the acting `UserAccountId`,
- a `Title`, and
- a `Context` dictionary of the data needed to render a human-readable
message (invoice number, email address, file name, row counts, etc.).
Services call the corresponding method on `IEventService`
(`Fuchs/Notifications/IEventService.cs`, implemented by `EventService`)
at the point the outcome is known — e.g.
`InvoiceSentToCustomerAsync(invoice, email, userAccountId)` or
`InvoiceIssueAsync(message, userAccountId, invoiceId)` on failure.
`EventService.PublishAsync` renders the event into a `GuiNotification` with a
German, end-user-readable `Message` (e.g. *"Rechnung R2026-0001 wurde an den
Kunden mit der E-Mail test@test.de versandt."*) and pushes it — see
[0002](0002-gui-notification-delivery-signalr.md) for delivery.
Every new business operation with a user-visible outcome (created, sent,
failed, imported, etc.) must add a `DomainEventType` value and a matching
`IEventService` method, and call it from the service at the point of success
**and** the point of failure.
## Consequences
- `IEventService` is injected into services that perform user-facing
operations (`InvoiceService`, `ReminderService`, `BankingService` callers)
— never bypass it by writing directly to `NotificationHub`.
- Failure paths must call the `*IssueAsync`/`*Failed` event too, not just
succeed-path events — silent failures are the problem this exists to
prevent.
- Messages are built server-side in `EventService.BuildNotification`, in
German, using only `Context` values — keep `Context` populated with
everything the message needs (don't rely on the client to look anything
up).
- Adding a new event type means updating the enum, the `IEventService`
interface + `EventService` implementation (trigger method + message
branch + `IsFailure` if it's a failure type), and the calling service —
in the same change.
## Alternatives considered
- **Polling a status endpoint from the client**: rejected — adds latency,
extra load, and doesn't generalize to background/multi-tab flows as
cleanly as a push model.
- **Raw exception messages surfaced to the GUI**: rejected — not
user-understandable and leaks internal details; `Context` + a rendered
German message keeps the boundary between internal errors and
user-facing text explicit.
@@ -0,0 +1,60 @@
---
status: Accepted
date: 2026-07-03
applyTo:
- "Fuchs/Notifications/**"
- "Fuchs/js/intranet/**"
- "Fuchs/wwwroot/web/**"
- "Fuchs/Program.cs"
supersededBy: ""
---
# 0002 — Backend notifications reach the GUI via a SignalR push to every logged-in session
## Context
Domain events (see [0001](0001-domain-events-and-notification-triggers.md))
need to reach whichever browser session(s) a user has open, in near
real time, without the client polling.
## Decision
- `NotificationHub` (`Fuchs/Notifications/NotificationHub.cs`) is an
`[Authorize]` SignalR `Hub` mapped at `/notifications` in `Program.cs`
(`app.MapHub<NotificationHub>("/notifications")`).
- `EventService.PublishAsync` sends every `GuiNotification` to
`_hub.Clients.All.SendAsync("notification", notification, ...)`. Delivery
is currently broadcast to all connected (authenticated) clients, not
targeted per-user — any logged-in session receives every notification.
- Publish failures are caught and logged (`_logger.LogWarning`) rather than
thrown — a notification-delivery failure must never fail the underlying
business operation that triggered it.
- On the client, `$fis.notifications` (`Fuchs/js/intranet/fis_main.js`)
opens the SignalR connection once a logged-in `useraccount_id` is known,
listens for the `"notification"` event, and calls `push(notification)`
to render a dismissible toast into `#notification_frame`. The toast is
styled by `notification.severity` (`"error"` vs `"info"`), giving failures
a distinct highlighted appearance from successes.
- `GuiNotification.Severity` is derived by `EventService.IsFailure` from the
`DomainEventType` — failure event types render as `"error"`, everything
else as `"info"`.
## Consequences
- Any new `DomainEventType` that represents a failure must be added to
`EventService.IsFailure` or it will render as a plain info toast instead
of being visually flagged.
- Because delivery is broadcast (not user-scoped), notifications are not a
substitute for private/sensitive data — `Context`/`Message` content must
stay appropriate for any logged-in user to see. If per-user targeting
becomes necessary, that is a new decision (SignalR groups keyed by user
ID), not a silent change to this one.
- The hub requires authentication; unauthenticated sessions never connect
and never receive notifications.
- Frontend rendering logic lives in `fis_main.js`/`fis.js` — keep the built
`wwwroot/web/fis.js`/`fis.min.js` in sync via the gulp build (see
`CLAUDE.md` Build & Test) whenever the notification client code changes.
## Alternatives considered
- **Per-user SignalR groups**: more correct long-term but adds group
join/leave lifecycle management; deferred until a concrete need for
private notifications arises.
- **Server-Sent Events / long polling**: rejected — SignalR was already the
chosen real-time transport and needs no extra infrastructure.
@@ -0,0 +1,56 @@
---
status: Accepted
date: 2026-07-03
applyTo:
- "Fuchs/Logging/**"
- "Fuchs/Program.cs"
supersededBy: ""
---
# 0003 — The solution is equipped with structured diagnostic logging
## Context
Diagnosing issues in a deployed intranet instance requires a durable,
inspectable log of what the application did, independent of whether an
OpenTelemetry collector is attached (see
[0004](0004-opentelemetry-observability.md)) — logging must work
out-of-the-box on every environment with zero external dependencies.
## Decision
- `Fuchs/Logging/FuchsLoggerProvider.cs` implements a custom
`ILoggerProvider`/`ILogger` registered via `builder.Logging.AddFuchsLogging()`
in `Program.cs`, with `SetMinimumLevel(LogLevel.Debug)`.
- Every log line always goes to `Debug.WriteLine` **and** to a rolling text
file under `<content root>/logs/``AppLog.txt` for
`Debug`/`Information`/`Warning`, `ErrorLog.txt` for `Error`/`Critical`
so a failure investigation never depends on a debugger being attached.
- Log lines are structured with timestamp, level tag, category, message, and
(when present) the exception message + stack trace on continuation lines.
- Database logging (`fuchs__admin_logdebug`) is **prepared but disabled** by
default (`FuchsLoggerProvider.DatabaseLoggingEnabled = false`) — flip it
on only where DB-durable diagnostics are specifically needed, since it
adds a DB round-trip per log call.
- All logger calls elsewhere in the codebase use `ILogger<T>` injected via
DI with **structured** placeholders (`_logger.LogInformation("Sent {InvoiceNumber} to {Email}", ...)`),
never interpolated strings — this is enforced project-wide (see Coding
Standards / Observability in `CLAUDE.md`).
- File writes are best-effort: `AppendToFile` swallows its own exceptions —
a logging failure must never crash or interrupt the operation being
logged.
## Consequences
- New code must inject `ILogger<T>` and log entry/result/timing/errors for
meaningful operations (see [0004](0004-opentelemetry-observability.md) for
the matching tracing/metrics requirement) rather than adding ad-hoc
`Console.WriteLine`/`Debug.Print` calls.
- Because logs always write to `logs/AppLog.txt` and `ErrorLog.txt`
regardless of telemetry configuration, these files are the first place to
check when OTLP export isn't configured for an environment.
- Enabling `DatabaseLoggingEnabled` is a deliberate, explicit choice per
environment, not a default — it has a per-call DB cost.
## Alternatives considered
- **Third-party logging framework (Serilog/NLog)**: rejected for now to
avoid an extra dependency for a need the in-box `ILogger` abstraction plus
a small custom provider already satisfies; revisit if requirements (e.g.
structured JSON sinks, log shipping) outgrow this.
@@ -0,0 +1,65 @@
---
status: Accepted
date: 2026-07-03
applyTo:
- "Fuchs/Observability/**"
- "Fuchs/Program.cs"
- "Fuchs/Services/**"
supersededBy: ""
---
# 0004 — OpenTelemetry is wired in extensively, without compromising performance
## Context
Beyond text logs (see [0003](0003-structured-diagnostic-logging.md)), the
solution needs distributed tracing and metrics to understand performance and
behavior in production (PDF render durations, email send outcomes, MFR call
volume, banking import throughput) without depending on a debugger or manual
log-grepping — while never letting the absence of a collector break or slow
down the app.
## Decision
- All instrumentation is centralized in `Fuchs/Observability/FuchsTelemetry.cs`:
one `ActivitySource` (`Fuchs.Intranet`) for tracing and one `Meter` for
metrics, exposing named `Counter<long>`/`Histogram<double>` instruments
(invoices/reminders/reports rendered, emails/SMS sent/failed, MT940 rows
parsed, banking entries skipped/truncated, MFR calls, blob upload
success/failure, PDF/report/email durations) plus a `StartActivity` helper.
- Wired in `Program.cs` behind `Fuchs:Telemetry:Enabled` (default `true`):
`AddOpenTelemetry()` with `AddAspNetCoreInstrumentation`,
`AddHttpClientInstrumentation`, `AddSqlClientInstrumentation` for tracing,
and `AddAspNetCoreInstrumentation`, `AddHttpClientInstrumentation`,
`AddRuntimeInstrumentation` for metrics.
- **Collection is always on; export is opt-in.** The OTLP exporter is only
added when `Fuchs:Telemetry:OtlpEndpoint` is configured — with no
collector present, spans/metrics are simply collected in-process and
discarded, so a missing collector can never cause startup failures,
exceptions, or blocking calls. Setting `Fuchs:Telemetry:Enabled=false`
disables instrumentation entirely.
- Per the project-wide Observability standard: every meaningful operation
starts an activity via `FuchsTelemetry.StartActivity(...)`, records the
matching counter/histogram, and logs entry/result/timing/errors via
injected `ILogger<T>` with structured placeholders — this is enforced for
new service/handler code, not just the initial wiring.
## Consequences
- New business operations worth observing must add a named instrument to
`FuchsTelemetry.cs` rather than creating ad-hoc `ActivitySource`/`Meter`
instances elsewhere — one source, one meter, keeps exporters and
dashboards simple.
- Because export is opt-in, local/dev environments get full in-process
instrumentation with zero setup; wiring an OTLP collector is purely an
ops-side configuration change (`Fuchs:Telemetry:OtlpEndpoint`), not a
code change.
- Instrumentation must stay cheap on the hot path — use the existing
counters/histograms rather than allocating new tags/dictionaries per call
where avoidable, and never make a business operation depend on the
exporter succeeding.
## Alternatives considered
- **Always-on OTLP exporter requiring a collector**: rejected — would make
local dev and any environment without a collector fail hard or add
latency/timeouts trying to reach one.
- **Per-service ActivitySource/Meter instances**: rejected in favor of one
centralized `FuchsTelemetry` — avoids scattered instrument names and
duplicate registration boilerplate in `Program.cs`.
+71
View File
@@ -0,0 +1,71 @@
# Decisions
This folder holds **Architecture Decision Records (ADRs)** — short, immutable
records of a specific technical choice, why it was made, and what it implies
going forward.
## What belongs here
A decision, not a how-to. If it answers "why do we do X this way, and what
else did we consider," it's a decision. If it explains "how subsystem X
currently works," that belongs in [`../Concepts`](../Concepts/README.md)
instead (and a decision often triggers a concept doc to be created/updated).
## File naming
`NNNN-kebab-case-title.md`, four-digit zero-padded, sequential across the
whole folder (`0001-...`, `0002-...`). Never reuse or renumber.
## Required YAML frontmatter
Every decision file starts with:
```yaml
---
status: Accepted # Proposed | Accepted | Superseded
date: 2026-07-03 # date the decision was accepted
applyTo: # glob(s) — files/areas this decision governs
- "Fuchs/Notifications/**"
supersededBy: "" # filename of the decision that replaced this one, if any
---
```
**Agents (Claude, Copilot, Codex) must scan the YAML frontmatter of every file
in this folder first** (cheap — no need to read the body) and only read the
full body of decisions whose `applyTo` glob matches the files they're about
to touch, or whose subject is otherwise clearly relevant to the task. This
keeps decision-following cheap even as the folder grows.
## Body template
```markdown
# NNNN — Title
## Context
What problem/situation forced a choice.
## Decision
What was decided, stated plainly.
## Consequences
What this implies for future code — constraints, follow-ups, trade-offs
accepted knowingly.
## Alternatives considered
Options that were rejected and why (optional but preferred).
```
## Rules
- **Decisions are immutable once `Accepted`.** Do not edit the Decision/
Consequences of an existing file to reverse it. Instead, write a new
decision, set its `applyTo`/subject accordingly, and set the old file's
`status: Superseded` + `supersededBy: NNNN-new-file.md`.
- **Follow existing decisions.** Before implementing anything in an area
covered by an `Accepted` decision, read it and conform to it. If you
believe a decision is wrong, raise it with the user rather than silently
deviating.
- **Capture new decisions as they happen.** Whenever the user (or the code
you're writing) settles a non-obvious architectural or cross-cutting
choice — not a routine implementation detail — add a decision here in the
same change, and create/update the matching concept doc in `../Concepts`.
+11
View File
@@ -0,0 +1,11 @@
The items, if completed, should be ticked / checked as done.
[x] Add note to `claude.md`, `copilot-instructions.md`, and `Codex.md` that the Decisions ind \Docs\Decisions must be followed
[x] Add note to `claude.md`, `copilot-instructions.md`, and `Codex.md` that whenever relevant new decisions should be captured, concept files should be created / updated
[x] Add note to `claude.md`, `copilot-instructions.md`, and `Codex.md` that the readme.md files in \Docs\Concept and \Docs\Decisions explain how to create, update, interpret the documents. Create those readme.md files. make sure that any concepts or decisions have a yaml header that contains applyTo key. The agents should scan those yaml headers first (saving tokens) and decide based on that if included/considered
[x] Add a first decision, that domain events (success and fails) should be identified and equiped with triggers that trigger notification to the EventService and pass on a context that allows a user understandable message like "Rechnung R2026-0001 wurde and Kunden unter test@test.de per Email versandt".
[x] Add a decision that reflects the current concept and implementation of Notification from Service in Backend over SignalR push to any logged in session to display in GUI. (failues with highlighting)
[x] Add a decision that the solution must be equiped with logging so that the diagnostics is possible without requiring a debugger or OTel collector.
[x] Add OpenTelemetry to the solution. Wire it in extensively without compromising performance.