Windows Event Log - ptweezy/cronstable GitHub Wiki
The eventlog reporter writes job outcomes to the Windows Event Log for
Event Viewer, Windows Event Forwarding, SCOM, and SIEM tools. Configure it
in a report block alongside sentry, mail, shell, webhook, or push.
See reporting for the hooks and the block's shared shape, and running on Windows for the rest of the platform's behavior.
It is Windows only. On any other platform it does nothing, and cronstable
logs a warning on every configuration load, including --validate-config,
rather than dropping reports silently.
jobs:
- name: nightly-backup
command: backup.cmd
schedule: "0 3 * * *"
onFailure:
report:
eventlog:
enabled: true
onSuccess:
report:
eventlog:
enabled: true| Key | Type | Default | Meaning |
|---|---|---|---|
enabled |
bool | false |
Write records for this hook. |
source |
string | cronstable |
The event source name that records are written under. |
includeOutput |
bool | false |
Carry the run's captured stderr (or stdout when stderr is empty), cut to its first 8000 characters, as the last insertion string. |
source must be a plain name. An empty value, one containing \ or /,
and the three log names Application, System, and Security are refused
at configuration load, because a source names a key underneath a log rather
than a log or a path. The check applies only to blocks that are enabled,
because source carries its default into every report block of every hook.
These are a contract. A SIEM rule, an Event Viewer custom view, and a forwarding subscription all key on the number, so a shipped ID keeps its meaning.
| Event ID | Level | Category | Fires on |
|---|---|---|---|
| 1000 | Information | 1 | a run succeeded (onSuccess) |
| 1001 | Error | 1 | a run failed (onFailure) |
| 1002 | Error | 1 | a run failed for the last time (onPermanentFailure) |
| 1003 | Warning | 1 | an SLA threshold was breached (onLate) |
| 1010 | Information | 2 | a daemon or orchestration event (notify:) |
| 1011 | Error | 2 | a daemon or orchestration event reporting failure |
| 1020 | Error | 2 | the Windows service refused to start |
Jobs occupy a contiguous 1000 to 1003 band so one rule can express "anything
that happened to a job", and daemon events start a fresh decade at 1010. The
service host writes 1020 whenever it refuses a start before its log opens,
whether or not any report: block enables the Event Log. Its source is the
service's name, which is cronstable unless you installed the service under
another name. An
overdue job is a Warning rather than an Error on purpose. It has not failed,
and an operator who pages on Level=2 in the Application log should not be
paged by a threshold they set to be advisory.
The IDs are plain small integers with no severity or customer bits folded
in. Event Viewer and the modern EventLog API report an ID masked to its low
16 bits, so a number carrying 0x20000000 would be documented as one value
and displayed as another. Severity travels in the event Level, which is
where every consumer already reads it.
Every record carries the same eleven insertion strings in the same order.
Position is the contract. Without a registered message DLL, there is no
message table to name the fields, and a forwarder ships them as <Data>
elements in order. A field that does not apply to an outcome is an empty
string rather than absent, so the arity never changes.
| # | Field | Contents |
|---|---|---|
| 0 | summary |
the one line a human reads in the Event Viewer list |
| 1 | name |
the job name (a DAG task's is <dag>.<taskId>), or the service name for 1020 |
| 2 | outcome |
success, failure, permanent-failure, late, event, event-alert or start-refused
|
| 3 | host |
the reporting host |
| 4 | exitCode |
the run's exit code |
| 5 | failReason |
why cronstable judged the run failed |
| 6 | runId |
the durable run ID, when a state: store is configured |
| 7 | startedAt |
ISO 8601 start instant |
| 8 | schedule |
the job's crontab line |
| 9 | detail |
per-outcome extras (SLA numbers, the event name, resource usage) |
| 10 | output |
the run's captured stderr (or stdout when stderr is empty), cut to its first 8000 characters, when includeOutput is on |
Field 2 repeats the outcome as text so a consumer that only rule-matches event ID 1001 can still tell a failure from a permanent failure.
Fields are capped so that a record can never exceed what the API accepts.
Everything except output is capped at 1024 characters, and output at
8000. A field cut short ends with ...[truncated]. The cap is arithmetic
rather than a runtime trim because of what happens otherwise: when the
combined vector is too long, ReportEventW writes no record at all, so an
uncapped reporter would drop precisely the alerts for the jobs that
produced the most output.
Because cronstable does not register its event source, Event Viewer has no message table to render and shows each record with a preamble like:
The description for Event ID 1001 from source cronstable cannot be found.
Either the component that raises this event is not installed on your local
computer or the installation is corrupted. You can install or repair the
component on the local computer.
followed by the insertion strings themselves. That preamble costs the
rendered prose and nothing else. The provider name, the event ID, the level,
the timestamp, and every insertion string are all present and unchanged, so
the XML view, wevtutil, Get-WinEvent, a Windows Event Forwarding
subscription, and every SIEM connector read the record exactly as they would
a registered one.
Registering a source is an HKLM write, which needs Administrator. Without a
message DLL to point EventMessageFile at, registering provides no benefit.
Building one means an mc.exe and rc.exe step per architecture, shipping
a second binary beside the executable, and the code-signing question that
binary would raise. Therefore, cronstable writes as an unregistered source
and documents the preamble rather than asking every install for
Administrator to remove a cosmetic line.
If you have your own message DLL, you can register the source yourself. The reporter writes the same records either way:
reg add "HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\cronstable" /v EventMessageFile /t REG_EXPAND_SZ /d "C:\path\to\your-messages.dll" /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\cronstable" /v TypesSupported /t REG_DWORD /d 7 /fThis is optional and unsupported: the message file is yours, and its message IDs have to match the earlier table.
An unregistered source writes into the Application log. To read only cronstable's records:
Get-WinEvent -FilterHashtable @{ LogName = 'Application'; ProviderName = 'cronstable' } -MaxEvents 20wevtutil qe Application /q:"*[System[Provider[@Name='cronstable']]]" /f:text /c:20 /rd:trueFor only the failures, filter on the event IDs:
Get-WinEvent -FilterHashtable @{ LogName = 'Application'; ProviderName = 'cronstable'; ID = 1001, 1002 }The same XPath works as an Event Viewer custom view and as the <Select>
clause of a Windows Event Forwarding subscription.
The Application log is readable by every local account. That is why
includeOutput is off by default, and it is the opposite default from
push's includeLogTail. A push payload is sealed to a paired device's key,
but anything written here is visible to anyone who can open Event Viewer on
the host. Turning includeOutput on is a decision to publish that job's
output locally.
Queued records are not durable. Writes are handed to a background thread so a busy Event Log service can never delay a job's completion handling. A graceful shutdown drains that queue with a five-second bound. An abrupt process exit drops whatever had not yet reached the service.
It complements a log file; it does not replace one. The logging:
section captures the daemon's own log (see
logging configuration). This reporter publishes
per-run outcomes. An unattended deployment usually benefits from both. The
standard library's logging.handlers.NTEventLogHandler is not an
alternative, because it needs pywin32, which the frozen executable does not
carry.
On POSIX it does nothing, and cronstable logs a warning on every
configuration load, including --validate-config, naming every job, DAG
task, or notify block that enabled it:
report.eventlog is enabled (job nightly-backup), but Windows Event Log is unavailable on this platform. Event Log reports will be skipped; other configured reporters still run normally
A warning rather than a refusal, so one configuration directory can serve a
mixed fleet. There is nothing an operator could install on Linux to satisfy
a refusal, which is what separates this from the user/group rejection on
Windows.
-
Reporting for the hooks, the shared
reportblock, and the other five reporters - Running on Windows for the rest of the platform's behavior
-
Late Run Detection for the
sla:thresholds behind event 1003 - Logging Configuration for the daemon's own log
- Configuration Reference for the full schema