Setting Up Audit
This page is for the developer who adds Audit to an application. Audit records every evaluation of a live Rule Repository rule that your application performs, so that people can review any decision later, see statistics, and turn real cases into tests. This page explains what Audit needs, how it fits into your application, and what each setting decides. The code itself is in the developer reference, linked from each step.
For the full technical reference, with code, see the Implementation Guide's Audit section.
The parts involved:
- The Audit library (
CodeEffects.Rule.Audit). It loads your live rules, listens to the Rule Engine while it evaluates them, and records what happened. It works with any .NET application that can use .NET Standard 2.0 libraries, and it does not need the repository's server library.
- The repository's database. Audit records into the same database the Rule Repository uses, through the same database connection library. The rules themselves only ever get there through the repository; Audit writes evaluation records and nothing else. Audit, the repository and the database may each run in a different place.
- The recorder (
AuditRecorder), one per application, created at startup.
- A local journal: a folder of files where records wait, safely, until a background worker moves them to the database.
Step 1: Check the Database
Audit uses the repository's database, so it is created and updated with the same scripts. Call AuditRecorder.CheckDatabase(..) once at startup: like the repository's own check, it tells you whether the database has the version Audit needs and, if not, which scripts to run. See Setting Up the Rule Repository.
Step 2: Create the Recorder
Create one AuditRecorder when your application starts, with your database provider and, optionally, your settings. Keep it for the life of the application and dispose of it when the application stops, so the records still waiting are moved to the database. The recorder starts its background worker by itself; the worker never keeps your application running.
See AuditRecorder and AuditSettings.
Step 3: Load Your Rules Through Audit
An application that records with Audit loads the rules it evaluates through the recorder's GetRules(..), never in any other way. The query names the rules you want: by their IDs or names, every rule of a source object, or only the rules of one type. The answer is one set of rules, ready for the Rule Engine, that holds:
- the live version of each rule you asked for, and of every rule they use;
- marks Audit needs to tie each evaluation to the exact rule version and source version that ran;
- a list of what it holds: each rule's name, its version and its source version, so your application knows what it is running.
Only live versions are loaded. To run a different version, make it live in the repository. If your application keeps the loaded rules in memory, its evaluations keep recording the versions it loaded, even after another version goes live; reload the rules to pick up the change.
See AuditRuleset and Rule Storage in Rule Repository.
Step 4: Connect Audit to the Rule Engine
Pass your evaluation parameters to the recorder's Attach(..) once, then create the Rule Engine's evaluator with those parameters as usual. From then on, every evaluation that evaluator performs is recorded: one record for each rule it evaluates. A rule used inside another rule is part of the record of the rule that used it.
When the evaluator filters a list of items, each item it checks is recorded as an evaluation of its own. Errors are recorded and then passed back to your code unchanged.
Step 5: Say Who and What Each Evaluation Is For
Wrap each evaluation, or each piece of work, in the recorder's Scope(..), giving:
- a correlation ID: your own reference for the business case, such as an order or an application number. It is what people type to find the evaluation, and what AI agents use to find "the decision for order 123";
- the user: the ID, name and type (a person, an AI agent or a service) of whoever the evaluation is for. A user the repository has never seen is added with that name and type; one it already knows is left as it is;
- optional tags: values of your own to keep with the record for statistics.
Every record made inside the scope carries them, including in code that continues asynchronously. Records made outside any scope carry none.
Step 6: Choose What to Record
| Setting |
What it decides |
Default |
RecordValues |
Whether a record holds only the values the rule read, or every field the source declares. Recording every field gives auditors the whole case and makes tests saved from evaluations more useful, but it reads every field of your object on each evaluation, which can be slow for objects that load their data on demand. |
Only the values read |
ExcludedMembers |
Fields never recorded, in either mode, such as personal identifiers you must not keep. |
None |
MaxCollectionItems |
When recording every field, the most items kept from each list; the list's real size is still recorded. |
100 |
AnalyticsFields |
Fields kept separately, so AI agents can count and average them, such as a region or a quoted price. |
None |
Enabled |
Whether anything is recorded at all. |
Yes |
Step 7: Choose How Records Reach the Database
By default, recording costs your evaluation almost nothing: each record is written to the local journal and the evaluation returns at once, and the background worker moves records to the database in batches. Records survive a crash of your application; the next recorder that starts moves what was left behind.
| Setting |
What it decides |
Default |
Mode |
Asynchronous: records are moved in the background. Synchronous: each record is written to the database before the evaluation returns, for applications whose decisions must never be acted on unless recorded. |
Asynchronous |
ThrowOnStoreFailure |
In synchronous mode, whether an evaluation whose record could not be written fails instead of returning its result. |
No |
Resilience |
In asynchronous mode, where records wait: in the journal on disk (survives crashes), or in memory only, moved when the application stops normally (Drain) or only when your code asks (Volatile). |
Journal |
JournalFolder |
Where the journal is kept. On servers whose disk is thrown away when they restart, such as containers, use a lasting volume. |
A folder under the temporary folder |
JournalCapMegabytes, QueueCapacity, WhenFull |
How much may wait when the database is out of reach for a long time, and what happens beyond it: drop the record (the default), or make the evaluation wait for room. |
1 GB, 100,000 records, drop |
OnProblem |
A function Audit calls to tell your application anything that went wrong, such as a full journal or a database that refused a batch. Audit never interrupts an evaluation with its own problems. |
None |
The recorder's Flush(..) waits until every record is in the database, for short-lived programs. Applications that should not talk to the database at all can leave the moving to a separate program with AuditJournal. AuditRecorder.Statistics reports how many records were captured, stored, dropped and still waiting.
See AuditSettings, AuditJournal and AuditStatistics.
What People See
Once your application records evaluations, the Live Evaluations grid, the Evaluation dialog, Save as Test... and the production numbers on the Stats tabs fill up, for every user with permission to see evaluations. A rule version that has ever been live can no longer be deleted on its own, so recorded evaluations always point to the rules that made them. See Audit.