By default, K6 prints a text summary to the terminal when the test ends. That is useful during development but not enough for CI pipelines, stakeholder reports, or time-series analysis. This lesson covers every output format K6 supports and when to use each.
Choosing an output format
K6 output formats
Text summary (default)
Printed to stdout automatically
No flags required
Human-readable metric table
No time-series data — aggregate only
Best for: local development, quick checks
JSON (--out json)
One JSON event per line (NDJSON)
Every metric sample included
Full time-series data
Processable with jq, Python, custom scripts
Best for: custom analysis, CI artifact storage
CSV (--out csv)
One row per metric sample
time,metric_name,metric_type,tags,value columns
Importable into Excel or Google Sheets
Large files for long tests with many metrics
Best for: ad-hoc analysis, sharing with analysts
Custom (handleSummary)
Override the end-of-test summary
Write HTML, JSON, or any format
Access all aggregated metrics in data object
Best for: HTML reports, custom CI output, stakeholder artifacts
JSON output
k6 run --out json=results.json script.js
The file contains one JSON object per line. Each line is a metric point:
The k6-reporter community library generates a self-contained HTML report with charts and tables:
import { htmlReport } from 'https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js';import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js';export function handleSummary(data) { return { 'report.html': htmlReport(data, { title: 'API Load Test — Sprint 42' }), stdout: textSummary(data, { indent: ' ', enableColors: true }), };}
Open report.html in any browser. It includes:
Pass/fail status for each threshold
Metric summary tables with percentiles
Charts for request count and duration
This is the most useful format for non-engineers — QA leads, product managers, and developers who do not have Grafana access.
Console logging
K6 supports console.log(), console.warn(), and console.error() inside any function:
export default function () { const res = http.get('https://api.example.com/health'); if (res.status !== 200) { console.warn(`Health check failed: ${res.status} — ${res.body.substring(0, 100)}`); }}
Use console logging sparingly. At high VU counts — 200 VUs × 10 iterations each — logging on every iteration produces thousands of lines that slow down the K6 process and obscure useful output. Log only on unexpected conditions (non-200 status, slow responses above a threshold).
⚠️ Common mistakes
Relying on text summary for CI pass/fail. The text summary prints to stdout with no exit code information about individual metrics. Use thresholds for CI gates — K6 exits with code 108 when any threshold fails, which CI pipelines detect automatically.
Logging on every iteration at high VU counts.console.log() inside the default function at 500 VUs with a 30-second duration can produce 50,000+ log lines. This slows the runtime and obscures useful output. Gate logs with conditions: if (res.status !== 200) console.warn(...).
Using --out json for long soak tests without log rotation. A 24-hour soak test at 100 RPS generates millions of JSON lines — potentially gigabytes. Stream to InfluxDB instead, which compresses and aggregates the data, rather than writing raw samples to disk.
🎯 Practice task
Generate multiple output formats and build an HTML report. 30 minutes.
Use https://jsonplaceholder.typicode.com.
Write a script with vus: 5, duration: '30s' that makes GET requests to /posts and /users. Add tags { name: 'ListPosts' } and { name: 'ListUsers' }.
Run with --out json=results.json. Open the file and use grep http_req_duration results.json | wc -l to count how many duration samples were captured.
Run with --out csv=results.csv. Open in a spreadsheet and filter for metric_name = http_req_duration. Sort by metric_value descending to find the 5 slowest requests.