Resolves the remaining Minor items from the 2026-05-15 review so the web-UI dashboard work has no open follow-ups: a real-HubConnection end-to-end test for the SignalR feed, stable mbproxy.admin.broadcast.* log-event names, keyboard/aria accessibility on the fleet table, frontend JS hardening (URL-decode guard, NaN guards, shared util.js), reconciler<->capture-registry coverage, throwing-sink and embedded-asset tests, broadcaster polish, and a soft upper bound on AdminPushIntervalMs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
32 KiB
Log Events
The stable catalog of every mbproxy.* event name the service emits, with its level, structured properties, and operational meaning. Operators grep the rolling log against these names, dashboards filter on them, and alerting rules trigger on them — once shipped, the names do not churn.
Conventions and Wiring
The service uses Serilog wired through the Microsoft.Extensions.Logging bridge. Three sinks are configured (see src/Mbproxy/HostingExtensions.cs):
- Console — stdout; captured by the Windows SCM or by systemd-journald.
- Rolling file —
%ProgramData%\mbproxy\logs\on Windows,/var/log/mbproxy/on Linux (mbproxy-<date>.log). - Platform diagnostic sink —
Error+ events only.DiagnosticSinkSelectorpicks it once at the composition root: the Windows Application Event Log under the SCM (EventLogBridge), local syslog under systemd (SyslogBridge), or none for interactive/dev runs.
Every event uses source-generated [LoggerMessage] definitions, so the property names below match the message template token-for-token. The default minimum level is Information; lower the floor for Mbproxy.* categories via the standard Logging:LogLevel configuration to surface Debug events such as the coalesce and cache traces.
{
"Logging": {
"LogLevel": {
"Mbproxy": "Debug"
}
}
}
Every event carries a Plc property (the configured PLC name from appsettings.json) wherever a PLC scope applies, so log lines for one device can be filtered out of the fleet stream:
grep '"Plc":"Line1-Mixer"' mbproxy-20260514.log
Each H3 below is a stable event identifier; the dotted lowercase casing is part of the operator contract and is preserved verbatim. The EventId numeric column is documented alongside the event name so Windows Event Viewer filters and Serilog EventId-based subscriptions remain stable too.
Service Lifecycle
mbproxy.startup.ready
Level: Information · EventId: 1 · Source: src/Mbproxy/Proxy/ProxyWorker.cs
| Property | Type | Meaning |
|---|---|---|
ListenersBound |
int |
Number of per-PLC TcpListener instances successfully bound at startup. |
PlcsConfigured |
int |
Total PLC entries in the current configuration snapshot. |
Fires once after ProxyWorker.StartAsync has spun up every per-PLC supervisor and the admin endpoint. ListenersBound < PlcsConfigured means at least one PLC failed its initial bind — the supervisor will keep retrying, but the gap is the operator's first signal.
Operator action: if the two counts disagree, search for mbproxy.startup.bind.failed entries to identify the missing PLCs.
mbproxy.startup.bind
Level: Information · EventId: 20 (PlcListener) / 40 (PlcListenerSupervisor) · Source: src/Mbproxy/Proxy/PlcListener.cs, src/Mbproxy/Proxy/Supervision/PlcListenerSupervisor.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Port |
int |
Local TCP port the listener is bound to. |
Fires when a per-PLC TcpListener successfully binds its configured port. Emitted by both the listener itself and the supervisor wrapper — the two sites share the event name so dashboards filtering on mbproxy.startup.bind see both startup binds and post-recovery rebinds.
Operator action: none in steady state. Use this event to confirm a hot-reload added PLC actually came up.
mbproxy.startup.bind.failed
Level: Error · EventId: 21 (ProxyWorker) / 41 (PlcListenerSupervisor) · Source: src/Mbproxy/Proxy/ProxyWorker.cs, src/Mbproxy/Proxy/Supervision/PlcListenerSupervisor.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Port |
int |
Port that failed to bind. |
Reason |
string |
Bind exception message (usually Address already in use or a permissions error). |
Fires when a listener fails to bind at process startup or after a configuration reload. The supervisor's recovery pipeline will keep retrying with the policy from Resilience:ListenerRecovery, so a single occurrence is not necessarily fatal.
Operator action: check for port collisions (netstat -ano | findstr :<port>). If the conflict is another mbproxy instance from a botched uninstall, stop the stray process; if it is a third-party service, change the PLC's Port in appsettings.json and let hot-reload pick up the change.
mbproxy.listener.recovered
Level: Information · EventId: 42 · Source: src/Mbproxy/Proxy/Supervision/PlcListenerSupervisor.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Port |
int |
Port now bound. |
AttemptCount |
int |
Number of retry attempts the recovery pipeline executed before success. |
Fires after the supervisor's Polly recovery pipeline successfully rebinds a listener that previously emitted mbproxy.listener.faulted or mbproxy.listener.ended. AttemptCount is cumulative for the current outage — useful for spotting a port that is repeatedly flapping.
Operator action: none directly. Correlate with the immediately preceding fault to characterise the outage cause.
mbproxy.listener.faulted
Level: Error (PlcListener) / Warning (PlcListenerSupervisor) · EventId: 22 / 43 · Source: src/Mbproxy/Proxy/PlcListener.cs, src/Mbproxy/Proxy/Supervision/PlcListenerSupervisor.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Port |
int |
Port whose listener faulted. |
Reason |
string |
Top-level exception message. |
Fires when a listener's accept loop throws. The two sources emit at different levels deliberately: the unsupervised PlcListener instance logs at Error (a terminal condition for that listener), while the supervised emission is Warning because Polly will retry. The supervised path attaches the exception object as the LoggerMessage exception parameter, so the stack trace is captured.
Operator action: if the same Plc produces repeated faults inside a few minutes, inspect the network path. A burst of faults paired with mbproxy.multiplex.backend.disconnected indicates the PLC itself is unhealthy rather than a proxy issue.
mbproxy.listener.ended
Level: Warning · EventId: 44 · Source: src/Mbproxy/Proxy/Supervision/PlcListenerSupervisor.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Port |
int |
Port whose accept loop terminated. |
Fires when an accept loop returns without throwing — usually because the underlying TcpListener was stopped without the supervisor requesting it. Treated as a fault: the recovery pipeline rebinds and a subsequent mbproxy.listener.recovered should follow.
Operator action: none unless paired with no recovery within the configured retry window.
mbproxy.admin.started
Level: Information · EventId: 70 · Source: src/Mbproxy/Admin/AdminEndpointHost.cs
| Property | Type | Meaning |
|---|---|---|
Port |
int |
TCP port the read-only admin endpoint is listening on. |
Fires when the Kestrel-hosted admin endpoint has bound and is ready to serve GET / and GET /status.json.
Operator action: none. Use this to confirm the configured AdminPort is actually serving — curl http://localhost:<port>/status.json should return immediately afterwards.
mbproxy.admin.bind.failed
Level: Error · EventId: 71 · Source: src/Mbproxy/Admin/AdminEndpointHost.cs
| Property | Type | Meaning |
|---|---|---|
Port |
int |
Port that failed to bind. |
Reason |
string |
Bind exception message. |
Fires when the admin endpoint cannot bind its configured AdminPort. The service continues to proxy Modbus traffic — only the status page and status.json are unavailable.
Operator action: change Mbproxy:AdminPort in appsettings.json to a free port. Hot-reload picks up the change; the admin endpoint rebinds without a service restart.
mbproxy.admin.broadcast.snapshot.failed
Level: Error · EventId: 72 · Source: src/Mbproxy/Admin/StatusBroadcaster.cs
No structured properties; the exception is attached.
Fires when the live-dashboard push loop cannot build a status snapshot. The current push cycle is skipped; the loop retries on the next interval. The proxy data path is unaffected.
Operator action: none if isolated. A sustained rate means the status-snapshot builder is consistently throwing — capture the attached exception and investigate.
mbproxy.admin.broadcast.fleet.failed
Level: Error · EventId: 73 · Source: src/Mbproxy/Admin/StatusBroadcaster.cs
No structured properties; the exception is attached.
Fires when the push loop fails to deliver the fleet snapshot to dashboard subscribers (a SignalR transport fault). The loop continues; per-PLC detail pushes are still attempted.
Operator action: none if isolated. Sustained occurrences mean the SignalR feed is unhealthy — the dashboard's "live" feed is stale even though the proxy is fine.
mbproxy.admin.broadcast.detail.failed
Level: Error · EventId: 74 · Source: src/Mbproxy/Admin/StatusBroadcaster.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name whose detail push failed. |
Fires when the push loop fails to deliver a per-PLC detail snapshot to that PLC's detail-page subscribers. The loop continues with the remaining PLCs.
Operator action: none if isolated. Sustained occurrences for one Plc mean that PLC's detail page is not receiving live updates.
mbproxy.admin.broadcast.loop.terminated
Level: Error · EventId: 75 · Source: src/Mbproxy/Admin/StatusBroadcaster.cs
No structured properties; the exception is attached.
Fires when the live-dashboard push loop itself terminates on an unhandled exception (not the expected cancellation at shutdown). The dashboard's live feed stops entirely until the admin endpoint is rebound (an AdminPort hot-reload restarts the loop).
Operator action: alert. The live feed is dead; capture the attached exception and restart the admin endpoint (toggle Mbproxy:AdminPort) or the service.
mbproxy.shutdown.complete
Level: Information · EventId: 80 · Source: src/Mbproxy/Diagnostics/ShutdownCoordinator.cs
| Property | Type | Meaning |
|---|---|---|
InFlightAtCancel |
int |
Aggregate in-flight request count across all multiplexers at the moment SIGTERM was received. |
ElapsedMs |
long |
Wall-clock time spent draining, capped by Connection:GracefulShutdownTimeoutMs. |
Fires once after the graceful drain completes (whether all in-flight requests finished or the timeout fired first). ElapsedMs close to the configured drain timeout indicates the drain budget was exhausted — some upstream clients saw forced disconnects.
Operator action: if InFlightAtCancel is consistently large, consider raising Connection:GracefulShutdownTimeoutMs so restarts don't strand in-flight reads/writes.
Client Sessions
mbproxy.client.connected
Level: Information · EventId: 110 · Source: src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
RemoteEp |
string |
IPAddress:port of the upstream client. |
Fires once per upstream client accept on a PLC's listener. The event name is preserved from the legacy 1:1 model so existing operator queries keep working after the multiplexer rewrite.
Operator action: none in steady state. A burst of connects from the same RemoteEp indicates a reconnect storm — pair with mbproxy.client.disconnected to confirm.
mbproxy.client.disconnected
Level: Information · EventId: 111 · Source: src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
RemoteEp |
string |
IPAddress:port of the upstream client. |
Reason |
string |
Disconnect reason: clean, an exception type, or cascade when the multiplexer closed the upstream pipe due to a backend failure. |
Fires when an upstream pipe is closed for any reason.
Operator action: none unless paired with mbproxy.multiplex.backend.disconnected — that combination indicates the disconnect cascade is fleet-wide for one PLC.
Backend Multiplexer
mbproxy.multiplex.backend.connected
Level: Information · EventId: 112 · Source: src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Host |
string |
Backend host or IP. |
Port |
int |
Backend TCP port (typically 502). |
Fires when the multiplexer's single backend socket to a PLC is established. Because the multiplexer holds exactly one backend socket per PLC and reuses it across every upstream client, this event is rare in steady state — see it at startup and after each mbproxy.multiplex.backend.disconnected.
Operator action: none.
mbproxy.multiplex.backend.disconnected
Level: Warning · EventId: 113 · Source: src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UpstreamCount |
int |
Number of upstream pipes the multiplexer cascade-closed. |
InFlightCount |
int |
Number of in-flight requests dropped (each upstream sees a Gateway Target Device Failed To Respond exception or a closed socket). |
Reason |
string |
Underlying disconnect reason (exception type or "backend EOF"). |
Fires when the backend socket closes for any reason. Closing the backend is fatal to all attached upstream pipes; the multiplexer cascade-closes them so clients reconnect cleanly through the listener.
Operator action: investigate the PLC and the network path. A single transient occurrence is normal — repeated occurrences for the same Plc indicate a sick controller, a flaky ECOM100, or an unstable middlebox. Pair with mbproxy.backend.failed events for the same PLC to confirm the proxy can't get back in.
mbproxy.multiplex.saturated
Level: Error · EventId: 114 · Source: src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
RemoteEp |
string |
Upstream client whose request was refused. |
Fires when the TxId allocator refuses to allocate — every slot in the 16-bit MBAP transaction-ID space is currently in flight. The multiplexer responds to the upstream with Modbus exception code 04 (Slave Device Failure) and frees nothing. The DL205/DL260 family serialises Modbus TCP at roughly 2–10 ms per request, so reaching 65 536 concurrent in-flight requests is a stress-only path; in production this event is alert-worthy because it indicates either a runaway client or a backend that has stopped responding.
Operator action: alert. Check the corresponding PLC's in-flight gauge on the status page; if it is also pegged, the backend is wedged and the listener should be restarted via the hot-reload path (toggle the PLC's Enabled flag).
mbproxy.multiplex.request.timeout
Level: Warning · EventId: 116 · Source: src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
ProxyTxId |
ushort |
Internal TxId the multiplexer assigned to the backend request. |
OriginalTxId |
ushort |
TxId the upstream client sent. |
Fc |
byte |
Modbus function code of the timed-out request. |
ElapsedMs |
long |
Wall-clock time the request spent in flight before the watchdog fired. |
Fires when the per-request watchdog times out an in-flight request whose response never arrived within BackendRequestTimeoutMs. The upstream client receives Modbus exception code 0x0B (Gateway Target Device Failed To Respond) and the proxy TxId is freed. Common causes: PLC dropped the response, packet loss, or a backend that echoes the wrong MBAP TxId (e.g. pymodbus 3.13.0's concurrent-multiplexed-request bug).
Operator action: isolated timeouts are noise. A sustained rate indicates the PLC is overloaded or the backend is misbehaving — correlate with mbproxy.multiplex.backend.disconnected.
mbproxy.backend.failed
Level: Warning · EventId: 115 · Source: src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Reason |
string |
Final connect-pipeline exception (after retries). |
Fires when the backend-connect Polly pipeline (3 attempts at 100 ms / 500 ms / 2000 ms by default) exhausts its retries. The multiplexer cascade-closes any waiting upstream pipes; new clients will trigger a fresh reconnect attempt.
Operator action: check the PLC's IP/port reachability. If multiple PLCs share an upstream switch or an EBC100 daughterboard, look for a common network event.
Read Coalescing
mbproxy.coalesce.hit
Level: Debug · EventId: 120 · Source: src/Mbproxy/Proxy/Multiplexing/CoalescingLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UnitId |
byte |
Modbus unit identifier from the request MBAP. |
Fc |
byte |
Function code (0x03 or 0x04). |
Start |
ushort |
First register in the request range. |
Qty |
ushort |
Quantity of registers. |
PartyCount |
int |
Number of upstream parties now attached to the in-flight peer (after this one joined). |
Fires when an FC03/FC04 request attaches to an in-flight request with the same (UnitId, Fc, Start, Qty) key, so only one wire request hits the PLC.
Operator action: none. Coalescing is steady-state behaviour; the counters on the status page surface the same data without log volume.
mbproxy.coalesce.miss
Level: Debug · EventId: 121 · Source: src/Mbproxy/Proxy/Multiplexing/CoalescingLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UnitId |
byte |
Modbus unit identifier. |
Fc |
byte |
Function code (0x03 or 0x04). |
Start |
ushort |
First register in the request range. |
Qty |
ushort |
Quantity of registers. |
Fires when an FC03/FC04 request opens a fresh in-flight entry — either no matching peer existed, or the matching peer had reached its MaxParties cap and a new entry was opened.
Operator action: none.
mbproxy.coalesce.dead_upstream
Level: Debug · EventId: 122 · Source: src/Mbproxy/Proxy/Multiplexing/CoalescingLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UnitId |
byte |
Modbus unit identifier. |
Fc |
byte |
Function code (0x03 or 0x04). |
Start |
ushort |
First register in the request range. |
Qty |
ushort |
Quantity of registers. |
Fires when fan-out skips a coalesced party because its upstream pipe was closed (or had pending writes the pipe rejected). A high rate paired with a high mbproxy.client.disconnected rate indicates clients are giving up on slow PLCs.
Operator action: none directly. Investigate if paired with rising mbproxy.multiplex.request.timeout on the same PLC.
Response Cache
mbproxy.cache.hit
Level: Debug · EventId: 140 · Source: src/Mbproxy/Proxy/Cache/CacheLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UnitId |
byte |
Modbus unit identifier. |
Fc |
byte |
Function code (0x03 or 0x04). |
Start |
ushort |
First register in the request range. |
Qty |
ushort |
Quantity of registers. |
Fires when an FC03/FC04 request is served entirely from the in-process response cache — no backend round-trip occurs and the result is rewritten through the BCD pipeline as usual.
Operator action: none. The status-page cache hit-rate gauge tracks the same signal at far lower cost than this event.
mbproxy.cache.miss
Level: Debug · EventId: 141 · Source: src/Mbproxy/Proxy/Cache/CacheLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UnitId |
byte |
Modbus unit identifier. |
Fc |
byte |
Function code. |
Start |
ushort |
First register in the request range. |
Qty |
ushort |
Quantity of registers. |
Fires when an FC03/FC04 request matches a cacheable tag range but no live cache entry exists; the request falls through to the backend.
Operator action: none.
mbproxy.cache.store
Level: Debug · EventId: 142 · Source: src/Mbproxy/Proxy/Cache/CacheLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UnitId |
byte |
Modbus unit identifier. |
Fc |
byte |
Function code. |
Start |
ushort |
First register in the request range. |
Qty |
ushort |
Quantity of registers. |
TtlMs |
int |
Cache TTL applied to the entry (in milliseconds). |
Fires when a successful FC03/FC04 response is admitted into the cache. TtlMs echoes the per-tag CacheTtlMs from appsettings.json.
Operator action: none.
mbproxy.cache.invalidated
Level: Debug · EventId: 143 · Source: src/Mbproxy/Proxy/Cache/CacheLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
UnitId |
byte |
Modbus unit identifier. |
WriteStart |
ushort |
First register the write touched. |
WriteQty |
ushort |
Number of registers the write touched. |
Count |
int |
Number of cache entries evicted by the write. |
Fires when an FC06/FC16 write invalidates one or more overlapping cache entries. The invalidator deliberately operates at the register level so a write to V2000 evicts every cached read range that includes register V2000.
Operator action: none. A Count of zero means the write was outside any cached range and is informational only.
mbproxy.cache.flushed
Level: Information · EventId: 144 · Source: src/Mbproxy/Proxy/Cache/CacheLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Reason |
string |
Why the cache was flushed: "backend reconnect", "hot-reload", or "shutdown". |
Count |
int |
Number of entries dropped. |
Fires whenever the entire per-PLC cache is wiped at once — primarily after a backend reconnect (the proxy can't reason about staleness across the gap) or after a hot-reload that changed the BCD tag map for the PLC.
Operator action: none unless flushes happen on a tight loop, which would indicate the backend connection itself is unstable.
Keepalive
See ../Architecture/Keepalive.md for the backend heartbeat design.
mbproxy.keepalive.heartbeat.sent
Level: Debug · EventId: 150 · Source: src/Mbproxy/Proxy/Multiplexing/KeepaliveLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
ProxyTxId |
ushort |
Proxy-allocated TxId carrying the synthetic FC03 probe. |
Address |
ushort |
Modbus address the probe reads (BackendHeartbeatProbeAddress). |
Fires each time the heartbeat loop issues a probe on an idle backend socket — at most one per BackendHeartbeatIdleMs per idle PLC.
Operator action: none. Debug-level; useful only when confirming the heartbeat is alive.
mbproxy.keepalive.heartbeat.timeout
Level: Warning · EventId: 151 · Source: src/Mbproxy/Proxy/Multiplexing/KeepaliveLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
ProxyTxId |
ushort |
Proxy TxId of the unanswered probe. |
ElapsedMs |
long |
Milliseconds from probe send to timeout. |
Fires when a heartbeat probe is not answered within BackendRequestTimeoutMs — the backend is connected but no longer answering Modbus.
Operator action: check the PLC and the network path. Paired with mbproxy.keepalive.backend.idle_disconnect for the same PLC.
mbproxy.keepalive.backend.idle_disconnect
Level: Information · EventId: 152 · Source: src/Mbproxy/Proxy/Multiplexing/KeepaliveLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
ElapsedMs |
long |
Milliseconds the failed heartbeat waited before the teardown. |
Fires when a failed heartbeat triggers a proactive backend teardown. Every attached upstream pipe is cascaded; clients reconnect on their next request. This is the keepalive feature doing its job — finding a dead path during idle instead of on the next real request.
Operator action: none if isolated. Repeated idle-disconnects on one PLC indicate it keeps going dark while idle — investigate the device or the network path.
BCD Rewriter
mbproxy.rewrite.partial_bcd
Level: Warning · EventId: 30 · Source: src/Mbproxy/Proxy/RewriterLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Address |
ushort |
PDU-decimal address of the BCD tag involved. |
ClientStart |
ushort |
Start register the client requested. |
ClientQty |
ushort |
Quantity of registers the client requested. |
Fires when a 32-bit BCD pair is only partially covered by a read or write range (the request straddles the CDAB boundary instead of covering both words). The raw bytes are passed through unchanged, so the client or PLC sees the original nibbles.
Operator action: the upstream client is misconfigured — its register map disagrees with the proxy's BcdTags list. Reconcile the client's tag definitions against appsettings.json.
mbproxy.rewrite.invalid_bcd
Level: Warning · EventId: 31 · Source: src/Mbproxy/Proxy/RewriterLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Address |
ushort |
PDU-decimal address of the BCD tag involved. |
RawValue |
ushort |
The raw register value that failed BCD validation (logged as 0x{RawValue:X4}). |
Direction |
string |
"Read" (response from PLC) or "Write" (request from client). |
Fires when a register at a configured BCD address contains a nibble >= 0xA — i.e. not a valid BCD digit. The raw bytes are passed through unchanged.
Operator action: if Direction=Read, the PLC has been written outside BCD discipline (probably by a ladder program that bypassed the proxy). If Direction=Write, the client is sending a value > 9999 that doesn't fit in 4 BCD digits — bound-check upstream.
mbproxy.exception.passthrough
Level: Information · EventId: 32 · Source: src/Mbproxy/Proxy/RewriterLogEvents.cs
| Property | Type | Meaning |
|---|---|---|
Plc |
string |
Configured PLC name. |
Fc |
byte |
Original function code (high bit set on the wire; logged as 0x{Fc:X2}). |
ExceptionCode |
byte |
Modbus exception code (1=illegal function, 2=illegal data address, 3=illegal data value, 4=slave device failure, etc.). |
Fires when the PLC returns a Modbus exception response (high bit set on the FC byte). The frame is forwarded verbatim to the client.
Operator action: none in isolation. A sustained ExceptionCode=2 rate against a configured BCD address suggests the PLC's V-memory map no longer matches the proxy's tag list.
Configuration Hot-Reload
mbproxy.config.reload.applied
Level: Information · EventId: 60 · Source: src/Mbproxy/Configuration/ConfigReconciler.cs
| Property | Type | Meaning |
|---|---|---|
PlcsAdded |
int |
Number of new PLC entries the supervisor brought online. |
PlcsRemoved |
int |
Number of PLC entries torn down. |
PlcsRestarted |
int |
Number of PLCs whose listener was rebound (port or host change). |
PlcsReseated |
int |
Number of PLCs whose BCD tag map was swapped without restarting the listener. |
GlobalTagDelta |
int |
Net change in the global tag count across the new snapshot. |
Fires after a debounced appsettings.json change passes validation and the reconciler has applied it. The five counters together describe the shape of the change so dashboards can plot churn over time.
Operator action: none. Use this event as the audit trail for who-changed-what after a configuration push.
mbproxy.config.reload.rejected
Level: Error · EventId: 61 · Source: src/Mbproxy/Configuration/ConfigReconciler.cs
| Property | Type | Meaning |
|---|---|---|
Errors |
string |
Concatenated validation failures (one line per failure). |
Fires when a configuration change fails validation — duplicate PLC names, port collisions inside the new file, malformed BCD tag entries, or a schema-level error. The previous valid configuration remains in effect; no listeners are touched.
Operator action: fix the offending entry in appsettings.json and save again. The reconciler debounces file events on a 250 ms window, so rapid sequential saves coalesce into one validation pass.
Conventions
Naming
All event names follow mbproxy.<area>.<noun>[.<state>]:
<area>matches a subsystem (startup,client,multiplex,coalesce,cache,rewrite,config,admin,listener,shutdown,backend,exception).<noun>describes the thing the event is about.<state>is optional and only used when the event represents a terminal or special outcome (bind.failed,reload.rejected,backend.connected).
Property names are PascalCase, match the [LoggerMessage] template tokens exactly, and use Plc as the canonical scope key for per-device filtering.
Stability Promise
Event names and EventId values are part of the operator contract. They are not changed in patch or minor releases. A rename or removal requires a major version bump and a migration note in the release. New events are additive and can ship in any release.
Where Events Are Defined
Each subsystem owns a single *LogEvents.cs static partial class with [LoggerMessage] declarations:
src/Mbproxy/Proxy/Multiplexing/MultiplexerLogEvents.cs— client sessions and backend multiplexer.src/Mbproxy/Proxy/Multiplexing/CoalescingLogEvents.cs— read coalescing.src/Mbproxy/Proxy/Cache/CacheLogEvents.cs— response cache.src/Mbproxy/Proxy/RewriterLogEvents.cs— BCD rewriting and exception passthrough.
Lifecycle events (startup.*, listener.*, admin.*, shutdown.*, config.reload.*) live as private [LoggerMessage] declarations next to the class that emits them — see ProxyWorker.cs, PlcListener.cs, PlcListenerSupervisor.cs, AdminEndpointHost.cs, StatusBroadcaster.cs (the admin.broadcast.* family), ShutdownCoordinator.cs, and ConfigReconciler.cs. New subsystems should follow the *LogEvents.cs pattern when they accumulate more than two events.
Related Documentation
- Troubleshooting — operator playbook keyed off the event names in this catalog.
- Connection Model — context for the
mbproxy.multiplex.*andmbproxy.client.*events. - Response Cache — context for the
mbproxy.cache.*events. - Status Page — counter equivalents for the high-volume Debug-level events.
- Read Coalescing — context for the
mbproxy.coalesce.*events. - Keepalive — context for the
mbproxy.keepalive.*events. - BCD Rewriting — context for the
mbproxy.rewrite.*andmbproxy.exception.passthroughevents. - Hot Reload — context for the
mbproxy.config.reload.*events.