KomoriLive.Bonsai 0.0.5

dotnet add package KomoriLive.Bonsai --version 0.0.5
                    
NuGet\Install-Package KomoriLive.Bonsai -Version 0.0.5
                    
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="KomoriLive.Bonsai" Version="0.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="KomoriLive.Bonsai" Version="0.0.5" />
                    
Directory.Packages.props
<PackageReference Include="KomoriLive.Bonsai" />
                    
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 KomoriLive.Bonsai --version 0.0.5
                    
#r "nuget: KomoriLive.Bonsai, 0.0.5"
                    
#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 KomoriLive.Bonsai@0.0.5
                    
#: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=KomoriLive.Bonsai&version=0.0.5
                    
Install as a Cake Addin
#tool nuget:?package=KomoriLive.Bonsai&version=0.0.5
                    
Install as a Cake Tool

Komori Bonsai C# SDK

A simple yet powerful logger for sending structured logs to Komori Live (the official cloud platform for Bonsai) or to your own self-hosted Bonsai instance. This SDK is designed for both server-side (.NET API) and client-side (Blazor, MAUI) applications, with a strong focus on type safety, security, and ease of use.

✨ Features

  • Type-Safe Structured Logging: Send rich, strongly-typed log messages with generic payloads for compile-time safety.
  • Efficient Log Batching: Automatically queue logs and send them in batches to reduce network overhead and improve performance.
  • Flexible Authentication: Supports both long-lived API keys and short-lived JWTs with automatic auth scheme detection.
  • ILogger Integration: Plugs directly into the standard Microsoft.Extensions.Logging framework.
  • Resilient Delivery: Automatically retries failed requests using exponential backoff with jitter.
  • Secure by Design: Provides a clear pattern for protecting API keys in client-side applications via a TokenProvider.
  • Asynchronous: All logging operations are non-blocking.
  • Interface-Based & DI-Friendly: Easily mockable for unit testing and integrates seamlessly with dependency injection.

📦 Installation

Install the package from NuGet:

dotnet add package KomoriLive.Bonsai

🚀 Basic Usage (Without Dependency Injection)

For simple applications or scripts, you can instantiate the logger directly. Note that the logger is generic, requiring you to specify the payload type. Use object for mixed or simple payloads.

using KomoriLive.Bonsai;

// The client should be a singleton in your application
var client = new BonsaiClient(new BonsaiLoggerOptions {
    ProjectId = "your-project-id",
    ApiKey = "your-long-lived-api-key",
    Source = "my-console-app"
});

// Create a logger instance
var logger = new BonsaiLogger<object>(client);
logger.SetSource("my-console-app"); // Manually set the source

// Use the fluent extension methods for cleaner logging
await logger.InfoAsync(
    "User signed up",
    new { UserId = "usr_1234", Plan = "premium" }
);

⚙️ ASP.NET Core Integration

For ASP.NET Core applications, the recommended approach is to register Bonsai with the built-in dependency injection container. This allows you to inject loggers anywhere in your application.

1. Configuration

In your Program.cs, add the Bonsai services.

using KomoriLive.Bonsai;

var builder = WebApplication.CreateBuilder(args);

// Add the Bonsai logging framework.
builder.Services.AddLogging(logging =>
{
    // Clear other providers if you only want to log to Bonsai.
    logging.ClearProviders();

    // Configure Bonsai using the new extension method.
    logging.AddBonsai(options =>
    {
        // You can bind from IConfiguration...
        builder.Configuration.GetSection("Bonsai").Bind(options);

        // ...or set properties directly.
        // options.Source = "MyWebApp";
    });
});

var app = builder.Build();

// ...

In appsettings.json:

{
  "Bonsai": {
    "ProjectId": "your-project-id",
    "ApiKey": "your-long-lived-api-key",
    "Source": "MyWebApp"
  }
}

2. Usage Scenarios

You now have two ways to log, depending on your needs.

Scenario A: Standard Logging with ILogger<T>

This is the standard Microsoft approach. It's great for general-purpose logging throughout your application. The payload sent to Bonsai will automatically include request state and exception details.

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

    public MyStandardService(ILogger<MyStandardService> logger)
    {
        _logger = logger;
    }

    public void DoWork()
    {
        // The structured data {UserId} is automatically included in the payload.
        _logger.LogInformation("Doing important work for user {UserId}", "usr_1234");
    }
}
Scenario B: Direct, Type-Safe Logging with IBonsaiLogger<TPayload>

This approach provides full compile-time safety for your log payloads, which is ideal for critical business events that drive analytics or dashboards. By adding using KomoriLive.Bonsai;, you gain access to fluent extension methods (.InfoAsync(), .ErrorAsync(), etc.) that simplify logging calls.

First, define your payload structure:

// Define a record for your structured payload
public record UserSignedUpPayload(string UserId, string Plan, string? Referrer);

Then, inject IBonsaiLogger<TPayload> into your service and use it. The logger source will automatically be set to the consuming class name (MyTypedLoggingService).

public class MyTypedLoggingService
{
    private readonly IBonsaiLogger<UserSignedUpPayload> _logger;

    public MyTypedLoggingService(IBonsaiLogger<UserSignedUpPayload> logger)
    {
        _logger = logger;
    }

    public async Task SignUpUser()
    {
        var payload = new UserSignedUpPayload(
            UserId: "usr_5678",
            Plan: "enterprise",
            Referrer: "google"
        );

        // The payload is strongly typed, and the fluent methods make logging easy.
        await _logger.InfoAsync("User signed up", payload);
    }

    public async Task HandleError()
    {
        var payload = new UserSignedUpPayload("usr_5678", "enterprise", null);
        try
        {
            throw new InvalidOperationException("Something failed!");
        }
        catch (Exception ex)
        {
            // The exception-aware overloads are great for error handling.
            await _logger.ErrorAsync(ex, "Failed to sign up user", payload);
        }
    }
}
Overriding the Log Source

When you inject IBonsaiLogger<T>, the SDK automatically sets the log Source to the name of the class it's injected into (e.g., MyTypedLoggingService). This is usually what you want.

However, for advanced cases, you can override this source by using the LogAsync overload that accepts a BonsaiLogMessage<T> object. This allows you to log from a shared service but attribute the log to a different part of your application.

public class CentralizedEventService
{
    private readonly IBonsaiLogger<object> _logger;

    public CentralizedEventService(IBonsaiLogger<object> logger)
    {
        _logger = logger;
    }

    public async Task LogPaymentEvent(string message, object payload)
    {
        var logEntry = new BonsaiLogMessage<object>
        {
            Message = message,
            Level = BonsaiLogLevel.Info,
            Payload = payload,
            Source = "PaymentGateway" // Override the source
        };

        await _logger.LogAsync(logEntry);
    }
}

⚡ High-Performance Logging with Batching

For applications that generate a high volume of logs, such as a busy web server or a data processing worker, sending each log as a separate HTTP request can be inefficient. The SDK provides an optional log batching feature to address this.

When enabled, the logger adds logs to an in-memory queue. A background worker then flushes this queue periodically, sending multiple logs in a single request to a dedicated batching endpoint (/api/logs/batch). This significantly reduces network overhead and can improve the performance of both your application and the logging service.

Enabling Batching

To enable batching, set EnableBatching to true in your BonsaiLoggerOptions. You can also customize the BatchSize and FlushInterval.

// In Program.cs
builder.Services.AddLogging(logging =>
{
    logging.ClearProviders();
    logging.AddBonsai(options =>
    {
        builder.Configuration.GetSection("Bonsai").Bind(options);
        
        // Enable and configure batching
        options.EnableBatching = true;
        options.BatchSize = 200; // Default: 100
        options.FlushInterval = TimeSpan.FromSeconds(10); // Default: 5 seconds
    });
});

Manual Flushing

The background worker automatically flushes the queue. However, in some cases, you might want to manually trigger a flush to ensure all buffered logs are sent immediately. This is especially important before your application shuts down to prevent data loss.

The IBonsaiLogger<T> interface provides a FlushAsync method for this purpose.

public class MyService
{
    private readonly IBonsaiLogger<MyService> _logger;

    public MyService(IBonsaiLogger<MyService> logger)
    {
        _logger = logger;
    }

    public async Task OnShutdown()
    {
        // Ensure all buffered logs are sent before the application exits.
        await _logger.FlushAsync();
    }
}

When using the standard ILogger provider, the DisposeAsync method on the BonsaiClient will automatically flush any remaining logs when the application host is gracefully shut down.

🛡️ Secure Client-Side Logging (Blazor, MAUI)

Never expose your long-lived API key in a client-side application. The correct pattern is to use a TokenProvider that fetches short-lived JWTs from a secure backend API.

The TokenProvider Pattern

  1. Backend API: Create an endpoint on your secure backend that uses your long-lived API key to vend a short-lived JWT. The SDK provides services to help with this.
  2. Client Application: Configure the logger with a TokenProvider function that calls your backend endpoint to get the token. The SDK handles the rest, including token renewal.

Example: Blazor WASM Client and ASP.NET Core Backend

1. In your Server Project (MyProject.Server):

First, set up the backend endpoint to vend the tokens. Your server holds the secret API key.

// In the server project's Program.cs
using KomoriLive.Bonsai;

var builder = WebApplication.CreateBuilder(args);
// ...

// A. Configure Bonsai logging as usual for the server
builder.Services.AddLogging(logging =>
{
    logging.ClearProviders();
    logging.AddBonsai(options => builder.Configuration.GetSection("Bonsai").Bind(options));
});

// B. Add the dedicated token service
builder.Services.AddBonsaiTokenService();

var app = builder.Build();
// ...
// In a controller on the server, e.g., LoggingController.cs
using KomoriLive.Bonsai;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/logging")]
public class LoggingController : ControllerBase
{
    private readonly IBonsaiTokenService _tokenService;

    public LoggingController(IBonsaiTokenService tokenService)
    {
        _tokenService = tokenService;
    }

    [HttpGet("token")]
    public async Task<IActionResult> GetToken()
    {
        try
        {
            var token = await _tokenService.GetTokenAsync();
            return Ok(new { Token = token });
        }
        catch (InvalidOperationException ex)
        {
            // This can happen if the server is configured with a TokenProvider
            // instead of an API key. Return a meaningful error.
            return BadRequest(new { Message = ex.Message });
        }
    }
}

Your server's appsettings.json needs the long-lived API key:

{
  "Bonsai": {
    "ProjectId": "your-project-id",
    "ApiKey": "your-LONG-LIVED-api-key",
    "Source": "MyWebApp-Server"
  }
}

2. In your Client Project (MyProject.Client):

Configure the logger in your Program.cs to use the TokenProvider.

// In the client project's Program.cs
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Logging;
using KomoriLive.Bonsai;
using System.Net.Http.Json;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
// ... other services

// Add a typed HttpClient for your backend API
builder.Services.AddHttpClient("BackendApi", client =>
{
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});

// Configure logging
builder.Services.AddLogging(logging =>
{
    logging.ClearProviders();
    logging.AddBonsai(options =>
    {
        var sp = builder.Services.BuildServiceProvider();
        var httpFactory = sp.GetRequiredService<IHttpClientFactory>();

        options.ProjectId = "your-project-id";
        options.Source = "blazor-client-app";

        // The TokenProvider tells the SDK how to get a token.
        // The SDK will call this function automatically.
        options.TokenProvider = async () =>
        {
            var client = httpFactory.CreateClient("BackendApi");
            var response = await client.GetFromJsonAsync<BonsaiLoggingTokenResponse>("api/logging/token");
            return response?.Token;
        };
    });
});

await builder.Build().RunAsync();

Now you can inject ILogger<MyComponent> into any Blazor component and use it securely.

🔧 Configuration (BonsaiLoggerOptions)

Property Type Required Description Default
ProjectId string Yes Your Bonsai project ID. -
ApiKey string (*) Your project's long-lived API key or a short-lived JWT. -
TokenProvider Func<Task<string>> (*) An async function that returns a JWT. The SDK will manage the token lifecycle for you. -
Source string No A string identifying the source of the logs (e.g., 'WebApp', 'Worker'). "bonsai-dotnet"
Endpoint string No The base URL of the Bonsai logging endpoint. "https://komori.live"
OnError Action<Exception> No A callback action to execute if an error occurs during logging. null
RetryCount int No The number of times to retry sending a log if the request fails. 2
RetryDelay TimeSpan No The delay between retry attempts. 5 seconds
Timeout TimeSpan No The timeout for HTTP requests to the Bonsai endpoint. 10 seconds
EnableBatching bool No Enables or disables log batching. false
BatchSize int No The maximum number of logs to include in a single batch. Only used when EnableBatching is true. 100
FlushInterval TimeSpan No The maximum time to wait before sending a batch. Only used when EnableBatching is true. 5 seconds

(*): You must provide either an ApiKey or a TokenProvider, but not both.

For simple logs without a payload, you can omit the payload argument:

_logger.LogInformation("Component initialized");

The IBonsaiLogger<T> also provides fluent logging extensions for direct, type-safe logging:

// Requires `using KomoriLive.Bonsai;`
await _bonsaiLogger.InfoAsync("Component initialized");
Product 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. 
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
0.0.5 194 7/13/2025
0.0.4 185 7/1/2025
0.0.3 191 7/1/2025
0.0.2 149 6/29/2025
0.0.1 235 5/2/2025