ExecutionMonitor 0.1.30
dotnet add package ExecutionMonitor --version 0.1.30
NuGet\Install-Package ExecutionMonitor -Version 0.1.30
<PackageReference Include="ExecutionMonitor" Version="0.1.30" />
<PackageVersion Include="ExecutionMonitor" Version="0.1.30" />
<PackageReference Include="ExecutionMonitor" />
paket add ExecutionMonitor --version 0.1.30
#r "nuget: ExecutionMonitor, 0.1.30"
#:package ExecutionMonitor@0.1.30
#addin nuget:?package=ExecutionMonitor&version=0.1.30
#tool nuget:?package=ExecutionMonitor&version=0.1.30
ExecutionMonitor
ExecutionMonitor adds attribute-based execution monitoring to ASP.NET Core and .NET apps.
It captures slow and failed executions, stores them in memory, files, or SQL Server, can generate HTML, JSON, and text reports on demand, and can optionally produce AI-assisted recommendations with a built-in rule-based fallback when no AI provider is configured.
It is shipped as a single NuGet package, so consumers install ExecutionMonitor once and do not add the internal ExecutionMonitor.* projects separately.
Install
dotnet add package ExecutionMonitor
If you are using ExecutionMonitor from NuGet, install only this package. The package already includes the internal runtime assemblies it needs.
If you are using a local package source, add the feed first:
dotnet nuget add source C:\path\to\ExecutionMonitor\Deploy\packages -n ExecutionMonitorLocal
dotnet add package ExecutionMonitor --source ExecutionMonitorLocal
Create A Fresh Sample App
Use this flow when you want to test the package outside this repository.
- Create a new ASP.NET Core Web API project.
- Add the package from the local feed or from your published NuGet source.
- Copy the configuration keys into
appsettings.json. - Register
ExecutionMonitorinProgram.cs. - Add one or more monitored controllers.
- Start the app and hit the demo endpoints.
- Open the report endpoints and confirm records are being captured.
- Switch the store to SQL Server when you want durable shared history.
Example commands:
dotnet new webapi -n Demo.ExecutionMonitor.Sample
cd Demo.ExecutionMonitor.Sample
dotnet nuget add source C:\Users\User\Documents\Inovation\packages\ExecutionMonitor\Deploy\packages -n ExecutionMonitorLocal
dotnet add package ExecutionMonitor --source ExecutionMonitorLocal
Example Program.cs:
using ExecutionMonitor;
using ExecutionMonitor.Abstractions;
using ExecutionMonitor.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddExecutionMonitor(options =>
{
options.ApplicationName = "Demo.ExecutionMonitor.Sample";
options.EnvironmentName = builder.Environment.EnvironmentName;
options.DefaultSlowAfterMs = 250;
options.RecordSuccessfulExecutions = true;
options.EnableAiAssistance = true;
options.AiStoreRecommendations = true;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.OnDemand;
})
.UseSqlServer(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("ExecutionMonitor");
options.SchemaName = "monitor";
options.FallbackSchemaName = "dbo";
options.AutoCreateObjects = true;
options.AutoMigrate = true;
});
var app = builder.Build();
app.UseExecutionMonitor();
app.MapControllers();
app.MapExecutionMonitorEndpoints("/execution-monitor");
app.Run();
When AutoCreateObjects = true, the package creates or reuses the SQL objects it needs at startup. In a fresh database, that means it will create monitor.ExecutionLogs and monitor.ExecutionRecommendations automatically if they do not already exist.
Example appsettings.json:
{
"ConnectionStrings": {
"ExecutionMonitor": "Server=localhost;Database=ExecutionMonitor;Trusted_Connection=True;TrustServerCertificate=True"
},
"ExecutionMonitor": {
"Ai": {
"Endpoint": "https://api.openai.com/v1/chat/completions",
"ApiKey": "replace-with-your-openai-key",
"Model": "gpt-4o-mini"
}
}
}
Quick Start
using ExecutionMonitor;
using ExecutionMonitor.Abstractions;
using ExecutionMonitor.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddExecutionMonitor(options =>
{
options.ApplicationName = "MyApp";
options.EnvironmentName = builder.Environment.EnvironmentName;
options.DefaultSlowAfterMs = 1000;
options.RecordSuccessfulExecutions = true;
options.EnableAiAssistance = true;
options.AiStoreRecommendations = true;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.OnDemand;
});
var app = builder.Build();
app.UseExecutionMonitor();
app.MapControllers();
app.MapExecutionMonitorEndpoints("/execution-monitor");
app.Run();
Use In Non-Web Apps
ExecutionMonitor is not only for APIs. You can use the shared monitoring core in:
- console apps
- worker services
- Windows services
- background jobs
- desktop apps that run managed code
For non-web apps, you typically use these pieces:
IExecutionMonitorServiceto wrap code you want to measureIExecutionMonitorStoreto persist recordsIExecutionReportServiceto build reports in codeIExecutionRecommendationProviderto generate AI or rule-based recommendations
Non-web app example
If you use AddExecutionMonitor(...) in a console app or worker service, add the ASP.NET Core shared framework reference so the hosting abstractions are available:
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
Important note for plain console apps:
If you use ExecutionMonitor in a plain console app, add the same framework reference above. That matches the package's hosted-service and web-aware dependencies and avoids the missing ASP.NET Core abstractions error.
using ExecutionMonitor;
using ExecutionMonitor.Abstractions;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddExecutionMonitor(options =>
{
options.ApplicationName = "ExecutionMonitor.Worker";
options.EnvironmentName = "Production";
options.DefaultSlowAfterMs = 500;
options.RecordSuccessfulExecutions = true;
options.RecordSlowExecutions = true;
options.RecordFailedExecutions = true;
options.EnableAiAssistance = true;
options.AiMode = ExecutionMonitorAiMode.RuleBasedOnly;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.OnDemand;
options.AiStoreRecommendations = true;
options.AiCacheRecommendations = true;
})
.UseInMemory();
using var provider = services.BuildServiceProvider();
var monitor = provider.GetRequiredService<IExecutionMonitorService>();
var reports = provider.GetRequiredService<IExecutionReportService>();
await monitor.ExecuteAsync(async ct =>
{
await Task.Delay(250, ct);
});
var report = await reports.GenerateAsync(new ExecutionReportOptions
{
Format = "Json",
IncludeRecommendations = true,
MaxRows = 50
});
Console.WriteLine(report);
Log A Named Method In A Console App
If you want the record to show the method name in a non-web app, wrap a named method and pass a template record into ExecuteAsync(...).
When that method is slow or fails, the store records it using the names you supplied.
using ExecutionMonitor;
using ExecutionMonitor.Abstractions;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddExecutionMonitor(options =>
{
options.ApplicationName = "ExecutionMonitor.Worker";
options.EnvironmentName = "Production";
options.DefaultSlowAfterMs = 500;
options.RecordSuccessfulExecutions = true;
options.RecordSlowExecutions = true;
options.RecordFailedExecutions = true;
})
.UseSqlServer(options =>
{
options.ConnectionString = "Server=localhost;Database=ExecutionMonitor;Trusted_Connection=True;TrustServerCertificate=True";
options.SchemaName = "monitor";
options.FallbackSchemaName = "dbo";
options.AutoCreateObjects = true;
options.AutoMigrate = true;
});
using var provider = services.BuildServiceProvider();
var monitor = provider.GetRequiredService<IExecutionMonitorService>();
await monitor.ExecuteAsync(DoWorkAsync, new ExecutionRecord
{
ClassName = nameof(Program),
MethodName = nameof(DoWorkAsync),
ApplicationName = "ExecutionMonitor.Worker",
EnvironmentName = "Production"
});
static async Task DoWorkAsync(CancellationToken cancellationToken)
{
await Task.Delay(750, cancellationToken);
}
In this example:
DoWorkAsyncis the method that actually runs- the
ExecutionRecordtemplate suppliesClassNameandMethodName - if the call is slow, it is captured because
RecordSlowExecutions = true - if the call fails, it is captured because
RecordFailedExecutions = true
Non-web app with SQL Server
If you want durable history and report generation across restarts, use SQL Server:
services.AddExecutionMonitor(options =>
{
options.ApplicationName = "ExecutionMonitor.Worker";
options.EnvironmentName = "Production";
options.DefaultSlowAfterMs = 500;
options.EnableAiAssistance = true;
options.AiMode = ExecutionMonitorAiMode.OpenAICompatible;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.OnDemand;
options.AiStoreRecommendations = true;
})
.UseSqlServer(options =>
{
options.ConnectionString = configuration.GetConnectionString("ExecutionMonitor");
options.SchemaName = "monitor";
options.FallbackSchemaName = "dbo";
options.AutoCreateObjects = true;
options.AutoMigrate = true;
});
Non-web config keys
Keep the values in appsettings.json, user secrets, or environment variables:
{
"ConnectionStrings": {
"ExecutionMonitor": "Server=localhost;Database=ExecutionMonitor;Trusted_Connection=True;TrustServerCertificate=True"
},
"ExecutionMonitor": {
"Ai": {
"Endpoint": "https://api.openai.com/v1/chat/completions",
"ApiKey": "replace-with-your-openai-key",
"Model": "gpt-4o-mini"
},
"License": {
"AppId": "ExecutionMonitor.Worker",
"LicensePath": "C:\\ProgramData\\ExecutionMonitor\\ExecutionMonitor.license.json",
"PublicKeyPath": "C:\\ProgramData\\ExecutionMonitor\\ExecutionMonitor.public.pem"
}
}
}
If ExecutionMonitor:Ai:ApiKey is missing, the package falls back to the built-in rule-based provider.
Configuration
You can keep everything in config. The sample app reads these values:
{
"ConnectionStrings": {
"ExecutionMonitor": "Server=localhost;Database=ExecutionMonitor;Trusted_Connection=True;TrustServerCertificate=True"
},
"ExecutionMonitor": {
"Ai": {
"Endpoint": "https://api.openai.com/v1/chat/completions",
"ApiKey": "replace-with-your-openai-key",
"Model": "gpt-4o-mini"
}
}
}
Connection string key:
ConnectionStrings:ExecutionMonitor
AI keys:
ExecutionMonitor:Ai:Endpoint
ExecutionMonitor:Ai:ApiKey
ExecutionMonitor:Ai:Model
Demo Code
Example monitored controller:
using ExecutionMonitor.Abstractions;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/demo")]
public sealed class DemoController : ControllerBase
{
[HttpGet("fast")]
[MonitorExecution(SlowAfterMs = 150, Category = "Demo")]
public IActionResult Fast() => Ok(new { Message = "Fast response" });
[HttpGet("slow")]
[MonitorExecution(SlowAfterMs = 150, Category = "Demo")]
public async Task<IActionResult> Slow()
{
await Task.Delay(250);
return Ok(new { Message = "Slow response" });
}
[HttpGet("fail")]
[MonitorExecution(SlowAfterMs = 150, Category = "Demo")]
public IActionResult Fail() => throw new InvalidOperationException("Simulated failure");
}
Attribute Options
[MonitorExecution] controls how a single controller action or endpoint is captured.
| Option | Type | Default | Description |
|---|---|---|---|
SlowAfterMs |
int |
1000 |
Marks the call as slow when its duration is greater than or equal to this threshold in milliseconds. |
Category |
string? |
null |
Groups executions into a named bucket such as Orders, Reporting, or Demo. |
CaptureParameters |
bool |
false |
Stores the input parameter values in the execution record. |
CaptureReturnValue |
bool |
false |
Stores the returned value when the action completes successfully. |
MaskSensitiveData |
bool |
true |
Redacts sensitive values before they are stored or shown in reports. |
Related attributes:
[IgnoreExecutionMonitor]skips an action or controller entirely.[MaskParameter]masks a specific parameter by name.[ExecutionCategory]applies a category at the class or method level.[TrackSla]marks a maximum allowed duration for SLA tracking.
TrackSla vs SlowAfterMs
Both values help the package decide when a call is "slow", but they serve different layers:
SlowAfterMson[MonitorExecution]is the execution-monitoring threshold for that action.[TrackSla(maxMs)]is an SLA-specific threshold that can be applied at the controller or action level.- If both are present,
[MonitorExecution(SlowAfterMs = ...)]takes precedence for that monitored action. - If
[MonitorExecution]does not setSlowAfterMs, the package falls back to[TrackSla]. - If neither is set, the global default
ExecutionMonitorOptions.DefaultSlowAfterMsis used.
Example:
[ApiController]
[Route("api/orders")]
[TrackSla(250)]
public sealed class OrdersController : ControllerBase
{
[HttpGet("{id}")]
[MonitorExecution(SlowAfterMs = 100)]
public IActionResult GetOrder(string id) => Ok(new { Id = id });
}
In that example:
- the order endpoint uses
100 msbecause the method-levelSlowAfterMswins - other actions on the controller would use
250 msfromTrackSlaunless they override it again
Where To Use Each Attribute
| Attribute | Use On | What It Affects |
|---|---|---|
[MonitorExecution] |
Controller class, controller action, or endpoint metadata | Main per-action monitoring settings such as slow threshold, category, parameter capture, and return-value capture. |
[IgnoreExecutionMonitor] |
Controller class or controller action | Completely excludes the decorated controller or action from monitoring. |
[ExecutionCategory] |
Controller class or controller action | Adds a category label that can be used for filtering and reports. |
[TrackSla] |
Controller class or controller action | Sets a performance SLA threshold used when SlowAfterMs is not explicitly set. |
[MaskParameter] |
Action parameter | Marks a specific parameter as sensitive so it is masked in stored records and reports. |
Rules of precedence:
- Method-level attributes override controller-level attributes.
MonitorExecution.SlowAfterMsoverridesTrackSla.MaxMs.MonitorExecution.CategoryoverridesExecutionCategory.MaskParameterapplies only to the parameter it decorates.IgnoreExecutionMonitorstops monitoring entirely for the decorated scope.
Minimal APIs:
MonitorExecutioncan also be attached through endpoint metadata.- If you are using minimal APIs, add the metadata when mapping the route instead of decorating a controller class or action method.
Storage
- InMemory for local development and tests
- File for JSON file snapshots
- SQL Server for real database persistence
Use SQL Server when you want shared reporting and durable history:
builder.Services.AddExecutionMonitor(options =>
{
options.EnableAiAssistance = true;
})
.UseSqlServer(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("ExecutionMonitor");
options.SchemaName = "monitor";
options.FallbackSchemaName = "dbo";
options.AutoCreateObjects = true;
});
App Type Guide
ExecutionMonitor has two layers:
- a shared monitoring/reporting core that works in any .NET app
- an ASP.NET Core integration layer that adds attributes, middleware, and HTTP report endpoints
Use in non-web apps
Use the shared core when you want to monitor background work, scheduled jobs, console commands, Windows services, or desktop workflows.
You can:
- wrap code with
IExecutionMonitorService - query or render reports with
IExecutionReportService - store executions with
IExecutionMonitorStore - generate AI recommendations with
IExecutionRecommendationProvider
Example:
using ExecutionMonitor;
using ExecutionMonitor.Abstractions;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddExecutionMonitor(options =>
{
options.ApplicationName = "WorkerJob";
options.EnvironmentName = "Production";
options.RecordSuccessfulExecutions = true;
})
.UseInMemory();
using var provider = services.BuildServiceProvider();
var monitor = provider.GetRequiredService<IExecutionMonitorService>();
var reports = provider.GetRequiredService<IExecutionReportService>();
await monitor.ExecuteAsync(async ct =>
{
await Task.Delay(250, ct);
});
var html = await reports.GenerateAsync(new ExecutionReportOptions
{
Format = "Html",
IncludeRecommendations = true,
MaxRows = 50
});
Use in web apps
Use the ASP.NET Core layer when you want to monitor controller actions and expose dashboard or report endpoints.
You can:
- decorate actions with
[MonitorExecution] - apply
[TrackSla]and[ExecutionCategory] - exclude endpoints with
[IgnoreExecutionMonitor] - mask sensitive inputs with
[MaskParameter] - expose
/execution-monitor/...reporting routes
Example:
builder.Services.AddExecutionMonitor(options =>
{
options.ApplicationName = "MyApi";
options.EnvironmentName = builder.Environment.EnvironmentName;
}).UseSqlServer(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("ExecutionMonitor");
options.AutoCreateObjects = true;
});
app.UseExecutionMonitor();
app.MapControllers();
app.MapExecutionMonitorEndpoints("/execution-monitor");
Works in both
These pieces are reusable in both app types:
ExecutionMonitorOptionsExecutionReportOptionsExecutionRecordQueryExecutionRecordExecutionSummaryExecutionReportIExecutionMonitorServiceIExecutionReportServiceIExecutionMonitorStoreIExecutionRecommendationProvider
Web only
These are only useful when the app hosts HTTP endpoints:
MonitorExecutionAttributeIgnoreExecutionMonitorAttributeExecutionCategoryAttributeTrackSlaAttributeMaskParameterAttributeExecutionMonitorMiddlewareExecutionMonitorActionFilterUseExecutionMonitor()MapExecutionMonitorEndpoints()
Where To Set Configuration
You can set ExecutionMonitor behavior in either code or config.
Common options
These live in AddExecutionMonitor(...) and work in both app types:
ApplicationNameEnvironmentNameDefaultSlowAfterMsCaptureParametersCaptureReturnValueMaskSensitiveDataRecordSuccessfulExecutionsRecordSlowExecutionsRecordFailedExecutionsSuccessSamplingRateEnableAiAssistanceAiModeAiTriggerModeGenerateRecommendationOnSlowCallGenerateRecommendationOnFailureGenerateReportAutomaticallyFailOnAiConfigurationErrorAiStoreRecommendationsAiCacheRecommendations
Non-web app example
Use this in a console app, worker service, background job, or Windows app:
using ExecutionMonitor;
using ExecutionMonitor.Abstractions;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddExecutionMonitor(options =>
{
options.ApplicationName = "BillingWorker";
options.EnvironmentName = "Production";
options.DefaultSlowAfterMs = 500;
options.EnableAiAssistance = true;
options.AiMode = ExecutionMonitorAiMode.OpenAICompatible;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.OnDemand;
options.AiStoreRecommendations = true;
})
.UseSqlServer(options =>
{
options.ConnectionString = "Server=localhost;Database=ExecutionMonitor;Trusted_Connection=True;TrustServerCertificate=True";
options.SchemaName = "monitor";
options.AutoCreateObjects = true;
});
If you want to keep secrets out of code, read them from config:
var connectionString = configuration.GetConnectionString("ExecutionMonitor");
var aiSection = configuration.GetSection("ExecutionMonitor:Ai");
Web app example
Use this in an ASP.NET Core API or MVC app:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddExecutionMonitor(options =>
{
options.ApplicationName = "OrdersApi";
options.EnvironmentName = builder.Environment.EnvironmentName;
options.DefaultSlowAfterMs = 1000;
options.EnableAiAssistance = true;
options.AiStoreRecommendations = true;
})
.UseSqlServer(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("ExecutionMonitor");
options.SchemaName = "monitor";
options.AutoCreateObjects = true;
})
.UseOpenAI(options =>
{
builder.Configuration.GetSection("ExecutionMonitor:Ai").Bind(options);
});
var app = builder.Build();
app.UseExecutionMonitor();
app.MapControllers();
app.MapExecutionMonitorEndpoints("/execution-monitor");
Web App Flow
The diagram below shows the full request path for ASP.NET Core apps, including when AI is used and when the package stays rule-based.
flowchart TD
U[User / Client Request] --> A[ASP.NET Core app]
A --> B[ExecutionMonitor middleware / action filter]
B --> C{MonitorExecution enabled?}
C -- No --> D[Request continues without monitoring]
C -- Yes --> E[Capture request metadata]
E --> F[Execute controller action / endpoint]
F --> G{Completed successfully?}
G -- Yes --> H{Record successful executions or sampled?}
G -- No --> I[Mark failure and capture exception]
H -- No --> J[Skip persistence]
H -- Yes --> K[Build ExecutionRecord]
I --> K
K --> L[Store execution in memory / file / SQL Server]
L --> M{AI enabled?}
M -- No --> N[Return response and persist only execution data]
M -- Yes --> O{AI trigger mode}
O -- OnDemand --> P[AI is called only from report or generate URLs]
O -- InlineForSlowAndFailed --> Q[AI runs during the monitored request for slow or failed calls]
O -- QueuedForSlowAndFailed --> R[AI runs in background after slow or failed calls]
P --> S{Remote AI configured and reachable?}
Q --> S
R --> S
S -- No --> T[Fallback to rule-based recommendation]
S -- Yes --> U2[Call OpenAI-compatible or Azure OpenAI endpoint]
U2 --> V[Parse recommendation JSON / text]
T --> W[Add recommendation to record]
V --> W
W --> X{AiStoreRecommendations enabled?}
X -- Yes --> Y[Persist recommendation to store]
X -- No --> Z[Keep recommendation only in memory for this request]
Y --> AA[Report endpoints can read the saved recommendation later]
Z --> AA
Web App Request Paths
GET /execution-monitor/report/htmlGET /execution-monitor/report/jsonGET /execution-monitor/report/textGET /execution-monitor/summaryGET /execution-monitor/slow-methodsGET /execution-monitor/failuresGET /execution-monitor/recommendationsPOST /execution-monitor/recommendations/generatePOST /execution-monitor/recommendations/generate/{executionLogId}
AI is used only when one of these conditions is true:
AiTriggerMode = OnDemandand you call a report or generate endpoint with AI enabled.AiTriggerMode = InlineForSlowAndFailedand the monitored call is slow or failed.AiTriggerMode = QueuedForSlowAndFailedand the monitored call is slow or failed, with generation moved off the request path.
If the remote AI provider is missing, misconfigured, or rate-limited, the package falls back to the built-in rule-based provider.
Config keys to set
Use these config paths for the most common values:
ConnectionStrings:ExecutionMonitor
ExecutionMonitor:Ai:Endpoint
ExecutionMonitor:Ai:ApiKey
ExecutionMonitor:Ai:Model
ExecutionMonitor:Ai:DeploymentName
ExecutionMonitor:Ai:ApiVersion
ExecutionMonitor:Ai:Organization
Full Configuration Reference
Monitoring options
Set these in AddExecutionMonitor(...):
| Option | Default | Purpose |
|---|---|---|
ApplicationName |
empty | Names the app in stored records and reports. |
EnvironmentName |
empty | Stores the deployment environment such as Development or Production. |
DefaultSlowAfterMs |
1000 |
Global slow-call threshold when an attribute does not override it. |
CaptureParameters |
false |
Captures input arguments for monitored executions. |
CaptureReturnValue |
false |
Captures return values for successful executions. |
MaskSensitiveData |
true |
Redacts sensitive values before storage and reporting. |
RecordSuccessfulExecutions |
false |
Stores successful calls when enabled. |
RecordSlowExecutions |
true |
Stores slow executions. |
RecordFailedExecutions |
true |
Stores failed executions. |
SuccessSamplingRate |
0.0 |
Keeps successful-call volume lower when you only want sampled telemetry. |
EnableAiAssistance |
false |
Turns on AI recommendation support. |
AiMode |
RuleBasedOnly |
Selects rule-based, OpenAI-compatible, or Azure OpenAI behavior. |
AiTriggerMode |
OnDemand |
Chooses when AI runs: on demand, queued, or inline. |
GenerateRecommendationOnSlowCall |
true |
Lets slow calls trigger recommendations. |
GenerateRecommendationOnFailure |
true |
Lets failures trigger recommendations. |
GenerateReportAutomatically |
false |
Enables automatic report generation behavior when supported by the host integration. |
FailOnAiConfigurationError |
false |
Controls whether bad AI config should fail fast or fall back. |
AiStoreRecommendations |
false |
Persists generated recommendations back to the store. |
AiCacheRecommendations |
true |
Reuses a cached recommendation for the same execution record. |
SQL Server storage options
Set these in .UseSqlServer(...):
| Option | Purpose |
|---|---|
ConnectionString |
SQL Server connection string used by the store. |
SchemaName |
Preferred schema for ExecutionLogs and ExecutionRecommendations. |
FallbackSchemaName |
Existing schema to reuse when the preferred schema is not available. |
AutoCreateObjects |
Creates the tables when they do not exist. |
AutoMigrate |
Allows the store to reuse compatible objects instead of requiring a blank schema. |
AI configuration keys
Set these in config for OpenAI-compatible or Azure OpenAI usage:
| Config key | Purpose |
|---|---|
ExecutionMonitor:Ai:Endpoint |
Chat-completions endpoint. |
ExecutionMonitor:Ai:ApiKey |
Remote AI API key. |
ExecutionMonitor:Ai:Model |
OpenAI-compatible model name. |
ExecutionMonitor:Ai:DeploymentName |
Azure OpenAI deployment name. |
ExecutionMonitor:Ai:ApiVersion |
Azure OpenAI API version. |
ExecutionMonitor:Ai:Organization |
Optional OpenAI organization header. |
ExecutionMonitor:Ai:TimeoutSeconds |
Request timeout for AI calls. |
ExecutionMonitor:Ai:MaxOutputTokens |
Maximum AI response size. |
Example appsettings.json:
{
"ConnectionStrings": {
"ExecutionMonitor": "Server=localhost;Database=ExecutionMonitor;Trusted_Connection=True;TrustServerCertificate=True"
},
"ExecutionMonitor": {
"Ai": {
"Endpoint": "https://api.openai.com/v1/chat/completions",
"ApiKey": "replace-with-your-openai-key",
"Model": "gpt-4o-mini",
"TimeoutSeconds": 20,
"MaxOutputTokens": 800
}
}
}
Licensing Workflow
The app should compute the identity values, create a protected local trial state on first run, and present the identity values to the user. The user then sends those values to owner (rajanvikramsingh@gmail.com) when a paid license is generated.
What to collect from the user side
MachineIdAppId
For the free trial, the app also creates and manages a hidden local state file automatically. The user should not edit that file.
License config keys:
ExecutionMonitor:License:AppIdExecutionMonitor:License:LicensePathExecutionMonitor:License:PublicKeyPathExecutionMonitor:License:RevalidationEndpointExecutionMonitor:License:RevalidationInterval
Suggested Pricing
These are example starting prices per machine per month. Adjust them to match your own licensing strategy.
| Edition | Price per machine / month | Notes |
|---|---|---|
| Free | $0 | Protected 36-hour trial, one machine, unlimited apps on that machine. |
| Pro | $19 | Production use for a single machine, with licensed revalidation support. |
| Enterprise | Custom | Volume pricing, deployment support, and contract-based terms. |
User flow
- Install the app.
- The app auto-creates a protected free-trial state file on first run.
- The app shows
MachineIdandAppId. - The user copies those values for activation if they want Pro or Enterprise.
- Owner (rajanvikramsingh@gmail.com) generates a signed license and sends it back.
- The app installs the license file and continues to validate it automatically.
- If no license file is present, the app keeps running on the protected free-trial state until the trial expires.
- If a revalidation endpoint is configured, the app rechecks the paid license periodically.
- If validation fails, the app fails closed instead of continuing in an unlicensed paid state.
Web apps
Expose a small endpoint that returns the identity values:
[ApiController]
[Route("api/license")]
public sealed class LicenseController : ControllerBase
{
private readonly LicenseIdentity _identity;
private readonly ILicenseStateStore _licenseStateStore;
public LicenseController(LicenseIdentity identity, ILicenseStateStore licenseStateStore)
{
_identity = identity;
_licenseStateStore = licenseStateStore;
}
[HttpGet("identity")]
public async Task<IActionResult> GetIdentity(CancellationToken cancellationToken)
{
var state = await _licenseStateStore.EnsureFreeTrialAsync(_identity, TimeSpan.FromHours(36), cancellationToken).ConfigureAwait(false);
return Ok(new
{
_identity.AppId,
_identity.MachineId,
Trial = new
{
state.LicenseId,
state.Edition,
state.FirstActivatedUtc,
state.LastSeenUtc,
state.ExpiresUtc,
}
});
}
}
The user can then copy the result or call:
curl http://localhost:5055/api/license/identity
Non-web apps
Print the values at startup, or show them in a simple admin screen. The same protected state store can be used in the background:
var identity = LicenseIdentityProvider.Create(
configuration["ExecutionMonitor:License:AppId"]);
var stateStore = new ProtectedLicenseStateStore();
await stateStore.EnsureFreeTrialAsync(identity, TimeSpan.FromHours(36));
Console.WriteLine($"AppId: {identity.AppId}");
Console.WriteLine($"MachineId: {identity.MachineId}");
For Pro and Enterprise, ask the user to email owner (rajanvikramsingh@gmail.com) the MachineId and AppId, then generate a signed license from those values.
AI behavior setup
These settings live on AddExecutionMonitor(...) and can be used in both app types:
EnableAiAssistanceAiModeAiTriggerModeGenerateRecommendationOnSlowCallGenerateRecommendationOnFailureGenerateReportAutomaticallyFailOnAiConfigurationErrorAiStoreRecommendationsAiCacheRecommendations
If ExecutionMonitor:Ai:ApiKey is not provided, the package falls back to the rule-based provider instead of calling a remote AI service.
Non-web app example
Use this in a console app, worker service, background job, or Windows app:
services.AddExecutionMonitor(options =>
{
options.EnableAiAssistance = true;
options.AiMode = ExecutionMonitorAiMode.RuleBasedOnly;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.OnDemand;
options.GenerateRecommendationOnSlowCall = true;
options.GenerateRecommendationOnFailure = true;
options.AiStoreRecommendations = true;
options.AiCacheRecommendations = true;
});
If you want to call a remote model from a non-web app, add the provider after the shared options:
services.AddExecutionMonitor(options =>
{
options.EnableAiAssistance = true;
options.AiMode = ExecutionMonitorAiMode.OpenAICompatible;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.OnDemand;
})
.UseOpenAI(ai =>
{
ai.Endpoint = configuration["ExecutionMonitor:Ai:Endpoint"];
ai.ApiKey = configuration["ExecutionMonitor:Ai:ApiKey"];
ai.Model = configuration["ExecutionMonitor:Ai:Model"];
});
Web app example
Use this in an ASP.NET Core API or MVC app:
builder.Services.AddExecutionMonitor(options =>
{
options.EnableAiAssistance = true;
options.AiMode = ExecutionMonitorAiMode.AzureOpenAI;
options.AiTriggerMode = ExecutionMonitorAiTriggerMode.InlineForSlowAndFailed;
options.GenerateRecommendationOnSlowCall = true;
options.GenerateRecommendationOnFailure = true;
options.AiStoreRecommendations = true;
options.AiCacheRecommendations = true;
})
.UseAzureOpenAI(ai =>
{
builder.Configuration.GetSection("ExecutionMonitor:Ai").Bind(ai);
});
What each of these means:
RuleBasedOnly: use the built-in fallback rules, no remote AI callOpenAICompatible: use OpenAI-style chat completionsAzureOpenAI: use Azure OpenAI chat completionsOnDemand: generate recommendations only when reports or generate endpoints are calledQueuedForSlowAndFailed: prepare recommendations for slow and failed executions outside the request pathInlineForSlowAndFailed: generate recommendations during the monitored call itselfAiStoreRecommendations = true: persist the generated recommendation with the execution recordAiCacheRecommendations = true: reuse a matching prior recommendation instead of generating againGenerateRecommendationOnSlowCall = true: allow slow calls to trigger recommendation generationGenerateRecommendationOnFailure = true: allow failed calls to trigger recommendation generation
Report Generation
Report generation works in both app types, but the delivery method is different.
In non-web apps
Generate the report directly in code:
var report = await reports.BuildReportAsync(new ExecutionReportOptions
{
IncludeRecommendations = true,
MaxRows = 100
});
var json = await reports.GenerateAsync(new ExecutionReportOptions
{
Format = "Json",
IncludeRecommendations = true,
MaxRows = 100
});
Use this when you want to:
- save reports to disk
- print summaries to the console
- send report output by email
- build operational tooling without hosting a website
In web apps
Expose the built-in report endpoints:
app.MapExecutionMonitorEndpoints("/execution-monitor");
Then call:
GET /execution-monitor/dashboardGET /execution-monitor/report/htmlGET /execution-monitor/report/jsonGET /execution-monitor/report/textGET /execution-monitor/summaryGET /execution-monitor/slow-methodsGET /execution-monitor/failuresGET /execution-monitor/recommendationsPOST /execution-monitor/recommendations/generate
Generate Reports
The reporting API is hosted by the application that references this package. There is no separate reporting service to deploy. The consuming app must:
- Register ExecutionMonitor in
Program.cs. - Add the middleware with
app.UseExecutionMonitor(). - Map the report endpoints with
app.MapExecutionMonitorEndpoints("/execution-monitor"). - Deploy with a real store, usually SQL Server, so the report endpoints can read persisted history.
- Configure AI credentials if you want recommendations generated automatically or on demand.
Map the report endpoints:
app.MapExecutionMonitorEndpoints("/execution-monitor");
Then open:
GET /execution-monitor/dashboardGET /execution-monitor/report/htmlGET /execution-monitor/report/jsonGET /execution-monitor/report/textGET /execution-monitor/summaryGET /execution-monitor/slow-methodsGET /execution-monitor/failuresGET /execution-monitor/recommendationsPOST /execution-monitor/recommendations/generate
Example report request:
curl http://localhost:5055/execution-monitor/report/json
Example AI recommendation generation:
curl -X POST http://localhost:5055/execution-monitor/recommendations/generate -H "Content-Type: application/json" -d "{\"onlyFailed\":true,\"maxRows\":20}"
If the consuming app is deployed behind IIS, Docker, Azure App Service, or a reverse proxy, the reporting URLs stay the same because they are part of the API surface of the consuming app. The only requirement is that the deployed API can reach the same SQL Server database configured under ConnectionStrings:ExecutionMonitor.
AI Behavior
ExecutionMonitorAiMode.RuleBasedOnlyuses the built-in fallback provider and does not call a remote AI service.ExecutionMonitorAiMode.OpenAICompatibleuses OpenAI-style chat completions.ExecutionMonitorAiMode.AzureOpenAIuses Azure OpenAI chat completions.ExecutionMonitorAiTriggerMode.OnDemandgenerates recommendations only when you call a report or generate endpoint.ExecutionMonitorAiTriggerMode.QueuedForSlowAndFailedprepares recommendations for slow and failed executions outside the request path.ExecutionMonitorAiTriggerMode.InlineForSlowAndFailedgenerates recommendations during the monitored call itself.AiStoreRecommendations = truesaves generated recommendations back to the store.AiCacheRecommendations = truereuses prior recommendations when the same execution shape is seen again.GenerateRecommendationOnSlowCall = trueallows slow calls to trigger AI recommendations.GenerateRecommendationOnFailure = trueallows failed calls to trigger AI recommendations.
Recommendation Reuse And Database Storage
- When a record already contains recommendations, report generation does not ask AI to generate another one for that same loaded record.
AiCacheRecommendations = trueavoids repeating the remote AI call in the same process for the same execution record.AiStoreRecommendations = truesaves generated recommendations back to the store.- The SQL Server store persists recommendations in the
ExecutionRecommendationstable. - The SQL Server read path reloads stored recommendations with the execution record, so future report requests can reuse them instead of treating the record as empty.
- If you call
POST /execution-monitor/recommendations/generate/{executionLogId}, the generated recommendation is stored and then returned for that execution record.
Notes
- Slow and failed executions are stored by default.
- Successful executions are only stored when sampling or explicit recording is enabled.
- AI recommendations are only captured when AI is enabled, the provider is configured, and the trigger mode allows it.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.