Skip to content

Events

Four event types capture the lifecycle of an API call. They map 1:1 to event_type values in api_log.

Event hierarchy

All three Java event classes extend org.springframework.context.ApplicationEvent:

ApplicationEvent
   ├── ApiCallInitiatedEvent    → INITIATED   row
   ├── ApiCallSuccessEvent      → SUCCESS     row
   └── ApiCallErrorEvent        → ERROR or RETRY_ERROR row
                                   (depending on is_retry)

Note: there are only three event classes, but ApiCallErrorEvent produces either an ERROR or RETRY_ERROR row based on the isRetry flag — ApiLogService decides which event_type to write.

ApiCallInitiatedEvent

Fired immediately before the request is sent. The row gives you a "we tried to call X" record even if the call hangs forever or the JVM crashes before getting a response.

Constructor:

new ApiCallInitiatedEvent(Object source, ApiRequest request)

Fields available on the event:

Field Type Notes
request ApiRequest The endpoint + payload being sent
eventTimestamp LocalDateTime When the event was constructed

Resulting api_log row:

Column Value
event_type INITIATED
request_id UUID (generated by the publisher)
endpoint from request.endpoint
payload from request.payload (JSONB-serialized)
response, status_code, error_message NULL
retry_count 0 (initial)
is_retry false

ApiCallSuccessEvent

Fired after a successful HTTP response (2xx). For non-RestApiClientUtil callers, you decide what "success" means.

Constructor:

new ApiCallSuccessEvent(Object source, ApiRequest request, ApiResponse response)

Fields:

Field Type Notes
request ApiRequest The original request
response ApiResponse data (body) + statusCode
eventTimestamp LocalDateTime

Resulting row:

Column Value
event_type SUCCESS
response from response.data (JSONB)
status_code from response.statusCode
error_message NULL

ApiCallErrorEvent

Fired when the call fails. Used for both terminal errors and retry attempts — the isRetry flag flips the event_type between ERROR and RETRY_ERROR.

Constructor:

new ApiCallErrorEvent(
    Object source,
    ApiRequest request,
    Throwable error,
    int retryCount,
    boolean isRetry
)

Fields:

Field Type Notes
request ApiRequest The request that failed
error Throwable The exception
retryCount int 0 for first attempt, 1+ for retries
isRetry boolean true for retry attempts, false for the first
eventTimestamp LocalDateTime

Resulting row:

Column Value
event_type RETRY_ERROR if isRetry == true, else ERROR
error_message {"type": "<exception class>", "message": "<message>"}
status_code If the exception is an HttpClientErrorException / HttpServerErrorException, the upstream status. Otherwise NULL.
retry_count from retryCount
is_retry from isRetry

Event type cheat sheet

event_type Source Has response Has error_message Has status_code
INITIATED ApiCallInitiatedEvent
SUCCESS ApiCallSuccessEvent
ERROR ApiCallErrorEvent (isRetry=false) Sometimes
RETRY_ERROR ApiCallErrorEvent (isRetry=true) Sometimes

Listening to events yourself

Beyond api-log's own listener, you can attach more — alerting, metrics, side-effects:

@Component
public class HighErrorRateAlerter {

    @EventListener
    @Async
    public void onError(ApiCallErrorEvent event) {
        if (event.getError() instanceof HttpServerErrorException &&
            "/critical/endpoint".equals(event.getRequest().getEndpoint())) {
            slackClient.notify("5xx on critical endpoint: " + event.getError().getMessage());
        }
    }
}

Multiple listeners on the same event run independently — api-log writes the row, your alerter sends Slack. Neither blocks the caller.

See also