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.
Middlewares
Integração automática via middleware no pipeline ASP.NET Core — toda requisição HTTP é capturada sem modificar nenhum controller ou endpoint. Clique no framework para ver o código completo.
Middleware via IMiddleware com InvokeAsync(HttpContext). Captura o Response.StatusCode após o next e envia o evento de forma assíncrona.
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
var sw = Stopwatch.StartNew();
await next(ctx);
_ = EmitAsync(ctx, ctx.Response.StatusCode, sw.Elapsed);
}Ver documentação completa →
Mesmo middleware registrado com app.UseMiddleware<>(). Defina evento e trilha por endpoint via HttpContext.Items.
app.UseMiddleware<ImmutableLogMiddleware>();
app.MapPost("/payments", (HttpContext ctx) =>
{
ctx.Items["imtbl.eventName"] = "payment.created";
return Results.Ok();
});Ver documentação completa →
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.
// 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).
// 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.
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.
