skip to content

Logging Only on Failure

Printing request and response only when an assertion fails, and keeping the credential out of that print. It is the difference between a diagnosable CI log and a wall of noise.

part ofREST Assuredoverview, primer and where to startread it →
on this pageshow

questions

4

In REST Assured, how do you print the request and response only when a validation fails?

level: juniorimportance: must knowfreq 68%

answer

  1. print nothing while the suite stays green
  2. the log call that names failure
  3. shared LogSpecification, one call per side
  4. no-argument form means LogDetail.ALL

basics

~20 s

REST Assured buffers the log instead of printing it when you chain given().log().ifValidationFails() on the request or then().log().ifValidationFails() on the response. The buffered text reaches the console only if a then() expectation fails. RestAssured.enableLoggingOfRequestAndResponseIfValidationFails() switches both sides on globally.

solid answer

~40 s

REST Assured's log DSL has a failure-triggered half. `given().log().ifValidationFails()` sends the request log into an in-memory buffer rather than the console, and `then().log().ifValidationFails()` does the same for the response; a default validation-failure listener flushes both buffers to the log stream the moment an expectation inside the `then()` chain throws. `ifValidationFails` is declared on the shared `LogSpecification`, so all three overloads — no-arg, `ifValidationFails(LogDetail)` and `ifValidationFails(LogDetail, boolean shouldPrettyPrint)` — exist on each side, and the no-arg form means `LogDetail.ALL`. To get both sides on every call without repeating the chain, use the static `RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()`, or the config form `LogConfig.enableLoggingOfRequestAndResponseIfValidationFails(HEADERS)` when you want less than the whole exchange. A green run then prints nothing at all, while a red one carries the exact curling-ladder request that produced the mismatch.

code

java · 14 lines
java
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

given()
    .log().ifValidationFails()
    .baseUri("https://ladder.curlingclub.test")
    .header("Authorization", "Bearer " + ladderToken)
    .pathParam("rinkId", "rink-3")
.when()
    .get("/rinks/{rinkId}/ladder")
.then()
    .log().ifValidationFails()
    .statusCode(200)
    .body("rungs[0].skip", equalTo("Aitken"));

go deeper

for a junior

Be ready to write both halves from memory: given().log().ifValidationFails() on the request and then().log().ifValidationFails() before the assertions, and to say plainly that a passing test prints nothing at all.

for a middle

Explain that the no-argument form means LogDetail.ALL, that the same three overloads sit on the shared LogSpecification because both sides inherit it, and that the static shortcut is really a LogConfig setting under a friendlier name.

for a senior

Show the judgement: failure-only logging is the sane default for a CI suite because it keeps a green run silent, and name what you still capture unconditionally when a run has to be diagnosed days later.

for a principal

Own it as policy across suites. One global switch in a shared base beats per-test log calls that drift, and it has to be paired with a header blacklist so the convenience never turns into a credential sitting in a build log.

## Why a suite logs nothing until something breaks A REST Assured suite that calls `given().log().all()` on every request produces a complete transcript of every call it makes. On a green run that transcript is noise: hundreds of requests nobody will read, scrolled past on a shared CI runner. Switch logging off entirely and the opposite problem arrives — a case against the curling-ladder API fails at 02:00 and the job output holds a Hamcrest mismatch with no request beside it. **Failure-triggered logging** resolves both: REST Assured formats the log every time, holds it in memory, and emits it only for calls that actually failed. ## The two calls, one per side `ifValidationFails` is declared on `LogSpecification` — the half of the log DSL that the request side and the response side share — so the same three overloads exist on both: - `ifValidationFails()` — shorthand for `LogDetail.ALL`. - `ifValidationFails(LogDetail logDetail)` — narrow it, for example to `HEADERS`. - `ifValidationFails(LogDetail logDetail, boolean shouldPrettyPrint)` — the same, with pretty printing forced on or off. Which side you call it on decides which half of the exchange you get: | call | side | what it captures | |---|---|---| | `given().log().ifValidationFails()` | request | method, URI, params, headers, cookies, body | | `then().log().ifValidationFails()` | response | status line, headers, cookies, body | Neither call implies the other. A chain carrying only the response form leaves you staring at a ladder payload with no idea which rink id produced it, which is why the two are almost always written together. ## Turning it on for a whole suite Repeating both calls in every test is exactly the duplication a suite drifts out of. Two switches remove it: 1. `RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()` — a static, usually called once in a `@BeforeAll` or a base class. The overload taking a `LogDetail` narrows what gets captured. 2. `LogConfig.enableLoggingOfRequestAndResponseIfValidationFails(...)` — the same setting reached through configuration, applied with `given().config(...)` or by assigning `RestAssured.config`. The static form is documented as a shortcut for exactly this. When the switch is on, REST Assured wires the buffered request log and the buffered response log while it builds the request — but only for a side that has no logging filter registered already. A request that carries its own `RequestLoggingFilter` keeps that one and does not get a second, buffered copy. ## What counts as "validation failed" This is the part that surprises people, and it is worth being literal about. The flush is driven by a **validation-failure listener** that REST Assured fires from inside its own response validation. It runs when an expectation declared in the `then()` chain — `statusCode(...)`, `body(...)`, `header(...)`, `contentType(...)`, `time(...)` — evaluates to a failure, and when a throwable escapes while those expectations are being evaluated. It does **not** run for: - a JUnit, TestNG or AssertJ assertion written after `extract()`, because that assertion is outside REST Assured entirely; - a connection or TLS failure that throws before `then()` is ever reached; - a status code on its own — status-driven logging is a different predicate from validation-driven logging, and the two do not fire at the same moments. So the rule of thumb is: if the assertion is inside the chain, the failure log fires; if you extracted first and asserted afterwards, it does not. ## A curling-ladder walk-through A case fetches `GET /rinks/{rinkId}/ladder` and expects the top rung to belong to skip `Aitken`. Both log calls are chained. On the happy path the service returns 200 with eight rungs, every expectation passes, and the job output stays empty. When the service quietly renames the field to `leadSkip`, the body matcher fails, and the console receives — in one block, request first — the method and URI including the resolved `rink-3` path parameter, every header that was sent, and the full response body that disagreed. That is enough to tell a contract change from a data problem without rerunning anything. ## What it costs and what it does not do - On a passing call the cost is building one string per side and letting it be garbage collected; nothing is written to the console. - It changes only what is **printed**. The request goes on the wire identically either way. - It is per-request state, not per-suite: each call gets its own buffer, and nothing leaks into the next test. - It captures what REST Assured built and sent, so it is the client's view of the exchange rather than the server's. - Chained by hand, the response-side call must sit **before** the assertions it is meant to explain, because a `then()` chain validates eagerly as each expectation is called. Used this way, failure-only logging gives a CI job the property people actually want from it: silence while everything works, and a complete, self-contained record of the one call that did not.

  • Does given().log().ifValidationFails() print anything when every expectation passes?
    No. The request is still formatted and written, but into a `ByteArrayOutputStream` held by an internal log repository rather than the console. Nothing flushes it unless a REST Assured expectation fails, so a green suite produces no output and pays only the cost of building one string per request.
  • How do you narrow the failure log to headers instead of the whole exchange?
    Pass a `LogDetail`: `given().log().ifValidationFails(LogDetail.HEADERS)` and `then().log().ifValidationFails(LogDetail.HEADERS)`, or set it once with `LogConfig.enableLoggingOfRequestAndResponseIfValidationFails(HEADERS)`. The static `RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(LogDetail)` takes the same argument. The no-argument forms are shorthand for `LogDetail.ALL`.

saying these in an interview costs you the question

  • Thinks ifValidationFails() exists only on the response side
  • Believes the request is never sent when nothing is printed
  • Expects a JUnit assertion after extract() to trigger the flush
  • Confuses ifValidationFails() with status-driven logging of an error code
  • Assumes the global switch also prints on passing requests
open as a page

In REST Assured, which LogConfig call keeps an Authorization header out of the printed request?

level: juniorimportance: should knowfreq 45%

basics

~20 s

LogConfig.blacklistDefaultSensitiveHeaders() masks exactly Authorization, Proxy-Authorization and Cookie, and blacklistHeader(name, others...) adds any other name. REST Assured still prints the header name but replaces its value with the literal marker [ BLACKLISTED ]. The blacklist reaches the request log.

open as a page

Your REST Assured CI run prints nothing on a failing case despite enableLoggingOfRequestAndResponseIfValidationFails(); how do you diagnose it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

REST Assured flushes a failure log only when an expectation inside its own then() chain throws, so the usual cause is an assertion outside that chain. Then check the call's position, and whether a later LogConfig assignment dropped the switch.

open as a page

In REST Assured, where does then().log().ifValidationFails() write while every expectation still passes?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

REST Assured swaps the print stream for a ByteArrayOutputStream registered in an internal log repository, so the text accumulates in memory. A default validation-failure listener flushes that buffer when a then() expectation throws. A passing call discards it silently.

open as a page