LittyLogs.File

file sink for litty-logs. yeets litty-fied logs to disk with rotation and gzip compression. text or JSON output, your choice bestie. emojis included no cap ๐Ÿ”ฅ


Keywords
emoji, file, gen-alpha, gzip, litty, logging, rotation, sink, structured
License
MIT
Install
Install-Package LittyLogs.File -Version 2.0.0

Documentation

litty-logs ๐Ÿ”ฅ

yo your .NET logs are giving corporate dystopia energy rn and thats not it bestie. litty-logs fully rewrites all them boring built-in framework messages into gen alpha slang while also blessing your terminal with emojis and ANSI colors no cap

before (deadass boring) ๐Ÿ’€

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app

after (absolutely bussin) ๐Ÿ”ฅ

[๐Ÿ”ฅ info] [2026-02-18T21:45:00.420Z] [Lifetime] we vibing on http://localhost:5000 fr fr ๐ŸŽง
[๐Ÿ”ฅ info] [2026-02-18T21:45:00.421Z] [Lifetime] app is bussin and ready to slay bestie ๐Ÿ’… yeet Ctrl+C to dip out no cap
[๐Ÿ”ฅ info] [2026-02-18T21:45:00.421Z] [Lifetime] content root living at /app bestie ๐Ÿ“

installation

dotnet add package LittyLogs

# for xUnit v3 test output (optional, separate package)
dotnet add package LittyLogs.Xunit

# for file sink with rotation and gzip compression (optional, separate package)
dotnet add package LittyLogs.File

# for Slack + Matrix webhook sinks (optional, separate package) ๐Ÿ”ฅ
dotnet add package LittyLogs.Webhooks

# for the CLI tool that litty-fies build, test, publish, pack, and clean output
dotnet tool install --global LittyLogs.Tool

usage โ€” one line thats it thats the tweet

web api

using LittyLogs;

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddLittyLogs(); // thats it bestie ๐Ÿ”ฅ
var app = builder.Build();
app.Run();

hosted service / background worker

using LittyLogs;

var host = Host.CreateDefaultBuilder(args)
    .ConfigureLogging(logging => logging.AddLittyLogs())
    .ConfigureServices(services => services.AddHostedService<MyService>())
    .Build();

await host.RunAsync();

console script (the ten-liner speedrun)

using LittyLogs;
using Microsoft.Extensions.Logging;

using var factory = LoggerFactory.Create(logging =>
{
    logging.SetMinimumLevel(LogLevel.Trace);
    logging.AddLittyLogs();
});

var logger = factory.CreateLogger("MyScript");
logger.LogInformation("we in here bestie ๐Ÿ”ฅ");

xUnit v3 tests

using LittyLogs.Xunit;
using Xunit;

public class MyTests
{
    private readonly ILogger<MyTests> _logger;

    public MyTests(ITestOutputHelper output)
    {
        // one line to litty-fy your test output bestie ๐Ÿ’…
        _logger = output.CreateLittyLogger<MyTests>();
    }

    [Fact]
    public void MyTest()
    {
        _logger.LogInformation("this shows up litty-fied in test output ๐Ÿ”ฅ");
    }
}

JSON structured logging โ€” for when machines need to eat too ๐Ÿฝ๏ธ

same litty rewrites and emojis, but as valid JSON. your log aggregator is gonna love this no cap

using LittyLogs;

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddLittyJsonLogs(); // structured JSON with emojis bestie ๐Ÿ”ฅ
var app = builder.Build();
app.Run();

output:

{"timestamp":"2026-02-19T10:45:00.420Z","level":"info","emoji":"๐Ÿ”ฅ","category":"Lifetime","message":"app is bussin and ready to slay bestie ๐Ÿ’… yeet Ctrl+C to dip out no cap"}

emojis in JSON? absolutely bussin โ€” JSON is UTF-8 native so every parser on earth handles it perfectly ๐Ÿ†

file sink โ€” yeet logs to disk with rotation and compression ๐Ÿ“

using LittyLogs.File;

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddLittyFileLogs(opts =>
{
    opts.FilePath = "logs/app.log";
    opts.OutputFormat = LittyFileOutputFormat.Text;  // or Json for structured output
    opts.RollingInterval = LittyRollingInterval.Daily;
    opts.MaxFileSizeBytes = 10 * 1024 * 1024;        // 10MB then rotate
    opts.CompressionMode = LittyCompressionMode.Gzip; // compress rotated files ๐Ÿ—œ๏ธ
});
var app = builder.Build();
app.Run();

features that go hard:

  • async I/O โ€” Channel<string> based, your app thread never blocks on disk writes ๐Ÿ‘‘
  • text or JSON โ€” human-readable or machine-parseable, your choice bestie
  • size + time rotation โ€” daily, hourly, or size-based. rotated files get timestamps in the name
  • gzip compression โ€” old rotated files auto-compress to .gz, active file stays uncompressed
  • startup safeguard โ€” never auto-rotates on startup, only rotates before writing the next entry ๐Ÿ”’
  • no ANSI codes โ€” files never get terminal escape chars, thats cursed ๐Ÿ’€

webhook sink โ€” yeet logs to Slack and Matrix chat ๐Ÿช๐Ÿ”ฅ

critical error hits? your group chat knows about it instantly, formatted all nice with emojis

using LittyLogs.Webhooks;

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddLittyMatrixLogs("https://hookshot.example.com/webhook/abc123"); // Matrix one-liner ๐ŸŸฃ๐Ÿ”ฅ
builder.Logging.AddLittySlackLogs("https://hooks.slack.com/services/your/secret/path"); // Slack one-liner ๐ŸŸข๐Ÿ”ฅ
var app = builder.Build();
app.Run();

with full options:

builder.Logging.AddLittySlackLogs("https://hooks.slack.com/services/your/secret/path", opts =>
{
    opts.MinimumLevel = LogLevel.Warning;   // only Warning+ goes to chat (default)
    opts.Username = "Deploy Alerts";        // Slack message header, not the app identity
    opts.BatchSize = 10;                    // 49 max so header + logs stay under 50 blocks
    opts.BatchInterval = TimeSpan.FromSeconds(2); // flush interval
});

features that go hard:

  • async batching โ€” Channel<T> based, groups messages by interval (2s) or count (10), your app thread never blocks ๐Ÿ‘‘
  • Polly resilience โ€” retry with exponential backoff, circuit breaker, per-request timeout via Microsoft.Extensions.Http.Resilience ๐Ÿ”’
  • best-effort โ€” if the webhook is bricked after retries, we drop the batch and keep vibing. never crashes your app no cap
  • min level filtering โ€” default Warning so your chat dont get spammed with trace logs ๐Ÿ’€
  • IHttpClientFactory โ€” proper socket management, named client "LittyWebhooks" for custom config
  • Matrix hookshot format โ€” HTML-escaped output plus a text fallback, with exceptions in code blocks ๐ŸŸฃ๐Ÿ”ฅ
  • Slack Block Kit โ€” one plain-text header + one plain-text section per log, with mrkdwn disabled so mentions, links, and formatting stay literal ๐ŸŸข๐Ÿ”’๐Ÿ”ฅ
  • Slack limits handled โ€” headers stop at 150 Unicode scalars, sections at 3000, and batches at 49 logs + one header ๐Ÿ”’๐Ÿ”ฅ
  • generic webhook config โ€” AddLittyWebhookLogs() still lets advanced besties select WebhookPlatform.Matrix or WebhookPlatform.Slack directly ๐Ÿช๐Ÿ”ฅ

setting up a test channel? follow the Slack incoming webhook setup for the exact app settings, .env config, hermetic tests, and opt-in live smoke test ๐ŸŸข๐Ÿ”ฅ

what gets litty-fied

all the boring framework messages you see every dotnet run:

boring version ๐Ÿ’€ litty version ๐Ÿ”ฅ
Application started. Press Ctrl+C to shut down. app is bussin and ready to slay bestie ๐Ÿ’… yeet Ctrl+C to dip out no cap
Now listening on: {url} we vibing on {url} fr fr ๐ŸŽง
Content root path: {path} content root living at {path} bestie ๐Ÿ“
Hosting environment: {env} we in our {env} era rn โœจ
Application is shutting down... app said aight imma head out ๐Ÿ’€
Request starting {details} yo a request just slid in: {details} ๐Ÿ‘€
Request finished {details} request finished cooking: {details} ๐Ÿณ

plus hosting lifecycle, endpoint routing, and more fr fr

log level emojis

level emoji vibe
Trace ๐Ÿ‘€ lowkey peeking
Debug ๐Ÿ” investigating bestie
Information ๐Ÿ”ฅ bussin as usual
Warning ๐Ÿ˜ค not it
Error ๐Ÿ’€ big L
Critical โ˜ ๏ธ its giving death

options โ€” configure the vibe

builder.Logging.AddLittyLogs(options =>
{
    options.RewriteMessages = true;     // rewrite framework messages (default: true, thats the whole point)
    options.UseColors = true;           // ANSI colors (default: true)
    options.ShortenCategories = true;   // yeet namespace bloat (default: true)
    options.UseUtcTimestamp = true;     // UTC timestamps (default: true, international rizz)
    options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffK"; // ISO 8601 with milliseconds (default)
    options.TimestampFirst = false;     // false = RFC 5424 (level first), true = observability style (timestamp first)
});

dotnet litty CLI tool โ€” litty-fy your build, test, publish, pack, and clean output ๐Ÿงช

your app logs are litty but dotnet build, dotnet test, dotnet publish, dotnet pack, and dotnet clean output is still giving corporate energy? install the tool and never look at boring terminal output again no cap

# install the tool
dotnet tool install --global LittyLogs.Tool

# litty-fy your test output (auto-shows ITestOutputHelper output too)
dotnet litty test

# litty-fy your build output
dotnet litty build

# litty-fy your publish output
dotnet litty publish

# litty-fy your pack output โ€” nupkgs go brrr ๐Ÿ“ฆ
dotnet litty pack

# litty-fy your clean output โ€” watch artifacts get yeeted in style ๐Ÿ—‘๏ธ
dotnet litty clean

# all args pass through to the underlying dotnet command
dotnet litty test --filter "FullyQualifiedName~MyTests"
dotnet litty build -c Release
dotnet litty publish -c Release --self-contained
dotnet litty pack -c Release
dotnet litty clean -c Release

before (boring test output) ๐Ÿ’€

Passed!  - Failed:     0, Passed:    66, Skipped:     0, Total:    66, Duration: 80 ms - LittyLogs.Tests.dll (net10.0)

after (bussin test output) ๐Ÿ”ฅ

  [xUnit.net] scouting for tests in LittyLogs.Tests ๐Ÿ”
  [xUnit.net] found the squad in LittyLogs.Tests ๐Ÿ“‹
  [xUnit.net] lets gooo LittyLogs.Tests is cooking ๐Ÿ”ฅ
  โœ… LittyLogs.Tests.MyTest.SomeTest [26 ms]
  [xUnit.net] LittyLogs.Tests absolutely ate no crumbs ๐Ÿ’…

  all tests ate and left no crumbs ๐Ÿ†
  total vibes checked: 66
  ate: 66 โœ…
  cooked in 0.5 Seconds โฑ๏ธ

your own logs stay untouched

litty-logs only rewrites known framework messages. your custom log messages pass through with the bussin formatting (emojis, colors, short categories) but the actual message text stays exactly how you wrote it no cap

logger.LogInformation("my custom message stays exactly like this");
// output: [๐Ÿ”ฅ info] [2026-02-18T21:45:00.420Z] [MyService] my custom message stays exactly like this

examples

seven example projects in examples/ so you can see litty-logs in every scenario:

example what it shows run it
WebApi startup demo with level-first โ†’ timestamp-first โ†’ JSON, then server runs just example web
HostedService startup demo with both timestamp modes, then background service vibes just example hosted
Console side-by-side text + JSON output comparison just example console
Xunit litty-fied xUnit test output with all log levels + TimestampFirst test just example xunit
Json structured JSON logging with both timestamp configs just example json
FileSink file sink with level-first โ†’ timestamp-first โ†’ JSON, reads em all back just example filesink
Webhooks dual webhook sink (Matrix + Slack) with mock listeners or live endpoints just example webhooks

every example auto-showcases ALL the modes when you run it โ€” no hidden flags, no secret handshakes. you run it, you see everything ๐Ÿ’…

the webhooks example runs three demos: Matrix-only, Slack-only, and dual mode. set HOOKSHOT_URL and/or SLACK_WEBHOOK_URL in .env to go live โ€” any missing sink falls back to a local mock listener so the demo always eats. see the Slack local setup guide before using a real webhook ๐Ÿช๐Ÿ”ฅ

development โ€” for the contributing besties ๐Ÿ› ๏ธ

just recipes

this project uses just as the task runner. here are the vibes:

recipe what it does
just build build the whole solution with litty-fied output ๐Ÿ—๏ธ๐Ÿ”ฅ
just test run all tests with litty-fied output ๐Ÿงช๐Ÿ”ฅ
just publish publish with litty-fied output ๐Ÿ“ค๐Ÿ”ฅ
just pack pack all five NuGet packages with litty-fied output ๐Ÿ“ฆ๐Ÿ”ฅ
just clean yeet all build artifacts with litty-fied output ๐Ÿ—‘๏ธ๐Ÿ”ฅ
just release-next read-only git-cliff preview of the next strict SemVer; never installs anything ๐Ÿ”๐Ÿ”ฅ
just release-notes read-only preview of the exact next changelog section ๐Ÿ“œ๐Ÿ”ฅ
just example <name> run an example โ€” web, hosted, console, xunit, json, filesink, webhooks ๐Ÿ”ฅ
just setup-completions install shell tab-completions for just example <tab>

shell completions

tab-complete just example <tab> to see all available examples. works with zsh and bash:

# auto-install to your shell rc file
just setup-completions

# or source manually
source completions/just.zsh   # zsh
source completions/just.bash  # bash

versioning

version lives in one place: Directory.Build.props. all five packages inherit from it. main is the only long-lived branch; every feature, fix, chore, Renovate update, and rolling Release PR goes straight into it through a tiny squash PR ๐ŸŒณ๐Ÿ”ฅ

trunk-based flow ๐ŸŒณ

# start every change from fresh main
git switch main
git pull --ff-only origin main
git switch -c feature/my-bussin-change

# cook, run the just checks, push, and PR straight into main
just build --configuration Release
just test --configuration Release --no-build
just pack --no-build --output ./nupkgs

keep branches tiny and auto-delete them after merge. main stays green and always represents the next shippable state; release math reads tags + squash commits, so deleted source branches are fully irrelevant no cap ๐Ÿ”ฅ

release flow ๐Ÿš€

every merged releasable squash wakes release-pr.yml. the newest checksum-verified stable git-cliff 2.x reads commits since the latest immutable v* tag, updates the single release-pr branch, and opens or refreshes one chore(release): vX.Y.Z ๐Ÿ”ฅ PR. merge that PR and automation tags that exact main commit once; release.yml then builds, tests, packs, and ships it ๐Ÿ”ฅ

strict SemVer rules are intentionally simple:

  • feat(scope)!: or any conventional breaking marker โ†’ major, even before 1.0 ๐Ÿ’ฅ๐Ÿ”ฅ
  • feat: โ†’ minor โœจ๐Ÿ”ฅ
  • fix:, perf:, revert:, and chore(deps): โ†’ patch ๐Ÿ›๐Ÿค–๐Ÿ”ฅ
  • generic chores, docs, CI, tests, style, build, and refactors โ†’ no release noise ๐Ÿ”๐Ÿ”ฅ

Forgejo manual dispatch supports auto, patch, minor, major, and promote. promote turns a prerelease like 1.0.0-rc.1 into 1.0.0 while copying its notes. manual dispatch only creates the PR; it never tags a side commit ๐Ÿ”’๐Ÿ”ฅ

if a future release uses an RC, automatic bumps pause while that prerelease is current instead of looping into rc.2; explicit promote is the stable gate and includes any emergency conventional commits that landed during the freeze ๐Ÿ”’๐Ÿ”ฅ

urgent fix flow ๐Ÿš‘

# urgent fixes are still tiny trunk PRs โ€” just move faster, not weirder
git switch main
git pull --ff-only origin main
git switch -c fix/production-is-cooked

# fix it, run the same checks, and PR into main
# after merge, the rolling Release PR updates automatically

there is no special hotfix branch type and no merge-back. the fix lands on main, so the trunk and production history can never drift bestie ๐ŸŒณ๐Ÿ”ฅ

CI/CD โ€” triple release pipeline ๐Ÿš€

forgejo actions on a self-hosted runner handles the whole squad:

  • CI (ci.yml) โ€” validates the conventional squash title, release policy, build, tests, and packages on every PR to main ๐Ÿ”ฅ
  • Renovate (renovate.json5) โ€” keeps NuGet packages and Forgejo Actions fresh on main. non-major updates auto-merge after green CI; major updates stay regular PRs for human review no cap ๐Ÿค–๐Ÿ”ฅ
  • Release PR (release-pr.yml) โ€” calculates strict SemVer with the newest stable git-cliff 2.x, maintains one rolling PR, then creates one immutable tag after merge ๐Ÿง ๐Ÿ”’๐Ÿ”ฅ
  • Release (release.yml) โ€” triggered by v* tags. the full pipeline hits THREE destinations:
    1. nuget.org โ€” all five .nupkg files with --skip-duplicate so retries dont catch Ls
    2. forgejo releases โ€” via Gitea API with .nupkg assets attached ๐Ÿ 
    3. github mirror releases โ€” via gh CLI with .nupkg assets on the mirror repo ๐Ÿ™

pipeline features that go hard:

  • fully retryable โ€” rerunning the same tag is safe; an existing tag at the same commit is a no-op, while moving or deleting a shipped tag is never part of the flow ๐Ÿ”„๐Ÿ”’๐Ÿ”ฅ
  • pre-release auto-detection โ€” versions with - (like 0.1.0-dev, 1.0.0-beta.1) auto-flag as pre-release on both platforms ๐Ÿงช
  • changelog extraction โ€” release notes auto-pulled from CHANGELOG.md for that professional rizz ๐Ÿ“œ
  • version sanity check โ€” tag must match Directory.Build.props or the pipeline tells you its not it ๐Ÿ’€

see docs/runner-setup.md for runner setup and required secrets no cap

manifesting these features ๐Ÿง โœจ

stuff that would go absolutely crazy but aint started yet. vibes only rn no cap

  • ๐ŸŸฃ Matrix Client-Server API โ€” direct room messages for power users who want full HTML control instead of hookshot
  • ๐ŸŽจ custom webhook templates โ€” user-defined message format strings so you can make it look however you want
  • ๐Ÿ—œ๏ธ zstd compression โ€” for file sink rotation (gzip is cool but zstd is faster and smaller fr fr)
  • ๐Ÿ“Š structured log enrichment โ€” auto-attach machine name, environment, correlation IDs to webhook messages

wanna see one of these happen? PRs are open bestie, or just vibe in the issues ๐Ÿ’…

security ๐Ÿ”’

litty-logs takes security seriously even though we dont take ourselves seriously no cap. heres the tldr:

  • webhook URL validation โ€” SSRF prevention, only http/https schemes allowed
  • log injection prevention โ€” newlines in messages get sanitized to spaces in text output
  • content injection prevention โ€” Matrix gets escaped HTML; Slack gets plain-text Block Kit with markdown disabled, so tracking pixels, surprise mentions, and phishing links stay literal ๐Ÿ”’๐Ÿ”ฅ
  • HTTP category filtering โ€” prevents infinite recursion AND accidental webhook URL token exposure

full details in docs/security.md

found a vulnerability? dont yeet it in a public issue โ€” open a security advisory instead bestie ๐Ÿ”’

license

MIT โ€” share the vibes bestie โœŒ๏ธ