CosmoLogs.Sinks.Seq 0.1.2-dev

This is a prerelease version of CosmoLogs.Sinks.Seq.
There is a newer version of this package available.
See the version list below for details.
dotnet add package CosmoLogs.Sinks.Seq --version 0.1.2-dev
                    
NuGet\Install-Package CosmoLogs.Sinks.Seq -Version 0.1.2-dev
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CosmoLogs.Sinks.Seq" Version="0.1.2-dev" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CosmoLogs.Sinks.Seq" Version="0.1.2-dev" />
                    
Directory.Packages.props
<PackageReference Include="CosmoLogs.Sinks.Seq" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CosmoLogs.Sinks.Seq --version 0.1.2-dev
                    
#r "nuget: CosmoLogs.Sinks.Seq, 0.1.2-dev"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CosmoLogs.Sinks.Seq@0.1.2-dev
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CosmoLogs.Sinks.Seq&version=0.1.2-dev&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=CosmoLogs.Sinks.Seq&version=0.1.2-dev&prerelease
                    
Install as a Cake Tool

CosmoLogs

ci

Fast, structured, leveled logging for .NET 10 — a C# port of Uber's Zap, layered on System.IO.Pipelines.

Status: alpha. Builds clean, 197/197 tests passing, benchmarks honest. API is stable enough to use; expect occasional breaking changes until 1.0.

Why CosmoLogs?

  • Zero-allocation hot path. logger.Info("msg", String("k", "v")) allocates 0 B/call on the typical structured-logging path. Verified by BenchmarkDotNet.
  • ~730× less allocation than Serilog in concurrent workloads (3 KB vs 2.15 MB across 32 threads × 100 logs).
  • Drop-in for ASP.NET Core / Microsoft.Extensions.Logging apps via the CosmoLogs.Extensions.Logging adapter.
  • Familiar Serilog ergonomics: LogContext (AsyncLocal scope), enrichers, namespace level overrides, CLEF (Compact JSON) for Seq/Datadog/Splunk, IConfiguration binding.
  • Real Seq integration out of the box — HTTP-batching sink with retry/backoff and configurable batch boundaries.

Solution layout

src/
├── CosmoLogs/                              core library
├── CosmoLogs.Extensions.Logging/           Microsoft.Extensions.Logging adapter
├── CosmoLogs.Extensions.Configuration/     IConfiguration binding (appsettings.json)
└── CosmoLogs.Sinks.Seq/                    Datalust Seq HTTP sink (CLEF)
tests/
├── CosmoLogs.Tests/                        178 unit tests (xUnit)
├── CosmoLogs.Extensions.Logging.Tests/     9 MEL bridge tests
└── CosmoLogs.Sinks.Seq.Tests/              10 Seq integration tests
benchmarks/
└── CosmoLogs.Benchmarks/                   38 BenchmarkDotNet pairs vs Serilog

Build / test / bench

# Build everything
dotnet build CosmoLogs.sln

# Run the full test suite (197 tests)
dotnet test CosmoLogs.sln

# Run benchmarks (uses BenchmarkDotNet — Release mode required)
dotnet run -c Release --project benchmarks/CosmoLogs.Benchmarks -- --filter '*'

# Single category
dotnet run -c Release --project benchmarks/CosmoLogs.Benchmarks -- --filter '*ThreeFields*'

Usage

Quick start

using CosmoLogs;
using static CosmoLogs.Fields;

// Production preset: JSON to stderr, InfoLevel and above, with sampling.
var logger = Loggers.NewProduction();

logger.Info("user logged in",
    String("userId", "alice"),
    Int("attempt", 3),
    Duration("elapsed", TimeSpan.FromMilliseconds(42)));

logger.Sync(); // flush before exit

Output (Compact JSON, one line per entry):

{"level":"info","ts":1712345678.123,"msg":"user logged in","userId":"alice","attempt":3,"elapsed":0.042}

Development mode (human-readable console)

var logger = Loggers.NewDevelopment();
logger.Info("starting up", String("port", "8080"));
2026-05-08T01:51:11.009+03:00  INFO  Program.cs:7  starting up  {"port": "8080"}

Structured fields (the typed API)

The fast path. Each constructor returns a Field struct — no boxing, no allocation:

logger.Info("hi",
    String("k1", "v"),                  // string
    Int("count", 42),                   // long
    Bool("retry", true),                // bool
    Float64("ratio", 0.95),             // double
    Duration("elapsed", TimeSpan.FromSeconds(1)),
    Time("at", DateTimeOffset.UtcNow),
    Binary("blob", new byte[] { 1, 2 }),
    Error(ex),                          // Exception → "error"+"errorVerbose" pair
    Stack("trace"));                    // captures the current stacktrace

For more than 4 fields, use *Many overloads with a ReadOnlySpan<Field>:

Field[] fs = [String("a","1"), String("b","2"), String("c","3"), String("d","4"), String("e","5")];
logger.InfoMany("five fields", fs);

Loose key/value (Sugar API)

When you don't want to write String(...) everywhere:

var sugar = logger.Sugar();
sugar.Infow("user logged in",
    "userId", "alice",
    "attempt", 3,
    "err", ex);                         // Exception detected, becomes "error" field

sugar.Infof("processed {0} records in {1}", count, elapsed);   // .NET-style format
sugar.Info("just", "a", "concatenated", "message");            // print-style

Child loggers

var requestLogger = logger
    .Named("http.api")
    .With(String("requestId", "r-42"), String("user", "alice"));

requestLogger.Info("processing");                    // includes requestId + user
requestLogger.Warn("retry", Int("attempt", 2));      // also includes them

Ambient context (LogContext)

For properties that flow through async/await without manually threading a logger:

using (LogContext.PushProperty("requestId", Guid.NewGuid()))
using (LogContext.PushProperty("userId", "alice"))
{
    await ProcessAsync();   // every log inside picks up requestId + userId
}

// In ProcessAsync:
async Task ProcessAsync()
{
    logger.Info("step 1");                    // includes requestId, userId
    await logger.Info("step 2 (after await)");
}

To wire up LogContext properties, add the enricher:

var logger = Loggers.NewProduction(
    Options.Enrich(Enrichers.FromLogContext, Enrichers.WithThreadId));

Microsoft.Extensions.Logging integration

// Program.cs
using CosmoLogs;
using CosmoLogs.Extensions.Logging;

var builder = WebApplication.CreateBuilder(args);

var cosmo = Loggers.NewProduction(
    Options.AddCaller(),
    Options.Enrich(Enrichers.FromLogContext));

builder.Logging.ClearProviders();
builder.Logging.AddCosmoLogs(cosmo);

var app = builder.Build();
app.MapGet("/", (ILogger<Program> log) => {
    log.LogInformation("hello {Name}", "world");
    return "ok";
});
app.Run();

Configure from appsettings.json

{
  "CosmoLogs": {
    "Level": "Info",
    "Encoding": "json",
    "OutputPaths": ["stdout"],
    "ErrorOutputPaths": ["stderr"],
    "InitialFields": { "service": "api", "env": "prod" },
    "Sampling": { "Initial": 100, "Thereafter": 100 },
    "MinimumLevelOverrides": {
      "Microsoft": "Warn",
      "Microsoft.AspNetCore.Hosting": "Warning",
      "System.Net.Http": "Warning"
    }
  }
}
using CosmoLogs.Extensions.Configuration;

var cfg = builder.Configuration;
var logCfg = CosmoLogsConfiguration.ReadFrom(cfg);
var nsLevels = CosmoLogsConfiguration.BuildNamespaceLevels(cfg);

var logger = logCfg.Build(
    nsLevels is null ? Options.AddCaller() : Options.WithNamespaceLevels(nsLevels));

builder.Logging.AddCosmoLogs(logger);

Send to Seq

using CosmoLogs.Sinks.Seq;

var logger = SeqExtensions.ForSeq(
    serverUrl: "http://localhost:5341",
    apiKey:    "MY-API-KEY",
    minLevel:  Level.Info,
    options:   Options.Enrich(Enrichers.WithMachineName));

The sink batches CLEF events, retries on 5xx with exponential backoff, drops the batch on 4xx, and exposes OnDropped for permanent-failure callbacks.

To use the URL-scheme registry (so Seq can be configured from appsettings.json):

SeqExtensions.RegisterSchemes();    // call once at startup
{
  "CosmoLogs": {
    "Encoding": "clef",
    "OutputPaths": ["seq+http://localhost:5341/?apiKey=MY-KEY&batchSize=500"]
  }
}

Rolling file sink

using CosmoLogs.Core;
using CosmoLogs.Sinks;

var sink = new RollingFileSink
{
    PathTemplate = "logs/app-{Date}.log",
    Interval = RollingInterval.Day,
    MaxFileSizeBytes = 50 * 1024 * 1024,   // 50 MB before size-rollover
    RetainedFileCountLimit = 30,
};

var enc = JsonEncoder.New(Config.NewProductionEncoderConfig());
var logger = Logger.New(CoreFactory.NewCore(enc, sink, Level.Info));

Sampling

Cap log volume on hot paths — log the first N entries for a given level+message in each tick, then 1 in M after that:

var inner = CoreFactory.NewCore(enc, sink, Level.Info);
var sampled = Sampler.NewSamplerWithOptions(inner,
    tick: TimeSpan.FromSeconds(1),
    first: 100,
    thereafter: 100,
    new SamplerOptions
    {
        Hook = (entry, decision) =>
            { if ((decision & SamplingDecision.Dropped) != 0) Metrics.LogsDropped++; }
    });
var logger = Logger.New(sampled);

Async (non-blocking) sink

For high-throughput workloads where the underlying sink (file, network, Seq) can't keep up:

var cfg = Config.NewProductionConfig();
cfg.AsyncSink = true;            // wrap output in AsyncWriteSyncer
cfg.AsyncSinkCapacity = 50_000;  // bounded queue; oldest drops on overflow
var logger = cfg.Build();

Producer threads do not block — writes go to a Channel<T> and a single drainer task hits the inner sink. Trade-off: events queued at process crash are lost.

Runtime level changes (HTTP endpoint)

var atomicLevel = AtomicLevel.At(Level.Info);
var logger = Logger.New(CoreFactory.NewCore(enc, sink, atomicLevel));

// Wire into ASP.NET Core (or any HTTP framework):
app.MapGet ("/log/level", () => LevelHttpHandler.Handle(atomicLevel, "GET"));
app.MapPut ("/log/level", async (HttpRequest req) => {
    var resp = LevelHttpHandler.Handle(
        atomicLevel,
        method: "PUT",
        contentType: req.ContentType,
        body: req.Body,
        queryLevel: req.Query["level"]);
    return Results.Bytes(resp.Body, resp.ContentType);
});

// curl http://localhost:8080/log/level                        → {"level":"info"}
// curl -X PUT http://localhost:8080/log/level?level=debug     → {"level":"debug"}

Test helpers (Observer)

For unit tests that want to assert on log entries without hitting disk:

using CosmoLogs.Test;

var (core, logs) = Observer.New(Level.Debug);
var logger = Logger.New(core);

logger.Info("hi", String("user", "alice"));

var entry = logs.All().Single();
Assert.Equal(Level.Info, entry.Entry.Level);
Assert.Equal("hi", entry.Entry.Message);
Assert.Equal("alice", entry.ContextMap()["user"]);

ObservedLogs exposes FilterLevelExact, FilterMessage, FilterMessageSnippet, FilterField, FilterFieldKey, FilterLoggerName, AllUntimed — same shape as zaptest.observer.


Performance

dotnet run -c Release --project benchmarks/CosmoLogs.Benchmarks -- --filter '*ThreeFields*'
Method Mean Allocated Ratio
Cosmo_ThreeFields 360 ns 0 B 1.00
Serilog_ThreeFields 391 ns 976 B 1.08

See benchmarks/README.md for the full picture (38 scenarios across 8 categories, including MEL bridge, sampler reject path, enricher pipeline, ambient scope, CLEF head-to-head, console encoder, and 32-thread concurrent throughput).

Architecture

The pipeline matches Zap's:

logger.Info(msg, fields)
   ↓
Logger.CheckCore(level, msg)
   ↓ — early-out if level < threshold
ICore.Check(entry, ce)         ← wrappers chain here (sampler, enricher, namespace levels, hooks)
   ↓
CheckedEntry.Write(fields)     ← dispatches to all registered cores; returns to pool
   ↓
IOCore.Write(ent, fields)
   ↓
IEncoder.EncodeEntry(ent, fields)  ← JsonEncoder / ConsoleEncoder / CompactJsonEncoder
   ↓ produces a pooled Buffer
IWriteSyncer.Write(buffer)     ← Stream / PipeWriter / Async / Buffered / Multi / Discard

Every box is replaceable: implement ICore, IEncoder, or IWriteSyncer and plug it in.

Releasing

Tag a commit with v* and push. The .github/workflows/release.yml workflow will:

  1. Build & test the whole solution at the tagged version.
  2. dotnet pack all four library projects (with symbol packages and SourceLink).
  3. Push them to nuget.org using the NUGET_API_KEY repo secret.
  4. Create a GitHub release with the .nupkg files attached and auto-generated notes.
# release 0.1.0
git tag v0.1.0
git push origin v0.1.0

Manual run (without a tag) is also supported via the Actions → release → Run workflow UI; type the version into the input field.

The NUGET_API_KEY secret needs to be set once in Settings → Secrets and variables → Actions with a key scoped to push these four package IDs.

License

MIT — see LICENSE. Original Zap copyright © Uber Technologies preserved.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.2 133 5/24/2026
1.1.1 95 5/23/2026
1.1.0 104 5/23/2026
1.0.1 106 5/21/2026
1.0.0 124 5/8/2026
0.1.2-dev 108 5/8/2026
0.1.1 112 5/8/2026
0.1.0 98 5/8/2026