Pretty-Printing JSON in CI Logs: Readability Without Leaks
CI pipelines that dump raw JSON logs are hard to read. Pretty-printing everything can leak secrets. Redact, truncate, and keep full bodies in artifacts.
By Rehan Baqir
Pretty-printing JSON in CI makes a one-line failure readable, and it can also paste a token into a log that other people can open. JSON Formatter is a reasonable place to shape a sample on your own machine before you copy a jq filter into the repo. In the pipeline, redact known secret keys first, cap array dumps, and write the full body to an artifact with access control. A public GitHub Actions log is a bad place for an OAuth response, indented or not.
A failed deploy once printed 4,000 lines of minified JSON: one line, no breaks. The engineer missed "error": "invalid_client" at column 9,000. The next pipeline added jq '.' everywhere. The week after that, a public GitHub Actions log showed a full OAuth token inside a pretty-printed response body.
Readable logs help until they become readable leaks.
Why CI JSON is painful
Build systems capture:
- API health check responses
- Terraform plan JSON
- Test report aggregates
- npm audit output
- Deployment webhook payloads
Tools emit compact JSON by default. A person scrolling GitHub Actions or GitLab CI needs indentation. Those same pipelines often run in a shared environment, and sometimes a public one.
Pretty-print mechanics
jq (shell pipelines)
curl -s https://api.example.com/status | jq '.'
Print the fields you need:
jq '{status: .status, version: .version}'
Node / JavaScript
console.log(JSON.stringify(payload, null, 2));
Use the JSON Formatter locally to prototype the output shape before you wire CI. Pair it with JSON Validator on fixtures.
Python
import json
print(json.dumps(data, indent=2, sort_keys=True))
For formatting habits and the usual syntax mistakes, see JSON Formatting Guide and Common JSON Formatting Errors.
Safe pretty-print rules
1. Redact before indent
Keep a denylist of keys:
authorization, token, api_key, password, secret, cookie
Replace the values with [REDACTED], and walk nested objects. jq can do that with a filter. A custom script has to handle nesting too.
2. Truncate large arrays
Log items[0:3] and "... 47 more". Leave the rest of the inventory out. A size cap also keeps you under log-platform rate limits.
3. Pretty-print artifacts, not the console stream
Write the full formatted JSON to a CI artifact that people download under access control. Keep the console to a summary:
Test report: 142 passed, 3 failed — see artifacts/report.json
4. Separate public and private workflows
Fork PRs on a public repo run with restricted secrets. Do not pretty-print env files in a PR log. Redaction still misses key names you have not seen before.
5. Fail on parse errors
jq empty < response.json || exit 1
Invalid JSON in a step marked success hides a broken integration.
JSON vs JSONL in pipelines
| Format | CI console | Log aggregator | | --- | --- | --- | | Pretty JSON | Human debugging | Poor per-line search | | Compact JSON | One line OK | Better for grep | | JSONL | One event per line | Ideal for Datadog/Splunk |
For streaming build events, JSONL fits. For a one-shot look at an API failure, pretty JSON fits, after redaction.
Read JSON vs JSONL when you pick an export format for test output.
Structured logging without full dumps
Instead of printing an entire webhook body:
{
"event": "deploy_failed",
"status": 422,
"error_code": "INVALID_IMAGE",
"request_id": "req_abc123"
}
One object, easy to grep, no secrets. Save the full body to an artifact if someone needs the rest.
Local vs CI parity
Developers pretty-print locally with browser tools. CI should use the same jq filters checked into the repo (scripts/format-healthcheck.sh) so the pipeline output matches what you saw on your machine.
GitHub Actions vs GitLab CI patterns
On a public GitHub Actions repo, assume the logs are world-readable. Use ::add-mask:: for secrets and do not echo ${{ secrets.* }} from a debug step. GitLab CI can store JSON reports as job artifacts with expire_in: 7 days and restricted visibility.
A fork PR workflow should not receive production secrets. Mock the API response in a fixture. A live authenticated call is how a real token ends up pretty-printed.
jq recipes for CI
Extract the error code only:
jq -r '.error.code // "UNKNOWN"' response.json
Check array length before you print the whole file:
count=$(jq '.items | length' data.json)
if [ "$count" -gt 50 ]; then jq '.items[:10]' data.json; else jq '.' data.json; fi
On-call handoff
When you page someone at 3 AM, attach a redacted pretty-printed artifact. An 8,000-line Slack paste is the wrong handoff. Point the incident ticket at the artifact download.
Limitations
Pretty-printing JSON in CI logs makes syntax and schema failures easy to spot. An unredacted dump can leak tokens into a shared build log and can exceed a log collector's line limits.
Conclusion
Pretty-print JSON in CI so a failure is faster to read. Do not dump an entire authenticated response into a shared log.
Redact, truncate, and put the rest in an artifact. Validate the JSON before you format it. If the token would have shown up on line 47 of an indented block, mask it before the first line is written.
Frequently Asked Questions
Common questions answered to help you get the most from this tool.