RoushTech.Asio
0.1.0
dotnet add package RoushTech.Asio --version 0.1.0
NuGet\Install-Package RoushTech.Asio -Version 0.1.0
<PackageReference Include="RoushTech.Asio" Version="0.1.0" />
<PackageVersion Include="RoushTech.Asio" Version="0.1.0" />
<PackageReference Include="RoushTech.Asio" />
paket add RoushTech.Asio --version 0.1.0
#r "nuget: RoushTech.Asio, 0.1.0"
#:package RoushTech.Asio@0.1.0
#addin nuget:?package=RoushTech.Asio&version=0.1.0
#tool nuget:?package=RoushTech.Asio&version=0.1.0
Asio
Real-time background job log streaming for ASP.NET Core.
Asio lets you stream log output from background jobs (Hangfire, hosted services, or any async workload) to connected clients in real-time, with full session replay for clients that connect after a job has already started. It hooks into the standard ILogger infrastructure — any Logger.Log* call made within a job context is automatically captured and forwarded to the session, with no changes to existing logging code.
Named after Asio, the genus of eared owls — watching quietly in the background.
Packages
| Package | Description |
|---|---|
RoushTech.Asio |
Core: session tracking, ILoggerProvider, Channel<T> drain pipeline |
RoushTech.Asio.Redis |
Redis-backed session log storage via StackExchange.Redis |
RoushTech.Asio.SignalR |
Real-time delivery via ASP.NET Core SignalR |
How It Works
- When a job starts, it calls
JobSessionService.ActivateSession(sessionId), which sets anAsyncLocal<Guid?>on the current async context. - A custom
ILoggerProvider(JobSessionLoggerProvider) checks thisAsyncLocalon every log call. If a session is active, it writes the entry to an in-processChannel<T>. JobSessionDrainService(aBackgroundService) reads from the channel and callsJobSessionService.AppendLog, which persists the entry viaIJobSessionStoreand pushes it live viaIJobSessionSink.- Clients connect to
JobSessionHuband callWatch(sessionId). The hub replays all persisted log entries from the store, then keeps the client subscribed to live updates for the remainder of the job.
Because sessions are stored in Redis with a 24-hour TTL, clients can disconnect and reconnect at any time and receive the full log history.
Installation
dotnet add package RoushTech.Asio
dotnet add package RoushTech.Asio.Redis
dotnet add package RoushTech.Asio.SignalR
Setup
1. Register services
In Program.cs:
using RoushTech.Asio;
using RoushTech.Asio.Redis;
using RoushTech.Asio.SignalR;
builder.Services
.AddAsio()
.AddAsioRedis(builder.Configuration) // reads ConnectionStrings:Redis
// ...
.AddSignalR()
.AddAsioSignalR();
2. Map the hub
app.UseEndpoints(endpoints =>
{
endpoints.MapAsioHub(); // default: /hubs/job-session
// or with a custom path:
endpoints.MapAsioHub("/hubs/my-jobs");
});
3. Configure Redis connection string
{
"ConnectionStrings": {
"Redis": "localhost:6379,password=yourpassword"
}
}
4. Configure log levels
Asio respects standard ILogger configuration. To control what level Asio captures independently of other providers, use the "Asio" section:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
},
"Asio": {
"LogLevel": {
"Default": "Warning",
"YourApp.Namespace": "Debug"
}
}
}
}
5. Add Redis health check
builder.Services
.AddHealthChecks()
.AddAsioRedis(tags: ["critical"]);
Usage in a Background Job
Generate a session ID in your controller or wherever you enqueue the job, return it to the caller, and pass it into the job:
[HttpPost("{id}/run")]
public IActionResult TriggerJob([FromRoute] Guid id)
{
var sessionId = Guid.NewGuid();
BackgroundJobClient.Enqueue<MyJobService>("queue", s => s.Run(id, sessionId));
return Ok(new { sessionId });
}
In your job, activate the session at the start and complete it at the end:
public class MyJobService(
ILogger<MyJobService> logger,
JobSessionService jobSessionService)
{
public async Task Run(Guid id, Guid sessionId)
{
JobSessionService.ActivateSession(sessionId);
try
{
// All Logger calls below are automatically captured in the session.
logger.LogInformation("Starting job for {Id}", id);
await DoWork(id);
logger.LogInformation("Job completed successfully.");
}
finally
{
await jobSessionService.CompleteSession(sessionId);
}
}
}
No other changes are needed — existing ILogger calls throughout the call tree are captured automatically for the duration of the activated session.
Frontend Integration
The hub exposes a small contract:
| Direction | Name | Payload |
|---|---|---|
| Client → Server | Watch(sessionId: string) |
Subscribes the caller. Replays all persisted log entries to the caller, then joins them to the live group for that session. |
| Server → Client | LogMessage |
(message: string, level: number) — level matches Microsoft.Extensions.Logging.LogLevel (Trace=0 … Critical=5). |
| Server → Client | SessionComplete |
(hasError: boolean) — fired once when the job completes. |
A minimal Vue 3 + @microsoft/signalr reference component lives at samples/vue/JobSessionLog.vue. It connects to the hub, replays history, renders live log lines with level-based coloring, and re-replays on reconnect. It's intentionally framework-light (no UI library dependency) — wrap it in your own dialog/modal as needed.
<JobSessionLog :session-id="sessionId" @complete="onJobComplete" />
Architecture Summary
Job (any async context)
└─ Logger.LogInformation(...)
└─ JobSessionLoggerProvider.IsEnabled() ── checks AsyncLocal session
└─ ChannelWriter.TryWrite() ── synchronous, non-blocking
JobSessionDrainService (BackgroundService, per host)
└─ ChannelReader.ReadAllAsync()
└─ JobSessionService.AppendLog()
├─ IJobSessionStore.AppendLog() ── persists to Redis (24h TTL)
└─ IJobSessionSink.PushLog() ── pushes via SignalR
Client (browser)
└─ HubConnection.invoke("Watch", sessionId)
└─ JobSessionHub.Watch()
├─ replay all persisted logs to caller
└─ subscribe to live updates via SignalR group
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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. |
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging (>= 9.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on RoushTech.Asio:
| Package | Downloads |
|---|---|
|
RoushTech.Asio.SignalR
Real-time delivery of RoushTech.Asio job session logs to browser clients via ASP.NET Core SignalR. |
|
|
RoushTech.Asio.Redis
Redis-backed session log storage for RoushTech.Asio via StackExchange.Redis. Multi-node safe with configurable TTL. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 202 | 5/27/2026 |