Spyglass.AspNetCore
0.2.1
dotnet add package Spyglass.AspNetCore --version 0.2.1
NuGet\Install-Package Spyglass.AspNetCore -Version 0.2.1
<PackageReference Include="Spyglass.AspNetCore" Version="0.2.1" />
<PackageVersion Include="Spyglass.AspNetCore" Version="0.2.1" />
<PackageReference Include="Spyglass.AspNetCore" />
paket add Spyglass.AspNetCore --version 0.2.1
#r "nuget: Spyglass.AspNetCore, 0.2.1"
#:package Spyglass.AspNetCore@0.2.1
#addin nuget:?package=Spyglass.AspNetCore&version=0.2.1
#tool nuget:?package=Spyglass.AspNetCore&version=0.2.1
Spyglass
A lightweight, embeddable dashboard for ASP.NET Core applications — add visibility into your running app with a single line of code.
Installation
dotnet add package Spyglass.AspNetCore
Getting started
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSpyglass();
builder.Services.AddHealthChecks(); // optional — health panel populates automatically
var app = builder.Build();
app.MapSpyglassDashboard();
app.Run();
Navigate to /spyglass to see the dashboard. The route is configurable:
app.MapSpyglassDashboard("/admin/status");
Monitoring external dependencies
Register the services your app depends on and Spyglass will show their connectivity status. Probes are lightweight TCP + protocol handshakes — no client libraries are pulled into your app.
builder.Services.AddSpyglass()
.AddDependencyProbes(probes => probes
.AddPostgres()
.AddMongoDb()
.AddRedis()
.AddKafka()
.AddRabbitMq());
Custom probes are supported for any dependency. The probe receives a CancellationToken and should return true for healthy, false for unhealthy, or throw to surface an error message.
builder.Services.AddSpyglass()
.AddDependencyProbes(probes => probes
.AddDependency("Orders API", "https://orders.internal/health", async ct =>
{
using var http = new HttpClient();
var response = await http.GetAsync("https://orders.internal/health", ct);
return response.IsSuccessStatusCode;
}));
Features
Overview
Live request metrics (total, req/s, avg and max response time, active requests, 4xx/5xx error counts), memory and CPU usage, GC statistics, thread pool utilization, and server uptime. Each stat card displays a canvas sparkline backed by a 300-sample (5-minute) ring buffer sampled every second. Health checks, dependencies, and (when log capture is enabled) a live Warnings & Errors feed are shown alongside a compact view of registered configuration keys and services. Metrics update on a configurable interval (1 s – 30 s) with a live countdown and pause control.
Health checks
Results of all registered IHealthCheck implementations with status (Healthy, Degraded, Unhealthy) and description text. Populates automatically — no additional registration required.
Dependencies
Real-time connectivity status for external services. Built-in probes for Postgres, MongoDB, Redis, Kafka, RabbitMQ, MySQL/MariaDB, SQL Server, Memcached, NATS, and Elasticsearch/OpenSearch. Custom probes supported for any other service. Probe results are cached for 5 seconds to avoid hammering slow or unreachable targets on every poll. Failure messages distinguish between connection refused, timeout, and DNS/host not found.
Request log
Capture every incoming HTTP request — path, method, status code, response time, and timestamp — in a fixed-size in-memory ring buffer and stream them live to the dashboard. Enable with .AddRequestLog():
builder.Services.AddSpyglass()
.AddRequestLog();
The Requests page shows entries in a scrollable table with:
- Status class filter chips (1xx / 2xx / 3xx / 4xx / 5xx / Err) — toggle which classes are visible; each chip is colour-coded to match its status badge
- Path search — real-time substring filter
- Sortable columns — Time, Method, Path, Status, Elapsed; default is newest-first
- Auto-scroll — follows the tail as new entries arrive; disabled automatically when a non-time sort is active
Up to 2 000 entries are retained. The current buffer is loaded as a snapshot when a client connects, then live entries are pushed via SignalR in 250 ms batches. Spyglass's own routes are never captured.
Logs
Real-time log streaming from the application's own logger. Log capture is opt-in — enable it with .AddLogging() on the builder:
builder.Services.AddSpyglass()
.AddLogging();
Entries are streamed to connected clients in batches via SignalR. The page supports:
- Per-level filter chips and category search
- Virtual scrolling — renders only visible rows regardless of entry count
- Click any row to expand the full message and exception text in a side panel
- Up to 10 000 entries retained in memory, with a 10-minute TTL
The minimum capture level defaults to Information. Pass a LogLevel to raise it — the UI automatically disables filter chips for levels below the minimum:
builder.Services.AddSpyglass()
.AddLogging(LogLevel.Warning);
When logging is not enabled, the Logs tab is visually disabled and the Warnings & Errors widget is hidden from the Overview page.
Log capture surfaces what your application already logs. Sensitive data should be excluded at the source by configuring log levels and scopes in the application itself.
Endpoints
Browse registered HTTP endpoints, SignalR hubs, and gRPC services. Each endpoint type is opt-in — enable one or more on the builder:
builder.Services.AddSpyglass()
.AddHttpRoutes() // HTTP endpoints
.AddSignalR() // SignalR hubs
.AddGrpc(); // gRPC services
When two or more types are enabled, the Endpoints page shows an inner tab bar to switch between them. When none are enabled, the Endpoints tab is visually disabled.
HTTP routes are grouped by controller name (MVC/API controllers) or by the first path segment (minimal APIs). Groups are sorted alphabetically; routes within each group are sorted by path then method. SignalR hub routes are excluded — they appear in the SignalR tab instead. Each route has a collapsible try/execute panel pre-filled with auto-generated example values — send requests directly from the dashboard without leaving the browser. Required parameters (non-nullable value types or [Required]-annotated) are marked with * and validated before sending. System.ComponentModel.DataAnnotations constraint attributes ([Range], [StringLength], [MaxLength], [MinLength], [RegularExpression]) are displayed as hint text below the input. For endpoints with a structured request body, a body builder generates a typed form field for each property (checkbox for booleans, date/time picker for DateTime, numeric input for numbers, dropdown for enums, etc.). A Raw toggle switches to a plain JSON textarea at any time; the two views stay in sync.
SignalR hubs are discovered automatically from EndpointDataSource. Each hub entry lists all public hub methods with parameter types, return types, and whether the method streams from server to client (IAsyncEnumerable<T> or ChannelReader<T> return types are detected automatically). An interactive hub listener panel lets you connect to any registered hub directly from the dashboard: each subscribed topic gets its own scrollable column. For typed hubs (Hub<TClient>), available client-side method names are pre-populated from the TClient interface via reflection. You can also invoke any server-side hub method by name with an optional JSON argument. Streaming methods (detected from their return type) use connection.stream() automatically.
gRPC services are discovered via reflection — no compile-time dependency on Grpc.AspNetCore is required in the host app. Each service entry lists all methods with request/response message types and the streaming mode (Unary, ClientStreaming, ServerStreaming, Bidirectional). Click any method row to expand a try panel:
- Unary — enter a JSON request body, click Send, see the single response
- Client streaming — enter a JSON array of request messages; all are sent to the service in order before the response is returned
- Server streaming — enter a single JSON request; each message the server emits is shown as a numbered block
- Bidirectional — enter a JSON array of request messages; one response block is shown per message
The request body textarea is pre-populated with all the message's fields set to their default values. The server-side proxy resolves the service implementation directly from DI — no HTTP/2 or gRPC-Web infrastructure is required.
Auth tab is always present in the tab bar on the Endpoints page (no separate opt-in call required). It shows:
- All registered authentication schemes with handler type and display name; JWT Bearer schemes additionally surface their configured Authority and Audience
- Named authorization policies showing required roles and whether authentication is required
DefaultPolicyandFallbackPolicyfromAuthorizationOptions(below the named-policies table), when configured- The active JWT bearer token (read from
localStorage), decoded inline —exp,iat,nbf, andauth_timeclaims are formatted as human-readable local dates with relative suffixes (e.g. "in 4 min", "2 h ago")
Benchmark
Send load against any endpoint in the app and see latency and throughput stats in real time. Enable the tab with .AddBenchmark():
builder.Services.AddSpyglass()
.AddBenchmark();
Three run modes are available:
- Count — fires a fixed number of requests, with an optional warm-up phase
- Duration — fires for a fixed time window at a configured rate (req/s) and concurrency
- Free Run — fires indefinitely until you press Stop; you can pause at any time, adjust the URL, method, rate, concurrency, or body, and then resume
Throughput is modelled as rate × concurrency: on each rate tick all available concurrency slots are filled, so setting rate = 10 and concurrency = 5 targets 50 req/s.
If .AddHttpRoutes() is also enabled, a route picker appears above the URL field. Selecting a route pre-fills the URL (with example parameter values substituted), method, and body.
Results are updated every 500 ms during a run and include latency percentiles (min / mean / std dev / p50 / p95 / p99 / p99.9 / max / first response), throughput (total requests / wall time / req/s / target req/s / success rate), and a status-code class breakdown (2xx / 4xx / 5xx / network errors). A latency histogram is shown below the stat cards.
Outgoing HTTP
Capture all outgoing HTTP calls made through IHttpClientFactory-registered clients. Enable with .AddOutgoingHttp():
builder.Services.AddSpyglass()
.AddOutgoingHttp();
The Outgoing page captures the named client, HTTP method, URL, response status code, latency, and whether the request resulted in an error. Up to 500 entries are retained. The current buffer is loaded as a snapshot when a client connects, then live entries are pushed via SignalR in 250 ms batches.
The page includes status class filter chips, URL and client name search filters, sortable columns, auto-scroll, and a click-to-expand detail panel. Only clients registered through IHttpClientFactory are captured — manually constructed HttpClient instances are not.
Configuration
Browse all configuration keys registered in the app. Filterable by provider (appsettings.json, environment variables, etc.), searchable by key or value, and sortable by any column.
In non-production environments, values whose key contains a sensitive term — password, secret, token, apikey, connectionstring, credentials, private_key, etc. — are shown as dots with a click-to-reveal toggle. No data is hidden from the server; values are only de-emphasised in the browser to reduce accidental on-screen exposure.
Services
Browse all services registered in the DI container. Searchable and sortable. Columns include service type, implementation type, namespace, lifetime, number of constructor dependencies (Injects), and number of services that depend on it (Dependents). Click any row to expand a detail panel showing the full constructor dependency list with each dependency's resolved lifetime.
Metadata badges appear inline:
hosted— service implementsIHostedServicedisposable— service implementsIDisposableorIAsyncDisposableopen-generic— registered as an open generic (e.g.IRepository<>)×N— multiple registrations of the same service type- keyed service key — for ASP.NET Core 8 keyed services
The summary strip shows a lifetime breakdown and a count of services with detected issues.
Lifetime issue detection:
- Captive dependency — a Scoped or Transient service injected into a Singleton
- Root-scope capture — a Singleton that injects
IServiceProvider(callingGetServiceon the root container can resolve Scoped services outside a scope)
Autofac integration
If your app uses Autofac as the DI container, install the companion package to surface native Autofac registrations in the Services tab alongside your IServiceCollection services:
dotnet add package Spyglass.AspNetCore.Autofac
Configure Autofac as the service provider factory and call .AddAutofac() on the Spyglass builder:
using Autofac;
using Autofac.Extensions.DependencyInjection;
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Services.AddSpyglass()
.AddAutofac();
builder.Host.ConfigureContainer<ContainerBuilder>(container =>
{
container.RegisterType<SlackNotifier>().As<INotifier>().SingleInstance();
container.RegisterType<AuditLog>().As<IAuditLog>().InstancePerLifetimeScope();
container.RegisterType<FeatureFlags>().As<IFeatureFlags>().InstancePerDependency();
});
Autofac-native registrations appear in the Services tab tagged with an autofac source badge. Their lifetimes are shown verbatim — SingleInstance, InstancePerLifetimeScope, InstancePerDependency — without mapping to the IServiceCollection equivalents. Registrations bridged from IServiceCollection are deduplicated automatically so each service appears only once.
Authorization
MapSpyglassDashboard() returns a RouteGroupBuilder, so you can protect the entire dashboard with any ASP.NET Core authorization policy:
app.MapSpyglassDashboard()
.RequireAuthorization("AdminOnly");
Production use
Spyglass is a developer and operator tool. Protect the route at the infrastructure level (reverse proxy, firewall, VPN) rather than relying solely on application-layer access control.
When the environment is Production, Spyglass automatically redacts:
- Configuration values — key names and providers remain visible; values are replaced with
*** - Health check descriptions — status (Healthy / Degraded / Unhealthy) is shown; the description text is hidden
- Dependency targets and error messages — service name and status are shown; the host/port and failure message are hidden
A ⚠ indicator appears next to the environment badge to make it immediately clear the dashboard is connected to live production data.
| 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. |
-
net8.0
- Google.Protobuf (>= 3.35.0)
- Grpc.Core.Api (>= 2.80.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Spyglass.AspNetCore:
| Package | Downloads |
|---|---|
|
Spyglass.AspNetCore.Autofac
Autofac integration for the Spyglass observability dashboard. Surfaces native Autofac registrations (SingleInstance, InstancePerLifetimeScope, InstancePerDependency) alongside IServiceCollection services in the Spyglass Services tab. |
|
|
Spyglass.AspNetCore.EntityFrameworkCore
EF Core integration for the Spyglass observability dashboard. Captures slow queries and errors via a DbCommandInterceptor and surfaces them in a dedicated Queries tab with live streaming, SQL preview, and click-to-expand detail. |
GitHub repositories
This package is not used by any popular GitHub repositories.
See CHANGELOG.md at https://github.com/jhuessen/spyglass/blob/main/CHANGELOG.md