Skip to content

Embedded CritterWatch ​

Run the console inside your own application's Wolverine runtime, instead of as a separate service. It mounts at a path you choose, monitors the host it lives in, and keeps its own data in its own store so nothing of yours is touched.

This is aimed squarely at the development inner loop: you get the console while you are working on the app, with no second process to run and — on SQLite — no infrastructure at all.

When to use the standalone console instead

Embedded monitors its own host and nothing else. If you need one console watching several services, run the standalone console; that is what it is for. The two are the same console, wired differently.

What isolation you get ​

The console's documents, events and schema live in an ancillary document store that is entirely its own. Concretely:

  • Your store is untouched. The console never writes documents or events into it, and never migrates it.

  • Your event store keeps its own stream identity. CritterWatch identifies streams by string; most applications use Guid. That is a per-store setting, not a per-aggregate one, which is exactly why the console needs its own store rather than a corner of yours.

  • The console gets its own wolverine_* envelope tables, in its own schema (or, on SQLite, its own file). One runtime, but two message stores.

    This page previously said the opposite

    Until 1.1 this section claimed the console's messages ride your application's existing envelope tables and that "you do not get a second set of wolverine_* tables to reason about or clean up." That was wrong, and it was measured rather than argued — embedded_durability_sharing_1139 provisions a real host three ways and reads the database catalogue.

    Sharing happens only when the host has set opts.Durability.MessageStorageSchemaName. ⚠️ That is not the same property as the MessageStorageSchemaName you set inside IntegrateWithWolverine(o => ...) — there are two with that name, the ancillary integration reads only the runtime-level one, and setting the other looks like it worked because it does take effect for your own tables.

    What it means for you: the console's envelope tables are a second set, so a maintenance routine that resets or rebuilds only the main message store will not touch them. If you run such a routine, iterate every store rather than IWolverineRuntime.Storage:

    csharp
    foreach (var store in await host.GetRuntime().Stores.FindAllAsync())
    {
        await store.Admin.RebuildAsync();
    }

    A second set left unmanaged is the shape of a bug we have already shipped once, where stale envelopes in an unswept schema replayed a previous run's telemetry on every boot.

Getting started ​

Not yet released

Embedded mode ships in 1.1.

A complete worked example is critterwatch/Embedded.Sqlite in the CritterStackSamples repository — one ASP.NET Core app with its own documents and handler, the console embedded in it, and no broker, database server or containers.

Two calls on the host you already have — one to register, one to mount:

cs
builder.Host.UseWolverine(opts =>
{
    // ... your own Wolverine configuration ...

    // On PostgreSQL/Marten this takes an NpgsqlDataSource.
    // The Polecat (SQL Server) and Fisher (SQLite) overloads take a connection string.
    opts.AddCritterWatchEmbedded(postgresSource, schemaName: "critterwatch");
});

var app = builder.Build();

// Mounts the console's UI under /critterwatch. Your own routes are untouched.
app.UseCritterWatchEmbedded();

The registration call differs by store

On PostgreSQL/Marten it takes an NpgsqlDataSource. On SQL Server/Polecat and SQLite/Fisher it takes a connection string. AddCritterWatchEmbedded and UseCritterWatchEmbedded also live in different namespaces — CritterWatch.Services and CritterWatch.Services.Hosting.

The console registers its own store, routes its own handlers and endpoints to it, and serves its UI under the path you choose — /critterwatch by default.

Two things the mount deliberately does not do

It does not map health endpoints, and it does not serve the SPA at your application's root. The standalone console does both, because it owns its whole route table; a mounted console is a guest and yours stays yours. If your application already calls MapWolverineEndpoints(), pass mapWolverineEndpoints: false — that one call maps every discovered Wolverine HTTP chain, the console's included, so calling it twice double-maps every route.

Running without a broker: PostgreSQL queues ​

An embedded console needs no broker of its own — it monitors its own host over in-process local:// messaging. If your application also wants to avoid a broker for its own messaging, Wolverine's database-backed queues run in the PostgreSQL you are already using:

cs
builder.Host.UseWolverine(opts =>
{
    // Wolverine's database-backed queues, in the same PostgreSQL you already run.
    //
    // role: MessageStoreRole.Ancillary is REQUIRED. Without it the transport registers itself
    // as a second "Main" message store, collides with your own Marten IntegrateWithWolverine
    // store, and the application fails to start with:
    //
    //   InvalidWolverineStorageConfigurationException: There must be exactly one message store
    //   tagged as the 'main' store ... Found multiples: ...
    //
    // Ancillary says "this is my transport, not my node store", leaving your Marten
    // integration as Main.
    opts.UsePostgresqlPersistenceAndTransport(
            connectionString,
            transportSchema: "myapp_queues",
            role: MessageStoreRole.Ancillary)
        .AutoProvision();

    opts.AddCritterWatchEmbedded(postgresSource, schemaName: "critterwatch");
});

role: MessageStoreRole.Ancillary is required

Leave it out and your application fails to start:

InvalidWolverineStorageConfigurationException: There must be exactly one message store tagged as
the 'main' store, you may need to mark all but one message store as 'ancillary'. Found multiples: ...

A database-backed transport otherwise registers itself as a second Main message store, which collides with your own IntegrateWithWolverine() store. Ancillary tells Wolverine this is a transport, not a node store.

The console's own control channel stays in-process

With embedded mode the console's communicationUri is local://critterwatch-control whether or not you use database queues. Postgres queues are your transport; CritterWatch self-monitors in-process and does not put its telemetry on them.

Your host must not handle CritterWatch's message types ​

If your application declares a handler for one of CritterWatch's own messages — ServiceUpdates, for instance — the host refuses to start, naming the conflict.

That refusal is deliberate. Under Wolverine's default MultipleHandlerBehavior, a direct handler wins over CritterWatch's BatchMessagesOf<ServiceUpdates>() batch handler and silently shadows it. The shadowed handler is the telemetry-ingest batching path, so the cost is not a wrong answer but an ingest collapse — roughly 15 messages/minute against 110+/second inbound — with nothing logged. A startup failure you can read is strictly better, so CritterWatch promotes the warning to an error.

You are unlikely to hit this by accident: it requires handling a message type that belongs to the monitoring tool.

Supported configurations ​

Your hostStoreSupported
Single instanceMarten (PostgreSQL)✅
Single instancePolecat (SQL Server)✅
Single instanceFisher (SQLite)✅ — no database service needed at all
ClusteredMarten / Polecat⏭️ planned; needs a transport (see below)
ClusteredFisher (SQLite)⛔ not supported

Fisher/SQLite is single-instance only ​

This is a hard limit, not a gap we intend to close. SQLite has no queue transport for Wolverine, so a second node's telemetry has nothing to travel over, and Fisher's async daemon runs in Solo mode by design. A Fisher-backed embedded console on a clustered host refuses to start rather than run without the guarantee it claims — you get an error naming the limitation, not a console that quietly loses data.

If you want an embedded console on a clustered host, use Marten or Polecat.

Clustering needs a transport — but a database queue is enough ​

When embedded clustering arrives, it will need a real transport between nodes: if only one node ingests telemetry, the other nodes' telemetry has to reach it, and an in-memory queue only reaches its own process.

That transport can be a database queue. Wolverine ships a partitioned database-queue topology for both PostgreSQL and SQL Server, where each partition slot is its own queue table. So clustered embedded does not imply RabbitMQ or Azure Service Bus — the database you are already running is enough.

How your handlers stay yours ​

CritterWatch routes its own handlers and HTTP endpoints to its store, selected by assembly. Your application's handlers are left completely alone: they keep the store, session and transaction they already had, and nothing about their generated code changes.

There are no attributes to add anywhere — not on your code, and not on CritterWatch's.

Limitations, stated plainly ​

  • Self-monitoring only. An embedded console watches the host it lives in. It does not ingest telemetry from other services.
  • Single instance for the 1.1 release, on every store.
  • Your host owns the runtime. The console shares your application's Wolverine node, listeners and message store. If you need the console isolated from your application's failures, run it standalone.

See also ​

Free for read-only monitoring. A commercial license is required for administrative actions and the MCP server.