AuditRecorder Class
Assembly: CodeEffects.Rule.Audit.dll
Namespace: CodeEffects.Rule.Audit
Summary
Records production evaluations of the rules in the Rule Repository database, and loads those rules for the Rules Engine.
Syntax
public sealed class AuditRecorder : System.IDisposable
Inheritance
System.Object
- CodeEffects.Rule.Audit.AuditRecorder
Audit is a separate library with its own license: the package CodeEffects.Rule.Audit, which you add to the application that evaluates the rules, and which runs on SQL Server first, with PostgreSQL, MySQL and Oracle to follow. It works on the Rule Repository database: the rules come from it, and the recorded evaluations are written to it, where the Rule Repository shows them next to the rule versions that ran. The Audit library writes only evaluations and their counters. The rules themselves get into the database through the Rule Repository, which can run in a different environment from the application that evaluates them, as long as both use the same database. Check the database with CheckDatabase(..).
An audited host loads its rules only through GetRules(..). It returns the live versions of the rules, and every rule they call, stamped with the rule version and the source version that each evaluation records. A rule that did not come from GetRules, such as one loaded with Context.GetRules of the Repository library or read from a file, is evaluated but not recorded. A version runs only once it is made live, so make live the versions that you want to run.
Make one recorder when the application starts, keep it for the life of the application, and dispose it when the application stops. Evaluations on any number of threads may use the same recorder. The steps are:
- Make the recorder with
AuditRecorder(..). Its AuditSettings say how records reach the database and what they hold.
- Load the rules with
GetRules(..).
- Plug the recorder into the engine with
Attach(..), once, on the EvaluationParameters of the evaluator.
- Wrap the evaluations in a
Scope(..) to name what they are for.
From then on every evaluation of a rule from GetRules is recorded: the values of the evaluated object that the engine read and nothing that it skipped, the value that each method call returned, the result or the error, the options of the evaluator, and the correlation ID and user of the scope. An exception that the rule throws is recorded and rethrown unchanged. A rule that is skipped by short-circuit evaluation is not recorded; a rule that another rule calls is part of the record of its caller. A rule run as a filter over a query is translated into the query, not evaluated by the engine, so it is not recorded.
Recording never throws into an evaluation, and neither does the background work. Problems are reported to AuditSettings.OnProblem, with the single exception of the AuditException that a synchronous recorder with ThrowOnStoreFailure throws when a record could not be written.
By default a record is appended to a local journal file before the evaluation returns, and a background thread moves it into the database. The alternatives are to hold records in memory, which costs less and is less safe, or to write them to the database before the evaluation returns. See AuditSettings.Mode and Resilience.
Constructors
AuditRecorder(IDbProvider, AuditSettings)
public AuditRecorder(IDbProvider dbProvider, AuditSettings settings = null)
Initializes a new instance of the AuditRecorder class that reads rules from, and writes evaluations to, the Rule Repository database. It starts the background thread that moves records into the database, and, with the default Journal resilience, moves at once whatever a process that is gone left in the journal folder.
Parameters
Exceptions
System.ArgumentNullException - The provider is null.
CodeEffects.Rule.Common.RuleException - A setting is one that the recorder cannot work with: a number out of range (control number CE20008), a journal that is required and has no folder (CE20001), or an analytics field with a name that is empty, longer than 128 characters or has spaces around it (CE20006).
Properties
Methods
Attach(..)
Returns: System.Void
public void Attach(EvaluationParameters parameters)
Plugs the recorder into the engine. An evaluator that is made with these parameters records every evaluation of a rule that came from GetRules(..). Call it once for the parameters, before you create the evaluator. The options of the evaluator are read from the parameters for every record, so options that you change between evaluations are recorded as each evaluation ran with them. It does not work for an evaluator over a Flex source, nor for a rule that runs as a filter over a query.
Parameters
Exceptions
System.ArgumentNullException - The parameters are null.
CheckDatabase(..)
Returns: CodeEffects.Rule.Connection.SchemaCheckResult - The version the database is at, the version this version of the Audit library needs, and whether that is enough.
public static SchemaCheckResult CheckDatabase(IDbProvider dbProvider)
Reads the schema version of the Rule Repository database and compares it with the version that this version of the Audit library needs. Call it once at start-up and act on the answer. The library never calls it by itself and keeps nothing. If you do not call it, you are told anyway: GetRules(..) throws a ConnectionException with control number CE19004 and the same message, and the move of the journal into the database reports it as DrainFailed, when a statement fails on a table or column that is not there and the database is behind. The update scripts come from the Code Effects portal, and you run them. The library never changes the schema.
Parameters
Exceptions
System.ArgumentNullException - The provider is null.
Example
using CodeEffects.Rule.Audit;
using CodeEffects.Rule.Connection;
SchemaCheckResult check = AuditRecorder.CheckDatabase(dbProvider);
if (!check.IsCurrent)
throw new InvalidOperationException(check.Message);
Dispose()
Returns: System.Void
public void Dispose()
Stops recording and waits, up to DrainTimeoutSeconds, for what was recorded to be in the database. What is journaled and not moved by then stays in the journal for the next recorder that uses the folder. Records held in memory that the database could not take in time are lost, which is reported as RecordsLost. After it, evaluations run as they do without Audit. Calling it more than once is harmless.
Flush(..)
Returns: System.Boolean - true when everything recorded so far is in the database.
public bool Flush()
public bool Flush(TimeSpan timeout)
Waits until everything recorded so far is in the database. Use it in a process that is about to end, such as a console job or a serverless function, so that the last records are not lost. It returns false when the wait ran out, and at once when another process moves the journal folder, because this recorder cannot tell when that process gets to its records. They are safe in the journal either way. In Synchronous mode there is nothing to wait for and it returns true.
Parameters
- timeout (
System.TimeSpan) - How long to wait. The first overload waits up to DrainTimeoutSeconds, 30 seconds by default.
Example
using CodeEffects.Rule.Audit;
if (!audit.Flush(TimeSpan.FromSeconds(10)))
logger.LogWarning("Records pending: " + audit.Statistics.Pending);
GetRules(..)
Returns: CodeEffects.Rule.Audit.AuditRuleset - The live versions of the rules the query names, and of every rule they call, as one ruleset.
public AuditRuleset GetRules(RuleQuery query)
Loads the live versions of the rules that a RuleQuery names, and of every rule they call, followed through every level, as one ruleset for the engine. It is the only way an audited host gets its rules. It reads the database every time it is called. No parameter names a version: a rule runs at its live version, so make live the version that you want to run. The ruleset is stamped with what the recorder needs to record the evaluations. With AuditValues.AllFields, it also reads the fields of each source version once and keeps them for the life of the process.
The Repository library has a method of the same name, Context.GetRules(..), for applications that evaluate without Audit. It returns a LoadedRuleset, which carries nothing for recording.
Parameters
Exceptions
System.ArgumentNullException - The query is null.
CodeEffects.Rule.Common.RuleException - No rule with a live version matches the query (control number CE20002); a rule asked for by ID or name does not exist or has no live version (CE20005); a rule calls a rule that has no live version (CE20003); or a live version is not a rule's XML (CE20004). The message says which.
CodeEffects.Rule.Connection.ConnectionException - The database is behind the schema that this version of the library needs (control number CE19004). The message names the update scripts to run.
Scope(..)
Returns: System.IDisposable - The scope. Dispose it to end it.
public IDisposable Scope(string correlationId = null, string userId = null, string userName = null, AuditUserType userType = AuditUserType.User, IDictionary<string, object> tags = null)
Names what the evaluations made inside it are for: your own ID of the transaction, which finds them again in the Rule Repository, the user they are made for, and analytics tags. Every parameter is optional. Outside a scope a record has none of these. The scope is ambient: it flows into asynchronous code that the block starts, and disposing it ends it. A scope inside another replaces it until it ends.
A user that the Rule Repository has not met yet is added with the name and type that you pass. A user it already has is used as it is, and its name and type are never changed by a scope.
Parameters
- correlationId (
System.String) - Your own ID of what the evaluations are for, such as an order number.
- userId (
System.String) - The ID of the user: the one you give the Rule Repository for the same user.
- userName (
System.String) - The name of the user, used only when the repository does not have the user yet.
- userType (
CodeEffects.Rule.Audit.AuditUserType) - The type of the user, used only when the repository does not have the user yet. The default is AuditUserType.User.
- tags (
System.Collections.Generic.IDictionary<System.String, System.Object>) - Analytics values of your own, by name, for what no rule reads, such as a channel, a region or a campaign. They are kept with each evaluation as AnalyticsFields are, and a tag replaces a field of the same name. A name is up to 128 characters, with no spaces around it. A null value is not kept. The dictionary is copied, so changing it afterwards has no effect.
Exceptions
System.ArgumentException - A tag has a name that is empty, longer than 128 characters or has spaces around it.
Example
using System.Collections.Generic;
using CodeEffects.Rule.Audit;
var tags = new Dictionary<string, object> { ["Channel"] = "web", ["Campaign"] = 42 };
using (audit.Scope("order-1001", "agent-7", "Order bot", AuditUserType.Agent, tags))
{
bool approved = evaluator.Evaluate(order, ruleset.Rule("Approve large orders").Id);
}
Example
using CodeEffects.Rule.Audit;
using CodeEffects.Rule.Common.Models;
using CodeEffects.Rule.Engine;
var audit = new AuditRecorder(dbProvider, new AuditSettings
{
OnProblem = problem => logger.LogWarning(problem.Message)
});
AuditRuleset ruleset = audit.GetRules(RuleQuery.BySource("Order"));
var parameters = new EvaluationParameters { Precision = 2 };
audit.Attach(parameters);
var evaluator = new Evaluator<Order>(ruleset.Xml, parameters);
using (audit.Scope(correlationId: order.Number, userId: currentUserId))
{
bool approved = evaluator.Evaluate(order, ruleset.Rule("Approve large orders").Id);
}
// When the application stops
audit.Dispose();