Pular para o conteúdo principal
ImmutableLog logo
VoltarC# / .NET

C# / .NET

Guia completo de integração com o ImmutableLog em C# / .NET. Middleware automático para ASP.NET Core e Minimal API — ou um cliente HttpClient tipado para workers, BackgroundServices e jobs. O envio é assíncrono e não bloqueia a resposta.

Cliente HttpClient tipado

Use o ImmutableLogClient para enviar eventos diretamente ao ImmutableLog sem depender do pipeline web. Ideal para workers, BackgroundService, consumidores de fila e qualquer código .NET que precise registrar eventos.

csharp
// ImmutableLogClient.cs
using System.Net.Http.Json;
using System.Text.Json;

public sealed class ImmutableLogClient
{
    private readonly HttpClient _http;
    private readonly string _apiKey;
    private readonly string _service;
    private readonly string _env;

    public ImmutableLogClient(HttpClient http, string apiKey, string service, string env)
    {
        _http = http;
        _apiKey = apiKey;
        _service = service;
        _env = env;
    }

    public async Task SendEventAsync(
        string eventName,
        string kind,                         // "success" | "info" | "error"
        object payload,
        string? immutableTrail = null,
        CancellationToken ct = default)
    {
        var requestId = Guid.NewGuid().ToString();
        var trail = SanitizeTrail(immutableTrail);

        // Todos os valores de meta precisam ser strings.
        var meta = new Dictionary<string, string>
        {
            ["type"] = kind,
            ["event_name"] = eventName,
            ["service"] = _service,
            ["request_id"] = requestId,
            ["env"] = _env,
        };
        if (trail is not null) meta["immutable_trail"] = trail;

        var body = new
        {
            payload = JsonSerializer.Serialize(payload),
            meta,
        };

        using var req = new HttpRequestMessage(HttpMethod.Post, "/v1/events")
        {
            Content = JsonContent.Create(body),
        };
        req.Headers.Add("Authorization", $"Bearer {_apiKey}");
        // Idempotency-Key e OBRIGATORIO (sem ele a API responde 400).
        req.Headers.Add("Idempotency-Key", $"{eventName}-{requestId}");
        req.Headers.Add("Request-Id", requestId);
        // Timezone do cliente — usado pelo dashboard para exibir os timestamps.
        req.Headers.Add("X-Client-TZ", "America/Sao_Paulo");
        req.Headers.Add("X-Client-Offset-Minutes", "-180");
        if (trail is not null) req.Headers.Add("X-Imtbl-Trail", trail);

        using var res = await _http.SendAsync(req, ct);
        res.EnsureSuccessStatusCode();
    }

    // Normaliza o immutable_trail (trim, nao-vazio, max 256, sem ':').
    // O servidor responde 400 invalid_immutable_trail se a regra for violada.
    public static string? SanitizeTrail(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var v = value.Trim().Replace(":", "-");
        if (v.Length > 256) v = v[..256];
        return v;
    }
}

// Program.cs — registro via IHttpClientFactory (typed client)
builder.Services.AddHttpClient("immutablelog", c =>
{
    c.BaseAddress = new Uri(
        Environment.GetEnvironmentVariable("IMTBL_URL") ?? "https://api.immutablelog.com");
    c.Timeout = TimeSpan.FromSeconds(5);
});
builder.Services.AddSingleton(sp =>
{
    var http = sp.GetRequiredService<IHttpClientFactory>().CreateClient("immutablelog");
    return new ImmutableLogClient(http,
        Environment.GetEnvironmentVariable("IMTBL_API_KEY") ?? "",
        "my-service", "production");
});

// Uso (fire-and-forget — descarta a Task; capture exceções se precisar):
// _ = client.SendEventAsync("user.created", "success",
//         new { user_id = "usr_123", plan = "pro" }, immutableTrail: "order-7782");

Use IHttpClientFactory para reaproveitar conexões. Para fire-and-forget, descarte a Task com _ = ...; envolva em try/catch se quiser tratar falhas de envio.

Retry com backoff exponencial

Para produção de alta disponibilidade, adicione retry com backoff exponencial. O ImmutableLog retorna 202 em sucesso e 200 em duplicata idempotente — nunca faça retry em 429 (limite mensal atingido).

csharp
// Retry em erros transitorios (nunca em 429).
public async Task SendWithRetryAsync(
    string eventName, string kind, object payload,
    string? trail = null, int maxAttempts = 3, CancellationToken ct = default)
{
    var delay = TimeSpan.FromMilliseconds(500);

    for (var attempt = 0; attempt < maxAttempts; attempt++)
    {
        try
        {
            await SendEventAsync(eventName, kind, payload, trail, ct);
            return;
        }
        catch (HttpRequestException ex) when (ex.StatusCode == (System.Net.HttpStatusCode)429)
        {
            // 429 = limite mensal — nao fazer retry.
            return;
        }
        catch (HttpRequestException) when (attempt + 1 < maxAttempts)
        {
            await Task.Delay(delay, ct);
            delay *= 2; // 500ms -> 1s -> 2s
        }
    }
}

Hash de dados sensíveis

Nunca envie dados pessoais brutos (e-mail, CPF, IP) para o ImmutableLog. Use SHA-256 para gerar um digest determinístico — rastreável sem expor o dado original.

csharp
using System.Security.Cryptography;
using System.Text;

// Faz o hash de dados sensiveis antes de enviar — nunca logue PII bruto.
static string Sha256Hex(string data)
{
    var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(data));
    return Convert.ToHexString(bytes).ToLowerInvariant();
}

// Uso no payload do evento:
var payload = new
{
    user_id    = "usr_123",
    email_hash = Sha256Hex("user@example.com"),
    ip_hash    = Sha256Hex("192.168.1.1"),
};

Esta documentação reflete o comportamento atual da API. Para dúvidas ou integrações avançadas, entre em contato com o time de suporte.