diff --git a/src/afterfact.rs b/src/afterfact.rs index ff53bb9a..2cda80b4 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -213,8 +213,9 @@ fn emit_csv( let disp_wtr = BufferWriter::stdout(ColorChoice::Always); let mut disp_wtr_buf = disp_wtr.buffer(); let json_output_flag = configs::CONFIG.read().unwrap().args.json_timeline; + let jsonl_output_flag = configs::CONFIG.read().unwrap().args.jsonl_timeline; - let mut wtr = if json_output_flag { + let mut wtr = if json_output_flag || jsonl_output_flag { WriterBuilder::new() .delimiter(b'\n') .double_quote(false) @@ -293,8 +294,9 @@ fn emit_csv( ) .ok(); } else if json_output_flag { + // JSON output wtr.write_field(" {")?; - wtr.write_field(&output_json_str(&detect_info.ext_field, &profile))?; + wtr.write_field(&output_json_str(&detect_info.ext_field, &profile, jsonl_output_flag))?; if processed_message_cnt != message::MESSAGES._len() - 1 || info_idx != detect_infos.len() - 1 { @@ -302,6 +304,9 @@ fn emit_csv( } else { wtr.write_field(" }")?; } + } else if jsonl_output_flag { + // JSONL output format + wtr.write_field(format!("{{ {} }}", &output_json_str(&detect_info.ext_field, &profile, jsonl_output_flag)))?; } else { // csv output format if plus_header { @@ -914,6 +919,7 @@ fn _convert_valid_json_str(input: &[&str], concat_flag: bool) -> String { fn output_json_str( ext_field: &LinkedHashMap, profile: &LinkedHashMap, + jsonl_output_flag: bool, ) -> String { let mut target: Vec = vec![]; for (k, v) in ext_field.iter() { @@ -921,8 +927,7 @@ fn output_json_str( let vec_data = _get_json_vec(output_value_fmt, v); if vec_data.is_empty() { let tmp_val: Vec<&str> = v.split(": ").collect(); - let output_val = - _convert_valid_json_str(&tmp_val, output_value_fmt.contains("%RecordInformation%")); + let output_val = _convert_valid_json_str(&tmp_val, output_value_fmt.contains("%RecordInformation%")); target.push(_create_json_output_format( k, &output_val, @@ -984,7 +989,9 @@ fn output_json_str( let output_tmp = format!("{}: {}", tmp, output_value_stock); let output: Vec<&str> = output_tmp.split(": ").collect(); let key = _convert_valid_json_str(&[output[0]], false); - let fmted_val = _convert_valid_json_str(&output, false); + let fmted_val = + _convert_valid_json_str(&output, false) + ; target.push(_create_json_output_format( &key, &fmted_val, @@ -999,7 +1006,9 @@ fn output_json_str( let output_tmp = format!("{}: {}", tmp, output_value_stock); let output: Vec<&str> = output_tmp.split(": ").collect(); let key = _convert_valid_json_str(&[output[0]], false); - let fmted_val = _convert_valid_json_str(&output, false); + let fmted_val = + _convert_valid_json_str(&output, false) + ; target.push(_create_json_output_format( &key, &fmted_val, @@ -1034,7 +1043,11 @@ fn output_json_str( } value.push("\n ]".to_string()); - let fmted_val = value.join(""); + let fmted_val = if jsonl_output_flag { + value.iter().map(|x| x.replace('\n', "")).join("") + } else { + value.join("") + }; target.push(_create_json_output_format( &key, &fmted_val, @@ -1043,7 +1056,13 @@ fn output_json_str( )); } } - target.join(",\n") + if jsonl_output_flag { + // JSONL output + target.into_iter().map(|x| x.replace(" ", "")).join(",") + } else { + // JSON format output + target.join(",\n") + } } #[cfg(test)] diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 73392bfc..f1849a65 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -241,6 +241,10 @@ pub struct Config { #[clap(help_heading = Some("OUTPUT"), short = 'j', long = "json", requires = "output")] pub json_timeline: bool, + /// Save the timeline in JSONL format (ex: -J -o results.jsonl) + #[clap(help_heading = Some("OUTPUT"), short = 'J', long = "jsonl", requires = "output")] + pub jsonl_timeline: bool, + /// Do not display result summary #[clap(help_heading = Some("DISPLAY-SETTINGS"), long = "no-summary")] pub no_summary: bool,