Java
Guia completo de integração com o ImmutableLog em Java. Escolha entre middleware automático para Spring Boot, Quarkus e Micronaut, cliente HTTP direto para workers e jobs, ou @AuditEvent via Spring AOP para instrumentar métodos de serviço.
Filtros por framework
A forma mais rápida de integrar é via filtro HTTP nativo do framework. Toda requisição é capturada automaticamente — latência, status, client IP e hash do body — sem modificar nenhum controller. Clique no framework para ver o código completo.
OncePerRequestFilter com ContentCachingWrapper, HttpClient.sendAsync() fire-and-forget e @AuditEvent via Spring AOP.
@Component
public class ImmutableLogFilter
extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws ... {
// wrap → chain → emit (async)
}
}Ver documentação completa →
ContainerRequestFilter + ContainerResponseFilter JAX-RS com @Provider. Compatível com RESTEasy Reactive e compilação nativa GraalVM.
@Provider
@Priority(Priorities.USER - 100)
public class ImmutableLogFilter
implements ContainerRequestFilter,
ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext req) { ... }
@Override
public void filter(ContainerRequestContext req,
ContainerResponseContext res) { ... }
}Ver documentação completa →
HttpServerFilter com Publishers.map() reativo. Zero reflection em runtime — compatível com GraalVM Native Image por padrão.
@Filter("/**")
@Singleton
public class ImmutableLogFilter
implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> req, ServerFilterChain chain) {
return Publishers.map(chain.proceed(req),
res -> { emit(req, res); return res; });
}
}Ver documentação completa →
Cliente HTTP direto
Use o java.net.http.HttpClient (Java 11+) para enviar eventos diretamente ao ImmutableLog sem depender de um framework web. Ideal para workers, Kafka consumers, scheduled jobs e qualquer código JVM que precise registrar eventos no ledger.
import java.net.URI;
import java.net.http.*;
import java.util.*;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ImmutableLogClient {
private static final String API_URL = "https://api.immutablelog.com/v1/events";
private final String apiKey;
private final String serviceName;
private final String env;
private final HttpClient http = HttpClient.newHttpClient();
private final ObjectMapper mapper = new ObjectMapper();
public ImmutableLogClient(String apiKey, String serviceName, String env) {
this.apiKey = apiKey;
this.serviceName = serviceName;
this.env = env;
}
public CompletableFuture<Void> sendEvent(
String eventName,
String type, // "success" | "info" | "error"
Map<String, Object> payload
) {
try {
String requestId = UUID.randomUUID().toString();
Map<String, Object> meta = Map.of(
"type", type,
"event_name", eventName,
"service", serviceName,
"request_id", requestId,
"env", env
);
Map<String, Object> body = Map.of(
"payload", mapper.writeValueAsString(payload),
"meta", meta
);
String json = mapper.writeValueAsString(body);
var request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey)
.header("Idempotency-Key", requestId)
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
// Fire and forget — never block the caller
return http.sendAsync(request, HttpResponse.BodyHandlers.discarding())
.thenApply(_ -> null);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}
// Usage:
var client = new ImmutableLogClient(
System.getenv("IMTBL_API_KEY"), "my-service", "production"
);
client.sendEvent("user.created", "success", Map.of(
"user_id", "usr_123",
"email_hash", sha256("user@example.com"),
"plan", "pro"
));sendAsync() retorna um CompletableFuture sem bloquear a thread. O evento é enviado de forma assíncrona — a execução do código prossegue imediatamente após a chamada.
Retry com backoff exponencial
Para produção de alta disponibilidade, adicione retry automático com backoff exponencial. O ImmutableLog retorna 202 em sucesso e 429 quando o limite mensal é atingido — nunca faça retry em 429.
public CompletableFuture<Void> sendEventWithRetry(
String eventName,
String type,
Map<String, Object> payload,
int maxAttempts
) {
return sendWithRetry(eventName, type, payload, maxAttempts, 0, 500);
}
private CompletableFuture<Void> sendWithRetry(
String eventName, String type, Map<String, Object> payload,
int maxAttempts, int attempt, long delayMs
) {
return sendEventRaw(eventName, type, payload)
.handle((res, ex) -> {
// 202 = success, 429 = rate limited (never retry)
if (ex == null) return CompletableFuture.<Void>completedFuture(null);
if (attempt + 1 >= maxAttempts) return CompletableFuture.<Void>failedFuture(ex);
return CompletableFuture
.runAsync(() -> {}, CompletableFuture.delayedExecutor(
delayMs, TimeUnit.MILLISECONDS
))
.thenCompose(_ -> sendWithRetry(
eventName, type, payload,
maxAttempts, attempt + 1, delayMs * 2 // exponential backoff
));
})
.thenCompose(f -> f);
}Envio em lote (batch)
Em workers de alta frequência, acumule eventos e envie em paralelo usando CompletableFuture.allOf(). Cada evento é enviado individualmente — allOf() paraleliza sem falhar no conjunto inteiro.
public CompletableFuture<Void> sendBatch(List<EventDto> events) {
// Send all events in parallel — fire and forget each one
var futures = events.stream()
.map(e -> sendEvent(e.eventName(), e.type(), e.payload())
.exceptionally(ex -> null) // never fail the batch
)
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
// Usage:
client.sendBatch(List.of(
new EventDto("order.created", "success", Map.of("order_id", "ord_1")),
new EventDto("payment.captured", "success", Map.of("amount", 9900)),
new EventDto("email.sent", "info", Map.of("template", "welcome"))
));AOP @AuditEvent
Com Spring AOP, instrumente qualquer método de serviço sem modificar a lógica de negócio. O evento é emitido no bloco finally — registrado mesmo quando o método lança uma exceção.
// 1. Add dependency: spring-boot-starter-aop
// pom.xml: <artifactId>spring-boot-starter-aop</artifactId>
// 2. Define the annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuditEvent {
String value() default "";
String type() default "success";
}
// 3. Implement the aspect
@Aspect
@Component
public class AuditEventAspect {
private final ImmutableLogClient client;
public AuditEventAspect(ImmutableLogClient client) {
this.client = client;
}
@Around("@annotation(auditEvent)")
public Object audit(ProceedingJoinPoint pjp, AuditEvent auditEvent)
throws Throwable {
long startMs = System.currentTimeMillis();
String name = auditEvent.value().isBlank()
? pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName()
: auditEvent.value();
try {
Object result = pjp.proceed();
long latency = System.currentTimeMillis() - startMs;
client.sendEvent(name, auditEvent.type(), Map.of("latency_ms", latency));
return result;
} catch (Throwable ex) {
long latency = System.currentTimeMillis() - startMs;
client.sendEvent(name, "error", Map.of(
"latency_ms", latency,
"error_type", ex.getClass().getName(),
"error_message", ex.getMessage()
));
throw ex;
}
}
}
// 4. Use on any Spring bean:
@Service
public class OrderService {
@AuditEvent("order.processed")
public Order process(OrderRequest req) {
// ... business logic
}
@AuditEvent(value = "payment.captured", type = "success")
public void capturePayment(String orderId, long amountCents) {
// ...
}
}@Around captura exceções
@Around é usado ao invés de @AfterReturning/@AfterThrowing para garantir que o evento seja emitido em todos os casos — sucesso, falha ou exceção.
Nome automático via reflection
Se value() estiver vazio, o aspecto usa o nome do método via pjp.getSignature(). Sempre prefira um nome semântico explícito via @AuditEvent("domain.action").
Configuração como Spring Bean
Para projetos Spring Boot, registre ImmutableLogClient como @Bean para injeção via @Autowired ou construtor em qualquer componente.
// Register ImmutableLogClient as a Spring bean for injection
@Configuration
public class ImmutableLogConfig {
@Bean
public ImmutableLogClient immutableLogClient(
@Value("${immutablelog.api-key}") String apiKey,
@Value("${immutablelog.service-name:my-service}") String service,
@Value("${immutablelog.env:production}") String env
) {
return new ImmutableLogClient(apiKey, service, env);
}
}
// application.properties:
// immutablelog.api-key=${IMTBL_API_KEY}
// immutablelog.service-name=my-service
// immutablelog.env=productionEsta documentação reflete o comportamento atual da API. Para dúvidas ou integrações avançadas, entre em contato com o time de suporte.
