AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证嵌入式、自托管的 ASP.NET Core 可观测性仪表板:日志、指标、追踪和警报,自动捕获您应用已生成的内容,并从您自己的进程内部提供。
AI 翻译
An embedded, self-hosted observability dashboard for ASP.NET Core: logs, metrics, traces, and alerting, captured automatically from what your app already produces and served from inside your own process.
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
An embedded, self-hosted observability dashboard for ASP.NET Core: logs,
metrics, traces, and alerting, captured automatically from what your app
already produces and served from inside your own process.
No collector, no message broker, no external database, no Redis, no
separate process to run or deploy. One NuGet package, two lines in
Program.cs.查看完整 README ↗An embedded, self-hosted observability dashboard for ASP.NET Core: logs, metrics, traces, and alerting, captured automatically from what your app already produces and served from inside your own process.
builder.Services.AddNafasServer(); // storage + real ingestion + alert evaluation
app.UseNafasDashboard("/nafas"); // mounts the dashboard UI at /nafas
No collector, no message broker, no external database, no Redis, no
separate process to run or deploy. One NuGet package, two lines in
Program.cs.
Standing up observability for a small or medium ASP.NET Core service usually means an OTel Collector, a time-series database, and a separate dashboard to run and secure: real infrastructure for a problem that's often just "let me see recent errors and know when something breaks." Nafas is the alternative for that case: it lives inside your application's own process, stores data in a local SQLite file (or your existing SQL Server) by default, and needs nothing external to run.
It is not a replacement for a full OTel/Prometheus/Grafana stack at scale. It does not federate across services, it does not do long-term high-cardinality storage, and it does not export to other backends. It is for the case where a single self-hosted app (or a handful of them sharing a database) needs real logs, metrics, traces, and threshold-based alerting without introducing new infrastructure to operate.
dotnet add package Nafas.Observability
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddNafasServer(options =>
{
// Every option below is optional -- these are the actual defaults.
options.Provider = DatabaseProvider.Sqlite;
options.RetentionDays = 30;
// options.AlertWebhookUrl = "https://example.com/webhooks/nafas";
});
var app = builder.Build();
app.UseNafasDashboard("/nafas"); // -> https://your-app/nafas
app.Run();
That's it: no instrumentation code to add anywhere else. Every
ILogger call, every Activity/span (including ASP.NET Core's own
per-request span), and every Meter measurement (including ASP.NET Core
8+'s built-in request-duration metric) your app already produces is
captured from this point on and shown on a real-time dashboard mounted
directly inside your own app, in English or Persian, light or dark.
Nafas hooks into the same extension points ASP.NET Core and
Microsoft.Extensions.Logging/System.Diagnostics already expose. It
does not require replacing ILogger<T>, wrapping HttpClient, or adding
any [Trace]-style attributes to your code:
| Signal | Source | Notes |
|---|---|---|
| Logs | An ILoggerProvider registered alongside your existing providers | Every ILogger.Log* call in the app, correlated to the active Activity when there is one |
| Traces | A process-wide System.Diagnostics.ActivityListener | Listens to every ActivitySource, including ASP.NET Core's own built-in per-request activity; no manual span creation needed for basic request tracing |
| Metrics | A process-wide System.Diagnostics.Metrics.MeterListener | Listens to every Meter, including ASP.NET Core 8+'s built-in http.server.request.duration, from which request-rate is also derived |
| Resource usage | A native sampler (Process + GC.GetGCMemoryInfo(), with a Windows-API fallback; see Requirements) | CPU and memory usage of the running process, sampled on an interval |
All of it is written through a bounded, non-blocking queue
(System.Threading.Channels) so a burst of telemetry never applies
backpressure to your request pipeline. Under sustained overload, Nafas
drops its own data rather than slow your app down. A dedicated background
service drains and batches that queue into storage, and the dashboard's
live views (the log stream, in particular) are pushed to the browser over
Server-Sent Events at write time; they are not a polling query against
the database.
All of these are set inside AddNafasServer(options => { ... }).
| Option | Default | Notes |
|---|---|---|
Provider | DatabaseProvider.Sqlite | Sqlite or SqlServer |
ConnectionString | Data Source=nafas.db (SQLite only) | Set directly, or use ConnectionStringName instead |
ConnectionStringName | (none) | Resolves from appsettings.json's ConnectionStrings section first, then classic web.config's <connectionStrings> |
Schema | dbo | SQL Server only: keeps Nafas's tables under a named schema, separate from the rest of your database |
RetentionDays | 30 | How long logs/metrics/traces are kept before a background sweep deletes them |
AlertWebhookUrl | (none) | Where alert incidents are POSTed as JSON. Alert rules and incidents still work without it; nothing gets delivered anywhere until it's set |
ServiceName | IHostEnvironment.ApplicationName | Overrides the service name stamped on everything ingested |
Authorize | null (local requests only) | Gates every request to the dashboard; see Security |
UseNafasDashboard(path) takes the mount path for the dashboard UI itself
(default "/nafas"). Pick any path your app isn't already using.
The dashboard shows logs, traces, and metrics, which routinely carry
sensitive data (request bodies, stack traces, connection strings inside
exception messages). By default, UseNafasDashboard only serves requests
that come from the local machine, the same default posture as
Hangfire's own dashboard.
Every other request gets a 403.
builder.Services.AddNafasServer(options =>
{
// Runs after your own auth middleware, if you have one registered
// earlier in the pipeline -- httpContext.User is already populated.
options.Authorize = httpContext => httpContext.User.IsInRole("Admin");
// Or, deliberately, open to everyone (not recommended without your
// own auth in front of it):
// options.Authorize = _ => true;
});
If you don't set Authorize and the dashboard doesn't load from where you
expect (a phone on the same network, a teammate's machine, behind a
reverse proxy), this is why. Set it explicitly once you know who should
be allowed in.
UseNafasDashboard needs an IApplicationBuilder to mount onto. A plain
WinForms, WPF, or console/worker app has no ASP.NET Core pipeline of its
own to provide one. Nafas.Observability.Server is a separate, optional
package for exactly that case: it runs a second, independent HTTP listener
(on its own port) that serves the same dashboard against the same running
app's real data. No Kestrel, no OWIN, just a minimal HTTP/1.1 server
directly on TcpListener (see
Nafas.Observability.Server/Nafas.Observability.Server.csproj's
own comment for why: the last version of Kestrel ever published as a plain
NuGet package is 2.3.13 from 2019, with no HTTP/2 and no security patches
since, and System.Net.HttpListener (the BCL's own listener) turned out
not to be usable for the LAN case either: on Windows it's backed by
HTTP.sys, which refuses to bind any prefix other than "localhost" unless
the process runs elevated or a URL ACL was reserved beforehand, verified
empirically). Being plain netstandard2.0 like the core package itself,
this also works from classic .NET Framework 4.6.1+ apps, not just modern
.NET. Confirmed against a real net48 console host, not just the TFM
compatibility rules, in
Nafas.Observability.Server.NetFrameworkSample
(see Requirements for the one net48-specific gotcha it
also caught, around SQLite and PlatformTarget).
dotnet add package Nafas.Observability.Server
services.AddNafasServer(); // same as always -- storage + ingestion
services.AddNafasHttpServer(o =>
{
o.Enabled = true; // opt-in; does nothing while false
o.Port = 5099;
o.AccessMode = NafasHttpServerAccessMode.LocalhostOnly; // or .Lan
});
then open http://localhost:5099/nafas. NafasHttpServerOptions.Enabled is
only read once at startup. To start/stop/reconfigure it later (e.g. a
settings screen's "enable dashboard" checkbox), resolve NafasHttpServer
from DI and call its StartAsync/StopAsync directly; see
Nafas.Observability.Server.Sample/
for a worked WPF example.
AccessMode only controls which network interface is bound:
LocalhostOnly (the default) binds loopback only, Lan binds every
interface so other devices on the network can reach the port via this
machine's own IP, with no administrator rights required either way (unlike
System.Net.HttpListener, verified empirically on both). It does not by
itself relax NafasServerOptions.Authorize (still local-requests-only by
default, see Security): set Authorize explicitly too if
Lan should actually let those requests through, otherwise they still get
a 403.
One request per connection, not HTTP/1.1 keep-alive. This listener is a
local/LAN admin tool, not a public high-throughput API, so giving up
connection reuse (an extra TCP handshake per dashboard API call,
imperceptible on localhost/LAN) removes an entire dimension of correctness
surface instead. See
Nafas.Observability.Server/Internal/NafasRawHttpConnection.cs's
own comment.
Threshold and absence rules are defined from the dashboard itself (Alerts page). No code required to create one. A background evaluator polls every 30 seconds and fires only on state transitions (an incident opens when a rule first goes out of bounds, and resolves when it recovers) so a rule that stays broken for an hour doesn't re-notify every cycle.
Nafas does not send email, SMS, or Slack messages itself. When
AlertWebhookUrl is set, every open or resolve event is POSTed there as
JSON, and your own system takes it from there:
{
"ruleId": 12,
"ruleName": "High error rate",
"ruleType": "threshold",
"status": "open",
"detail": "error_rate is 8.4, threshold is 5",
"triggeredAtUtc": "2026-01-15T09:32:00Z",
"resolvedAtUtc": null
}
A failed webhook delivery is logged and retried on the next transition; it never fails the request pipeline, and the incident still exists in the dashboard's own incident list regardless of whether delivery succeeded.
Nafas has no concept of a central collector: each instance that calls
AddNafasServer() ingests and stores its own process's telemetry. Two
common setups:
ServiceName
resolves to IHostEnvironment.ApplicationName.ConnectionString/ConnectionStringName at the same
database and give each a distinct ServiceName. All of them write to
the same tables and are distinguishable in every dashboard view by
service name; any instance's /nafas mount shows data from all of
them.SQLite is a single-file, single-process store and is not meant to be shared across instances. Use SQL Server for that case.
netstandard2.0 for the broadest possible reach on
the consuming app's side: .NET Core 2.0+, .NET 5+, or classic .NET
Framework 4.6.1+ can all reference it (ConnectionStringName's
classic web.config support exists for exactly that last case).UseNafasDashboard requires
IApplicationBuilder. For WinForms/WPF/console apps, see
Nafas.Observability.Server
instead.memoryUsage (one of several resource metrics; logs, traces, CPU
usage, and alerting are all unaffected either way) is read differently
depending on the host, in this order:
GC.GetGCMemoryInfo(),
which is container-aware. It reflects a cgroup or Docker memory limit
when one is set, not just physical host RAM.GlobalMemoryStatusEx API (the same one
.NET Framework apps have always used for this, no managed equivalent
exists there). This reports whole-machine physical memory, not
container/Job-Object-aware, so a process capped by a Job Object
memory limit will under-report here.GC.GetGCMemoryInfo() (practically:
.NET Core 2.x on Linux/macOS): reports 0. This path is explicitly
gated to Windows only, so a Linux container never attempts a
kernel32.dll call in the first place.PlatformTarget.
netstandard2.0 compatibility means .NET Framework can reference this
package, but the SQLite native driver still needs to know which
architecture to load. On .NET Core/.NET 5+, the runtime's own RID-graph
resolves this automatically regardless of how the project was built; on
classic .NET Framework there's no such mechanism, so an AnyCPU build
can copy a native e_sqlite3.dll that doesn't match the process's actual
bitness at run time, failing with Library e_sqlite3 not found / %1 is not a valid Win32 application. Set <PlatformTarget>x64</PlatformTarget>
(or x86, matching your deployment) and <Prefer32Bit>false</Prefer32Bit>
explicitly in the consuming to fix it. Verified against a real
net48 console host in
,
see that project's own comment. SQL Server storage isn't
affected (no native driver to resolve this way).For anyone evaluating this beyond the quick start:
Ingestion/) is entirely in-process: an
ILoggerProvider, an ActivityListener, and a MeterListener, each
writing into a bounded per-signal Channel<T> (capacity 20,000,
drop-on-full). A single hosted service drains all three, batches
writes (up to 500 records or every second, whichever comes first), and
persists them.Storage/) is a thin interface
(INafasIngestionWriter/INafasQueryStore) with two concrete
implementations, SQLite and SQL Server. No ORM, parameterized SQL,
schema created and migrated at startup.NafasAlertEvaluationHostedService) is a polling
BackgroundService reusing the same query layer the dashboard's KPI
views use, tracking one open-incident-per-rule at a time.NafasLiveFeed) fed by the ingestion writer and alert evaluator, read
by the dashboard's SSE endpoints. Nothing here is a polling query.ClientApp/) is a Vue 3 + Vite SPA (PrimeVue,
Pinia, Chart.js, vue-i18n for English/Persian), built ahead of time and
embedded directly into the assembly as embedded resources. A consumer
installing the package gets the whole UI with no separate static-file
deployment step, and no Node.js runtime on the host machine.api/* surface (NafasDashboardEndpoints.cs) is a small
hand-rolled router rather than ASP.NET Core's IEndpointRouteBuilder,
because endpoint routing has been FrameworkReference-only since
ASP.NET Core 3.0 and is unavailable to a netstandard2.x class
library.Nafas.Observability/: the package itself.Nafas.Dashboard.TestHost/: a minimal
ASP.NET Core app used to manually verify the package end to end; not a
usage example to copy patterns from.Nafas.Observability.Server/: the
optional standalone HTTP listener package for non-ASP.NET Core apps (no
Kestrel, no OWIN); see Desktop and other non-ASP.NET Core apps.Nafas.Observability.Server.Sample/:
a WPF app demonstrating it, with a settings-screen-style checkbox to
enable/disable the dashboard server, pick its port, and choose
localhost-only vs. LAN access at runtime.Nafas.Observability.Server.NetFrameworkSample/:
a plain net48 console app; not a usage pattern to copy (a real .NET
Framework consumer wouldn't normally need the Generic Host boilerplate
shown here), but an actual classic .NET Framework process used to
empirically verify the whole stack (storage, ingestion, and the
TcpListener-based HTTP server) rather than just relying on
netstandard2.0's TFM compatibility claim.For normal use of the published package, no Node.js/npm step is needed at
all. The dashboard UI is already built and committed under
Nafas.Observability/wwwroot/. Just build the solution:
dotnet build nafas.observability.sln
To try the package locally end to end:
cd Nafas.Dashboard.TestHost
dotnet run
then open http://localhost:<port>/nafas.
To change the dashboard UI itself:
cd Nafas.Observability/ClientApp
npm install # first time only
npm run build # output goes to ClientApp/dist/
# then replace (not merge) the contents of ../wwwroot/ with dist/'s contents
then dotnet build again so the updated files are re-embedded.
.github/workflows/publish.yml does this same rebuild-and-sync step from
source on every publish, so a tagged release never ships a stale UI.
Feature-complete for the free tier. Logs, metrics, traces, alerting,
the embedded dashboard, and the standalone desktop/console server
(Nafas.Observability.Server) are all shipped. No further feature work
is planned for this scope. From here, changes to the free tier are bug
fixes and hardening, not new capability.
Still pre-1.0 (see the NuGet badges at the top of this file for the exact
versions), and that status is about proof, not scope: real ingestion,
storage, dashboard, and alert evaluation all work and are exercised
through Nafas.Dashboard.TestHost, but neither package has yet run in a
real production deployment or been through a real upgrade cycle. The
public API surface (NafasServerOptions, UseNafasDashboard) is expected
to stay stable regardless.
FSL-1.1-ALv2: free to use, modify and redistribute for any purpose other than building a competing product or service; each release converts to the Apache License, Version 2.0 two years after it ships. See LICENSE.md.
.csproj.csproj