RUNNING TESTS
Logging
Karate v2 has a single bucket for everything log-related: configure logging. It controls what gets captured into reports, what hits the console, how HTTP bodies are formatted, and which fields get redacted.
TL;DR
function fn() {
karate.configure('logging', {
report: 'debug', // what gets captured into reports
console: 'info', // what hits stdout via SLF4J/Logback
pretty: true, // pretty-print HTTP JSON bodies
mask: { // HTTP-only redaction
headers: ['Authorization', 'Cookie'],
jsonPaths: ['$.password', '$..token'],
patterns: [{ regex: '\\bBearer [A-Za-z0-9._-]+\\b', replacement: 'Bearer ***' }]
}
});
return { baseUrl: 'https://api.example.com' };
}
The same shape works inside a feature file:
Background:
* configure logging = { report: 'debug', console: 'info', pretty: true }
Two thresholds: report vs console
Karate keeps two independent levels.
| Knob | What it controls | CLI flag | Default |
|---|---|---|---|
logging.report | What gets captured into HTML / JSONL / Cucumber JSON / JUnit | --log-report | debug |
logging.console | What hits stdout via SLF4J / Logback | --log-console | info |
The HTTP logger always writes the full request/response (headers + bodies) to the report buffer at INFO. The console emission tiers automatically by SLF4J level: INFO = one-liner, DEBUG = +headers, TRACE = +body. Splitting the two knobs lets you, for example, capture full traces in reports for post-hoc debugging while keeping a parallel run's console quiet.
# Quiet console, rich reports — typical CI setup
* configure logging = { report: 'debug', console: 'warn' }
# Reverse — chatty local debugging, lean reports
* configure logging = { report: 'warn', console: 'debug' }
Mid-test level flips with auto-restore
* configure logging = { ... } mid-flow takes effect immediately. At scenario end the levels are automatically snapshotted and restored, so the next scenario starts at whatever karate-config.js set. No manual cleanup needed.
Scenario: silence a noisy reusable
* configure logging = { report: 'error' }
* call read('classpath:noisy-warmup.feature')
# report level is back to default at scenario end — automatically
Pretty body formatting
logging.pretty applies to HTTP request/response JSON bodies in both console and report.
* configure logging = { pretty: true } # default — multi-line, 2-space indent
* configure logging = { pretty: false } # single line
Non-JSON bodies (XML, plain text, binary) pass through unchanged. The pretty pass runs after masking, so masked values stay masked.
Masking sensitive data
logging.mask is a declarative redactor that runs against HTTP request/response logs. It does not scan print or karate.log output — those are user-controlled channels. If a step might leak via print, raise logging.report to 'warn' to drop INFO captures, or tag the scenario @report=false.
Headers
Header names are matched case-insensitively (RFC 7230). Matched headers' values are replaced with replacement (default '***').
* configure logging = {
mask: { headers: ['Authorization', 'Cookie', 'X-Api-Key'] }
}
* header Authorization = 'Bearer abc.def.ghi'
# Logged as: Authorization: ***
JSON paths
Two simple forms are supported:
$.a.b.c— descend by key$..foo— recursive search for anyfookey at any depth
Matched values become replacement.
* configure logging = {
mask: { jsonPaths: ['$.password', '$..token', '$.user.ssn'] }
}
* request { username: 'alice', password: 'hunter2', meta: { token: 'shhh' } }
# Logged body:
# {
# "username": "alice",
# "password": "***",
# "meta": { "token": "***" }
# }
Regex patterns
Each rule is { regex, replacement }. Patterns run after header / JSON-path masking, so they catch anything those passes didn't.
* configure logging = {
mask: {
patterns: [
{ regex: 'Bearer [A-Za-z0-9._-]+', replacement: 'Bearer ***' },
{ regex: '\\b\\d{16}\\b', replacement: '****-****-****-****' }
]
}
}
URL-scoped masking
enableForUri(uri) is an optional JS predicate. When it returns falsy for a request, no masking applies — useful for excluding /health or other low-value endpoints from redaction so debugging stays easy.
* configure logging = {
mask: {
headers: ['Authorization'],
enableForUri: function(uri){ return uri.indexOf('/health') < 0 }
}
}
Customizing the replacement
* configure logging = {
mask: {
headers: ['Authorization'],
replacement: '<redacted>'
}
}
# Logged as: Authorization: <redacted>
Hide a scenario from reports — @report=false
Tag a scenario @report=false to keep it in the run (it still counts toward suite totals) but suppress its step detail from HTML / Cucumber JSON / JUnit XML / JSONL outputs.
@report=false
Scenario: warmup with sensitive credentials
* call read('classpath:auth/login.feature')
Behavior:
- The scenario runs normally and contributes to pass / fail / skip counts.
- HTML / Cucumber / JUnit / JSONL show the row + status only — no step text, no captured logs, no embedded screenshots.
- Failures surface a redacted message:
output suppressed by @report=false (full detail in runtime logs). - Full failure detail still hits SLF4J / Logback, so you can debug locally with
--log-console debug. - Suppression propagates: any feature called from a
@report=falsescenario is also hidden.
Use this for any run that may include secrets in HTTP bodies or error messages and where the artifacts get uploaded somewhere (CI archives, S3, Slack, etc.).
Deep merge
configure logging deep-merges with the parent. A partial update keeps everything else.
# Background sets the global mask + pretty
Background:
* configure logging = { mask: { headers: ['Authorization'] }, pretty: false }
# A later scenario flips just the threshold — mask + pretty survive
Scenario:
* configure logging = { report: 'error' }
SLF4J categories
For finer console control beyond a single threshold, Karate emits under named SLF4J categories. Tune them in logback.xml or via your dependency-injected logger config.
| Category | What it covers |
|---|---|
karate.runtime | Feature/scenario lifecycle, step execution, suite orchestration |
karate.http | HTTP client — request/response bodies, headers, retries |
karate.mock | Mock server — incoming requests, matched scenarios, responses |
karate.server | Embedded HTTP server — request/response logs |
karate.scenario | User output — print, karate.log(), scenario-scoped messages |
karate.console | CLI console output — summary, colors, progress |
Example: DEBUG only for HTTP traffic, INFO everywhere else:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern></encoder>
</appender>
<logger name="karate.http" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
logging.console is a convenience that sets the karate parent logger's level. For per-category control, edit logback.xml directly.
Putting it together — environment-driven logging
function fn() {
var env = karate.env || 'dev';
var config = { baseUrl: 'https://api.example.com' };
if (env === 'dev') {
karate.configure('logging', {
report: 'debug', console: 'debug',
pretty: true
});
} else if (env === 'ci') {
karate.configure('logging', {
report: 'debug', // capture rich detail in artifacts
console: 'warn', // keep CI logs quiet
pretty: true,
mask: {
headers: ['Authorization', 'Cookie', 'X-Api-Key'],
jsonPaths: ['$.password', '$..token', '$..secret'],
patterns: [
{ regex: 'Bearer [A-Za-z0-9._-]+', replacement: 'Bearer ***' }
]
}
});
}
return config;
}
CLI
# Set both thresholds at run time
karate run --log-report debug --log-console warn features/
# Or via karate-pom.json (`logging` block)
{
"paths": ["src/test/features"],
"logging": {
"report": "debug",
"console": "info"
}
}
Mask configuration lives in feature files / karate-config.js, not the CLI — it's declarative and belongs with the test code that knows what's sensitive.
See also
- Test Reports — formats, output directories, CI publishing
- Debugging — using captured logs to troubleshoot
- Tags —
@report=falseand other built-in tags - Configuration — the
configurekeyword andkarate-config.js