diff --git a/CHANGES b/CHANGES index 5a04012955..cc42fa701b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,19 @@ +2.1-178 | 2012-11-23 19:35:32 -0800 + + * The ASCII writer now supports a new filter config option + "only_single_header_row" that turns the output into CSV format + when set to "T". (Carsten Langer) + + * Add new function flavor called a "hook". This new flavor of + function behaves like a "synchronous event". See + doc/scripts/builtins.rst more details on usage. (Jon Siwek) + + * Improve auto-generated enum documentation. The names of enum types + are tracked so that variables holding a value of a given enum type + can generate a reference to it instead of just listing the type as + a generic "enum". (Jon Siwek) + 2.1-171 | 2012-11-23 18:24:15 -0800 * Fix ambiguity between composite table index and record ctor diff --git a/VERSION b/VERSION index 7a44c3a19f..fc5f5588a7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1-171 +2.1-178 diff --git a/scripts/base/frameworks/logging/writers/ascii.bro b/scripts/base/frameworks/logging/writers/ascii.bro index bacb0996d0..800af51502 100644 --- a/scripts/base/frameworks/logging/writers/ascii.bro +++ b/scripts/base/frameworks/logging/writers/ascii.bro @@ -1,5 +1,13 @@ ##! Interface for the ASCII log writer. Redefinable options are available ##! to tweak the output format of ASCII logs. +##! +##! The ASCII writer supports currently one writer-specific filter option via +##! ``config``: setting ``only_single_header_row`` to ``T`` turns the output into +##! into CSV mode where only a single header row with the column names is printed +##! out as meta information. Example filter using this:: +##! +##! local my_filter: Log::Filter = [$name = "my-filter", $writer = Log::WRITER_ASCII, $config = table(["only_single_header_row"] = "T")]; +##! module LogAscii; diff --git a/src/logging/writers/Ascii.cc b/src/logging/writers/Ascii.cc index 11b322f5a3..c65b0701c3 100644 --- a/src/logging/writers/Ascii.cc +++ b/src/logging/writers/Ascii.cc @@ -19,6 +19,7 @@ Ascii::Ascii(WriterFrontend* frontend) : WriterBackend(frontend) { fd = 0; ascii_done = false; + only_single_header_row = false; output_to_stdout = BifConst::LogAscii::output_to_stdout; include_meta = BifConst::LogAscii::include_meta; @@ -80,7 +81,7 @@ void Ascii::CloseFile(double t) if ( ! fd ) return; - if ( include_meta ) + if ( include_meta && ! only_single_header_row ) WriteHeaderField("close", Timestamp(0)); safe_close(fd); @@ -108,29 +109,29 @@ bool Ascii::DoInit(const WriterInfo& info, int num_fields, const Field* const * return false; } + for ( WriterInfo::config_map::const_iterator i = info.config.begin(); i != info.config.end(); i++ ) + { + if ( strcmp(i->first, "only_single_header_row") == 0 ) + { + if ( strcmp(i->second, "T") == 0 ) + only_single_header_row = true; + + else if ( strcmp(i->second, "F") == 0 ) + only_single_header_row = false; + + else + { + Error("invalid value for 'only_single_header_row', must be boolean (T/F)"); + return false; + } + } + } + if ( include_meta ) { string names; string types; - string str = string(meta_prefix, meta_prefix_len) - + "separator " // Always use space as separator here. - + get_escaped_string(string(separator, separator_len), false) - + "\n"; - - if ( ! safe_write(fd, str.c_str(), str.length()) ) - goto write_error; - - if ( ! (WriteHeaderField("set_separator", get_escaped_string( - string(set_separator, set_separator_len), false)) && - WriteHeaderField("empty_field", get_escaped_string( - string(empty_field, empty_field_len), false)) && - WriteHeaderField("unset_field", get_escaped_string( - string(unset_field, unset_field_len), false)) && - WriteHeaderField("path", get_escaped_string(path, false)) && - WriteHeaderField("open", Timestamp(0))) ) - goto write_error; - for ( int i = 0; i < num_fields; ++i ) { if ( i > 0 ) @@ -143,11 +144,39 @@ bool Ascii::DoInit(const WriterInfo& info, int num_fields, const Field* const * types += fields[i]->TypeName().c_str(); } + if ( only_single_header_row ) + { + // A single CSV-style line is all we need. + string str = names + "\n"; + if ( ! safe_write(fd, str.c_str(), str.length()) ) + goto write_error; + + return true; + } + + string str = string(meta_prefix, meta_prefix_len) + + "separator " // Always use space as separator here. + + get_escaped_string(string(separator, separator_len), false) + + "\n"; + + if ( ! safe_write(fd, str.c_str(), str.length()) ) + goto write_error; + + if ( ! (WriteHeaderField("set_separator", get_escaped_string( + string(set_separator, set_separator_len), false)) && + WriteHeaderField("empty_field", get_escaped_string( + string(empty_field, empty_field_len), false)) && + WriteHeaderField("unset_field", get_escaped_string( + string(unset_field, unset_field_len), false)) && + WriteHeaderField("path", get_escaped_string(path, false)) && + WriteHeaderField("open", Timestamp(0))) ) + goto write_error; + if ( ! (WriteHeaderField("fields", names) && WriteHeaderField("types", types)) ) goto write_error; } - + return true; write_error: diff --git a/src/logging/writers/Ascii.h b/src/logging/writers/Ascii.h index cf0190aa80..37ec19aba6 100644 --- a/src/logging/writers/Ascii.h +++ b/src/logging/writers/Ascii.h @@ -45,6 +45,7 @@ private: // Options set from the script-level. bool output_to_stdout; bool include_meta; + bool only_single_header_row; char* separator; int separator_len; diff --git a/testing/btest/Baseline/scripts.base.frameworks.logging.ascii-csv/ssh-filtered.log b/testing/btest/Baseline/scripts.base.frameworks.logging.ascii-csv/ssh-filtered.log new file mode 100644 index 0000000000..f59c7c8f54 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.logging.ascii-csv/ssh-filtered.log @@ -0,0 +1,6 @@ +t id.orig_h id.orig_p id.resp_h id.resp_p status country b +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 success unknown - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 - US - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 failure UK - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 - BR - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 failure (empty) T diff --git a/testing/btest/scripts/base/frameworks/logging/ascii-csv.bro b/testing/btest/scripts/base/frameworks/logging/ascii-csv.bro new file mode 100644 index 0000000000..1c10f5fc6b --- /dev/null +++ b/testing/btest/scripts/base/frameworks/logging/ascii-csv.bro @@ -0,0 +1,37 @@ +# +# @TEST-EXEC: bro -b %INPUT +# @TEST-EXEC: cat ssh.log | grep -v PREFIX.*20..- >ssh-filtered.log +# @TEST-EXEC: btest-diff ssh-filtered.log + +module SSH; + +export { + redef enum Log::ID += { LOG }; + + type Log: record { + t: time; + id: conn_id; # Will be rolled out into individual columns. + status: string &optional; + country: string &default="unknown"; + b: bool &optional; + } &log; +} + +event bro_init() +{ + Log::create_stream(SSH::LOG, [$columns=Log]); + + local filter = Log::get_filter(SSH::LOG, "default"); + filter$config = table(["only_single_header_row"] = "T"); + Log::add_filter(SSH::LOG, filter); + + local cid = [$orig_h=1.2.3.4, $orig_p=1234/tcp, $resp_h=2.3.4.5, $resp_p=80/tcp]; + + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $status="success"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $country="US"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $status="failure", $country="UK"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $country="BR"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $b=T, $status="failure", $country=""]); + +} +