RuleGate Diagnostics

August 1, 2026 · View on GitHub

RuleGate diagnostics provide structured information about ASP.NET Core attribute enrichment, completed authorization evaluations, and their requirement trees. Policy-source hosting also emits safe snapshot activation and reload-rejection events.

Diagnostics are intended for:

  • Trusted application logs
  • Operational troubleshooting
  • Authorization test assertions
  • Custom observability integrations
  • Performance investigation

Diagnostics do not change whether access is allowed or denied.

For ASP.NET Core registration and endpoint integration, read the ASP.NET Core integration guide.

For the wider runtime trust model, read the security model.

Diagnostics are disabled by default

Calling AddRuleGate() alone does not enable diagnostics:

builder.Services
    .AddRuleGate()
    .AddPolicies(policies);

When no diagnostics sink is registered, RuleGate uses the non-diagnostic evaluation path.

This avoids constructing evaluation identifiers, requirement traces, and diagnostic snapshots when diagnostics are not needed.

Enable built-in logging diagnostics

Enable the ASP.NET Core structured logging sink explicitly:

using Fotbiler.RuleGate.AspNetCore.DependencyInjection;

builder.Services
    .AddRuleGate()
    .AddLoggingDiagnostics()
    .AddPolicies(policies);

AddLoggingDiagnostics():

  • Adds Microsoft.Extensions.Logging services
  • Registers one singleton IAuthorizationDiagnosticsSink
  • Registers one singleton IRuleGateEnrichmentDiagnosticsSink
  • Is idempotent
  • Preserves custom sinks registered earlier

Diagnostics are produced only when the sink's IsEnabled property returns true.

For the built-in logging sink, IsEnabled is true when either Information or Debug logging is enabled for its category.

Logging category

The current built-in logger category is:

Fotbiler.RuleGate.AspNetCore.Diagnostics.LoggingAuthorizationDiagnosticsSink

ASP.NET Core enrichment uses:

Fotbiler.RuleGate.AspNetCore.Diagnostics.LoggingRuleGateEnrichmentDiagnosticsSink

Policy source activation and reload hosting uses:

Fotbiler.RuleGate.AspNetCore.PolicySources.PolicySourceReloadHostedService

Example appsettings.json configuration:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Fotbiler.RuleGate.AspNetCore.Diagnostics.LoggingAuthorizationDiagnosticsSink": "Information",
      "Fotbiler.RuleGate.AspNetCore.Diagnostics.LoggingRuleGateEnrichmentDiagnosticsSink": "Warning",
      "Fotbiler.RuleGate.AspNetCore.PolicySources.PolicySourceReloadHostedService": "Information"
    }
  }
}

Use Debug when requirement-level traces are needed:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Fotbiler.RuleGate.AspNetCore.Diagnostics.LoggingAuthorizationDiagnosticsSink": "Debug"
    }
  }
}

Requirement-level Debug logging can be significantly more verbose than the authorization-level Information event.

Enable it deliberately and retain the output only inside trusted operational boundaries.

Built-in logging events

The built-in sinks and policy-source host emit seven structured event types.

Event IDLevelPurpose
2000InformationCompleted authorization evaluation
2001DebugCompleted requirement evaluation
2010DebugSuccessful attribute-enrichment provider
2011WarningFail-closed enrichment provider outcome
2020InformationImmutable policy snapshot activated
2021WarningCandidate policy reload rejected
2022WarningPolicy-source file watch unavailable

Policy reload events include snapshot version, policy count, or stable diagnostic codes. They do not include policy values, manifest contents, exception messages, subject IDs, resource IDs, roles, permissions, or attribute values. The source guide documents the structured PolicyReloadResult returned to trusted application code.

Enrichment events

Events 2010 and 2011 describe one ASP.NET Core enrichment provider call.

They contain:

FieldDescription
ProviderNameProvider implementation type
AttributeSourceSubject, Resource, or Context
OrderProvider execution order
CollisionBehaviorFail, KeepExisting, or ReplaceExisting
OutcomeSafe structured provider outcome
AttributeCountNumber of attributes returned by the provider
DurationMsProvider execution and merge duration

The possible failure outcomes are:

  • MissingRequiredData
  • ProviderFailed
  • ProviderException
  • AttributeCollision
  • InvalidAttribute
  • Cancelled

Enrichment logs never include attribute names, attribute values, exception messages, or provider result payloads.

Custom integrations can implement:

using Fotbiler.RuleGate.AspNetCore.Enrichment;

public interface IRuleGateEnrichmentDiagnosticsSink
{
    ValueTask WriteAsync(
        RuleGateEnrichmentDiagnostic diagnostic,
        CancellationToken cancellationToken = default);
}

RuleGateEnrichmentDiagnostic intentionally exposes only provider type, source, order, collision behavior, outcome, attribute count, and duration. Diagnostic-sink exceptions are ignored so observability cannot change an authorization result.

For provider contracts and pipeline behavior, read the ASP.NET Core enrichment guide.

Authorization event

Event 2000 represents the completed authorization decision.

It contains:

FieldDescription
EvaluationIdUnique authorization evaluation identifier
PolicyIdMatched policy identifier, or null when no policy matched
IsAllowedFinal allow or deny result
DurationMsTotal measured evaluation duration
FailureCodesComma-separated authorization failure codes
RequirementCountNumber of recorded requirement evaluations

Conceptual log entry:

RuleGate authorization evaluation <evaluation-id> completed.
PolicyId: document-read;
IsAllowed: False;
DurationMs: 1.42;
FailureCodes: RULEGATE_MISSING_PERMISSION;
RequirementCount: 1.

The exact formatting is controlled by Microsoft.Extensions.Logging and the configured logging provider.

Requirement event

Event 2001 represents one evaluated requirement.

It contains:

FieldDescription
AuthorizationEvaluationIdParent authorization evaluation
RequirementEvaluationIdUnique requirement evaluation identifier
ParentEvaluationIdParent requirement identifier in the trace tree
RequirementIdOptional policy-defined requirement identifier
RequirementKindBuilt-in or custom requirement category
OutcomeSatisfied, NotSatisfied, or Indeterminate
DurationMsRequirement evaluation duration
FailureCodesComma-separated requirement failure codes
AttributeSourceSubject, Resource, or Context when applicable
ComparedAttributeSourceSecond operand source when applicable

The built-in logging sink intentionally does not log the attribute name.

Diagnostic contract

Custom integrations use:

using Fotbiler.RuleGate.Abstractions.Diagnostics;

public interface IAuthorizationDiagnosticsSink
{
    bool IsEnabled { get; }

    ValueTask WriteAsync(
        AuthorizationEvaluationDiagnostic diagnostic,
        CancellationToken cancellationToken = default);
}

The engine checks IsEnabled before selecting the diagnostic evaluation path.

When it is false:

  • No diagnostic trace session is created.
  • WriteAsync is not called.
  • Authorization behavior remains unchanged.

AuthorizationEvaluationDiagnostic

One AuthorizationEvaluationDiagnostic represents one completed authorization evaluation.

It exposes:

PropertyTypeDescription
EvaluationIdGuidNon-empty evaluation identifier
PolicyIdstring?Matched policy identifier
IsAllowedboolFinal decision
DurationTimeSpanTotal measured duration
FailureCodesIReadOnlyList<string>Decision failure codes
RequirementEvaluationsIReadOnlyList<RequirementEvaluationDiagnostic>Requirement trace

The model:

  • Rejects an empty evaluation ID
  • Rejects a negative duration
  • Rejects blank failure codes
  • Copies supplied collections
  • Exposes read-only collection views

When no policy matches:

PolicyId: null
IsAllowed: false
FailureCodes:
  - RULEGATE_NO_MATCHING_POLICY
RequirementEvaluations: empty

RequirementEvaluationDiagnostic

One RequirementEvaluationDiagnostic represents one requirement evaluation.

It exposes:

PropertyTypeDescription
EvaluationIdGuidUnique requirement evaluation ID
ParentEvaluationIdGuid?Parent requirement evaluation ID
RequirementIdstring?Optional policy-defined ID
RequirementKindAuthorizationRequirementKindRequirement category
OutcomeRequirementEvaluationOutcomeEvaluation outcome
DurationTimeSpanMeasured duration
FailureCodesIReadOnlyList<string>Requirement failure codes
AttributeSourceAuthorizationAttributeSource?Attribute model
AttributeNamestring?Attribute name for custom sinks
ComparedAttributeSourceAuthorizationAttributeSource?Second operand attribute model
ComparedAttributeNamestring?Second operand attribute name for custom sinks

The diagnostic model can contain attribute names for either operand.

It never contains:

  • Actual attribute value
  • Expected attribute value
  • Raw authorization attribute value

Applications implementing custom sinks must decide whether attribute names are safe for their environment.

The built-in logging sink does not emit AttributeName.

Requirement kinds

AuthorizationRequirementKind contains:

ValueMeaning
PermissionPermission requirement
RoleRole requirement
AttributeAttribute requirement
AttributeComparisonAttribute comparison requirement
TimeWindowRecurring local time-window requirement
DateTimeWindowBefore, after, or bounded date-time requirement
ContextAgeAuthentication- or MFA-age requirement
ContextCanonical context-property requirement
AllLogical all requirement
AnyLogical any requirement
NotLogical negation requirement
CustomApplication-specific requirement

Custom requirement definitions are reported as Custom unless RuleGate knows their built-in category.

Requirement outcomes

RequirementEvaluationOutcome contains:

OutcomeMeaning
SatisfiedThe requirement succeeded
NotSatisfiedThe requirement evaluated normally but did not pass
IndeterminateThe requirement could not produce a reliable comparison

Examples of NotSatisfied:

  • Missing permission
  • Missing role
  • Missing attribute
  • Literal comparison returned false
  • Time or date-time window was not satisfied
  • Context timestamp exceeded its maximum age
  • A negated child was satisfied

Examples of Indeterminate:

  • Requirement evaluator not found
  • Unsupported runtime attribute type
  • Attribute type mismatch
  • Context timestamp is in the future
  • Unsupported operator and type combination

Both NotSatisfied and Indeterminate lead to a denied authorization decision through the fail-closed engine.

Requirement tree relationships

Every requirement evaluation receives a unique EvaluationId.

Root requirements have:

ParentEvaluationId: null

Nested requirements point to the evaluation ID of their parent.

Example:

all-root
├── permission-node
└── attribute-node

Conceptual identifiers:

all-root
EvaluationId: A
ParentEvaluationId: null

permission-node
EvaluationId: B
ParentEvaluationId: A

attribute-node
EvaluationId: C
ParentEvaluationId: A

This allows custom observability tools to reconstruct nested all, any, and not evaluation trees.

The top-level authorization evaluation has a separate AuthorizationEvaluationDiagnostic.EvaluationId. Requirement events also carry that authorization evaluation ID when emitted by the built-in logger.

Failure codes

Diagnostics expose stable authorization failure codes rather than user-facing explanations.

Examples include:

RULEGATE_NO_MATCHING_POLICY
RULEGATE_MISSING_PERMISSION
RULEGATE_MISSING_ROLE
RULEGATE_REQUIREMENT_EVALUATOR_NOT_FOUND
RULEGATE_ATTRIBUTE_NOT_FOUND
RULEGATE_ATTRIBUTE_TYPE_NOT_SUPPORTED
RULEGATE_ATTRIBUTE_TYPE_MISMATCH
RULEGATE_ATTRIBUTE_OPERATOR_NOT_SUPPORTED
RULEGATE_ATTRIBUTE_COMPARISON_NOT_SATISFIED
RULEGATE_TIME_WINDOW_NOT_SATISFIED
RULEGATE_DATE_TIME_WINDOW_NOT_SATISFIED
RULEGATE_CONTEXT_AGE_NOT_SATISFIED
RULEGATE_CONTEXT_TIMESTAMP_IN_FUTURE

A requirement diagnostic can contain its local failure codes.

The authorization diagnostic contains the failure codes propagated into the final denied decision.

Failure codes are useful for:

  • Tests
  • Alert grouping
  • Operational dashboards
  • Troubleshooting
  • Decision analysis

They should not automatically be returned to untrusted API clients.

Data included by the built-in logging sink

The Information authorization event includes:

  • Evaluation ID
  • Policy ID
  • Allow or deny result
  • Duration
  • Failure codes
  • Requirement count

The Debug requirement event includes:

  • Authorization evaluation ID
  • Requirement evaluation ID
  • Parent requirement evaluation ID
  • Requirement ID
  • Requirement kind
  • Outcome
  • Duration
  • Failure codes
  • Attribute source
  • Compared attribute source

Data omitted by the built-in logging sink

The built-in sink does not log:

  • Attribute names
  • Attribute values
  • Subject identifiers
  • Resource identifiers
  • Raw claims
  • Role values
  • Permission values
  • Raw authorization requests
  • Resource attributes
  • Subject attributes
  • Context attributes

These omissions reduce accidental disclosure, but the remaining fields may still be sensitive.

Policy IDs, requirement IDs, failure codes, timing data, and decision outcomes can reveal application behavior.

Protect diagnostics as security-relevant operational data.

Custom diagnostics sink

Register a custom sink before calling AddRuleGate():

using Fotbiler.RuleGate.Abstractions.Diagnostics;

builder.Services.AddSingleton<
    IAuthorizationDiagnosticsSink,
    ApplicationAuthorizationDiagnosticsSink>();

builder.Services
    .AddRuleGate()
    .AddPolicies(policies);

Example implementation:

using Fotbiler.RuleGate.Abstractions.Diagnostics;
using Microsoft.Extensions.Logging;

public sealed class
    ApplicationAuthorizationDiagnosticsSink
    : IAuthorizationDiagnosticsSink
{
    private readonly ILogger<
        ApplicationAuthorizationDiagnosticsSink>
        _logger;

    public ApplicationAuthorizationDiagnosticsSink(
        ILogger<
            ApplicationAuthorizationDiagnosticsSink>
            logger)
    {
        _logger = logger;
    }

    public bool IsEnabled =>
        _logger.IsEnabled(
            LogLevel.Information);

    public ValueTask WriteAsync(
        AuthorizationEvaluationDiagnostic diagnostic,
        CancellationToken cancellationToken = default)
    {
        cancellationToken
            .ThrowIfCancellationRequested();

        _logger.LogInformation(
            "Authorization {EvaluationId} completed. " +
            "Allowed: {IsAllowed}; DurationMs: {DurationMs}.",
            diagnostic.EvaluationId,
            diagnostic.IsAllowed,
            diagnostic.Duration.TotalMilliseconds);

        return ValueTask.CompletedTask;
    }
}

This example intentionally avoids logging policy IDs, requirement IDs, failure codes, and attribute names.

Select fields according to the application's threat model and operational requirements.

Custom sink registration behavior

AddLoggingDiagnostics() uses TryAddSingleton.

Therefore:

builder.Services.AddSingleton<
    IAuthorizationDiagnosticsSink,
    ApplicationAuthorizationDiagnosticsSink>();

builder.Services
    .AddRuleGate()
    .AddLoggingDiagnostics();

preserves ApplicationAuthorizationDiagnosticsSink.

It does not register both sinks.

RuleGate currently resolves one IAuthorizationDiagnosticsSink.

When output must be sent to multiple destinations, register an application-owned composite sink that delegates to those destinations.

Custom sink requirements

A custom sink is registered as a singleton and should be:

  • Thread-safe
  • Non-blocking where practical
  • Bounded in memory use
  • Resilient to temporary destination failures
  • Careful with sensitive fields
  • Safe under concurrent authorization traffic

Avoid performing slow network calls directly on the authorization path.

Prefer:

  • Buffered channels
  • Bounded queues
  • Existing telemetry SDKs
  • Efficient structured logging providers

A custom sink should monitor its own failures because exceptions escaping WriteAsync are isolated by the authorization engine.

Sink failure isolation

Diagnostics are best-effort.

After an authorization decision is produced, RuleGate attempts to write one authorization diagnostic.

Exceptions thrown by the sink are caught and ignored by the engine.

A sink failure:

  • Does not change an allowed decision into a denial
  • Does not change a denied decision into an allowance
  • Does not make authorization unavailable
  • Is not rethrown to the application

This isolation applies to all exceptions escaping WriteAsync.

The custom sink should record or monitor its own delivery failures when such visibility is required.

Cancellation behavior

Request cancellation is honored during:

  • Policy lookup
  • Requirement dispatch
  • Requirement evaluation

When cancellation occurs during evaluation, it is propagated normally.

Diagnostic publication happens only after RuleGate has produced a decision.

The engine calls the sink with:

CancellationToken.None

This means request cancellation does not cancel the final best-effort diagnostic write after the decision has been created.

The IAuthorizationDiagnosticsSink.WriteAsync contract still accepts a cancellation token because sinks may be called directly or used by other integrations.

The built-in logging sink checks the token supplied to it.

Incomplete evaluations

Diagnostics describe completed authorization decisions that reach diagnostic publication.

An unexpected exception during:

  • Policy lookup
  • Requirement evaluation
  • Custom evaluator execution
  • Core engine execution

may prevent a completed authorization diagnostic from being created.

Diagnostics must not be treated as a guaranteed audit record of every attempted request.

Security audit trails should use a separate, application-owned mechanism with appropriate durability guarantees.

Diagnostics and audit logs

Diagnostics and audit logs serve different purposes.

Diagnostics answer questions such as:

  • Which policy was selected?
  • Which requirement failed?
  • How long did evaluation take?
  • Was the outcome indeterminate?
  • What did the requirement tree look like?

Audit logs answer questions such as:

  • Who attempted the protected action?
  • Which business resource was affected?
  • When did the business operation occur?
  • Was the operation completed?
  • What compliance record must be retained?

RuleGate diagnostics intentionally omit much of the subject and resource data normally needed by an audit record.

Do not use diagnostics as the only compliance audit trail.

Production logging guidance

A practical production baseline is:

  • Information events enabled
  • Debug requirement events disabled
  • Restricted access to authorization logs
  • Bounded retention
  • Structured field indexing
  • Alerting on unusual denial or indeterminate rates
  • No raw claims or attribute values
  • No public exposure of diagnostic payloads

Enable Debug traces temporarily when requirement-tree investigation is needed.

Return the category to Information after troubleshooting.

Useful operational signals

Event 2000 can support metrics such as:

  • Authorization evaluation count
  • Allow rate
  • Deny rate
  • No-matching-policy rate
  • Evaluation duration
  • Failure-code frequency

Event 2001 can support investigations such as:

  • Slow requirement categories
  • Frequently failing requirement IDs
  • Indeterminate requirement rate
  • Deep or unexpectedly complex policy trees

RuleGate does not currently publish built-in metrics.

Metrics must be derived by the logging or custom diagnostics integration.

Testing diagnostics

Test custom diagnostics behavior without asserting entire formatted log messages.

Prefer asserting:

  • Event ID
  • Log level
  • Structured field values
  • Evaluation ID relationships
  • Requirement outcomes
  • Failure codes
  • Omission of sensitive fields
  • Sink failure isolation
  • Disabled-sink behavior

Also test:

  • Allowed decisions
  • Denied decisions
  • Missing policies
  • Nested logical requirements
  • Attribute requirements
  • Indeterminate outcomes
  • Concurrent evaluations

OpenTelemetry signals

RuleGate also emits exporter-neutral activities and metrics through the public RuleGateTelemetry.ActivitySourceName and RuleGateTelemetry.MeterName constants. These signals cover authorization decisions, bounded failure categories, latency, policy lookup, source loading, and atomic reload.

OpenTelemetry is independent from IAuthorizationDiagnosticsSink. A host may enable structured logs, a custom diagnostic sink, telemetry, or any combination. Built-in telemetry never records policy, requirement, source, subject, resource, role, permission, claim, or attribute values.

See the telemetry, performance, and concurrency guide for registration, instrument names, bounded dimensions, benchmarks, stress tests, and thread-safety contracts.

Current boundaries

The current diagnostics surface includes:

  • Opt-in diagnostics
  • Authorization-level diagnostics
  • Requirement-level diagnostics
  • Parent-child requirement identifiers
  • Evaluation durations
  • Failure codes
  • Built-in ASP.NET Core structured logging
  • Policy snapshot activation and reload-rejection logging
  • Structured deterministic reload results and source diagnostics
  • Custom singleton diagnostics sink
  • Sink failure isolation
  • Exporter-neutral OpenTelemetry authorization and reload activities
  • Built-in low-cardinality counters and duration histograms
  • Cancellation and unexpected-error telemetry

RuleGate 1.0 does not include:

  • Persistent diagnostic storage
  • Built-in multi-sink fan-out
  • Decision visualization
  • Durable compliance audit logging
  • Guaranteed diagnostics for failed or canceled evaluations

See the roadmap for post-stable product planning.

Next steps

Continue with: