feat: add native JSON output (-J/-j flags)#353
Conversation
Add Fjson/Fjsonl global flags, option parsing for -J (nested JSON) and -j (JSON Lines), mutual exclusivity validation, and default field selection when -F is not explicitly given.
Emits {"lsof_version":"...","processes":[...]} around process
objects. Handles empty results and suppresses -F marker output
in JSON modes during repeat cycles.
Refactors json_open_envelope/json_close_envelope into print.c which already includes version.h via the correct build ordering. Updates all dialect Makefiles to add version.h as a dependency for print.o.
CI StatusAll 14 checks pass except the Cirrus CI / FreeBSD 14.3 job, which is a known pre-existing failure unrelated to this PR. Evidence:
This PR touches only the CLI output path ( |
src/main.c
Outdated
| } | ||
| } | ||
| if (!has_fields) { | ||
| for (i = 0; FieldSel[i].nm; i++) { |
There was a problem hiding this comment.
Lots of code duplication with existing -F handling, can we deduplicate them?
There was a problem hiding this comment.
Done in bb8bab1. Extracted select_default_fields() static helper in main.c — now called from both the case 'F': default path and the -J/-j default field setup. Removed ~40 lines of duplicated #ifdef logic.
src/print.c
Outdated
| static void json_print_str(int *sep, const char *key, const char *val) { | ||
| if (*sep) | ||
| putchar(','); | ||
| printf("\"%s\":\"", key); |
There was a problem hiding this comment.
The key might also require escape?
There was a problem hiding this comment.
Good catch. Fixed in bb8bab1 — extracted json_print_key() helper that escapes the key via json_puts_escaped(). All json_print_* functions now route through it.
src/print.c
Outdated
| if (FieldSel[LSOF_FIX_ACCESS].st) { | ||
| char a[2] = {access_to_char(Lf->access), '\0'}; | ||
| if (a[0] != ' ') | ||
| json_print_str(sep, "access", a); |
There was a problem hiding this comment.
Maybe a json_print_char makes it simpler?
There was a problem hiding this comment.
Done in bb8bab1. Added json_print_char() and switched the access and lock fields to use it — avoids the char[2] array construction.
- Extract select_default_fields() to deduplicate field selection logic shared between -F and -J/-j default handling (jiegec review) - Escape JSON keys via json_print_key() helper (jiegec review) - Add json_print_char() for single-char fields like access and lock, avoiding unnecessary char[2] array construction (jiegec review)
|
FYI, as currently written, as strings in the output are encoded as JSON strings, and JSON kind of mandates UTF-8, that doesn't work properly for file names that are encoded in a character encoding other than ASCII or UTF-8. From testing it looks like it replaces all sequences of 1 or more bytes that can't be decoded into UTF-8 with one There's no good way to address that. That's a shortcoming of the JSON format.
I'm not saying See https://unix.stackexchange.com/questions/757832/how-to-process-json-with-strings-containing-invalid-utf-8 for more background on that. |
My bad, I messed up my testing. Looks like lsof behaves like lsfd and many other tools that dump those bytes as-is, so producing invalid JSON, but one that contains the information in a theoretically parseable way. Still worth documenting. |
Summary
-Jflag for nested JSON output (single JSON object with processes→files hierarchy)-jflag for flat JSON Lines output (one denormalized JSON object per open file, one per line)-Ffield selection mechanism for choosing which fields to includeMotivation
The legacy
-Ffield output uses single-character field IDs with newline/NUL terminators. While machine-parseable, it requires custom parsers. Native JSON enables direct ingestion into logging pipelines (Datadog, Splunk, Elastic Stack) and seamless parsing by Python, Go, Nushell, andjq.Changes
src/store.cFjson,Fjsonl,Fjson_first_procglobal flagslib/common.hsrc/main.c-J/-j, mutual exclusivity validation, default field selectionsrc/print.cjson_print_file(),json_print_proc_fields(), JSON branch inprint_proc(), envelope functionslib/proto.hjson_open_envelope/json_close_envelopelib/dialects/*/Makefileversion.hdependency forprint.oLsof.8-Jand-jtests/case-30-json-output.bash-Jnested JSONtests/case-31-jsonl-output.bash-jJSON LinesMakefile.amDesign decisions
size,offset,inodeemitted as JSON strings to avoid IEEE 754 precision losstcp_info: {state, recv_queue, send_queue, ...}in both formatsTest plan
-Jproduces valid JSON (parseable bypython3 -m json.tool)-jproduces valid JSON Lines (each line parseable)-Fpcfnlimits output to selected fields only-Jand-jare mutually exclusive (error if both specified)-J/-jand-tare mutually exclusive{"lsof_version":"...","processes":[]}for-J, empty for-j)+J/+jproduce error messages