diff --git a/TODO.logging b/TODO.logging index 886049b5f0..8d1be59e20 100644 --- a/TODO.logging +++ b/TODO.logging @@ -2,12 +2,22 @@ List of the things not implemented yet: - Removing filters - Filter predicates - - Filter events - Dynamic path function. - Cluster-style remote_print - Rotation support + - Flushing support - Spawning writers in separate threads (not clear if we want that initially). - Not sure if the logging does the right thing with &optional and &default values. Needs testing. + - Seems we could do some of the filter-related type checks + currently done dynamically at startup via a TraversalCallback. There's probably more missing. + +Question: + + * Is giving the record column twice to create_stream too + redundant? (once directly, and once via the event):: + + global ssh_log: event(rec: Log); + log_create_stream(LOG_SSH, SSH::Log, ssh_log); diff --git a/policy/bro.init b/policy/bro.init index 47fa336939..0b727fcdc5 100644 --- a/policy/bro.init +++ b/policy/bro.init @@ -308,19 +308,13 @@ type log_filter: record { # extension is given; the writer will add whatever is # appropiate. path: string &optional; - path_func: function(id: string): string &optional; + path_func: function(id: Log_ID): string &optional; # A subset of column names to record. If not given, all # columns are recorded. include: set[string] &optional; exclude: set[string] &optional; - # An event that is raised whenever the filter is applied - # to an entry. The event receives the same parameter - # as the predicate. It will always be generated, - # independent of what the predicate returns. - ev: event(rec: any) &optional; - # The writer to use. writer: Log_Writer &default=Log_default_writer; }; diff --git a/policy/test-logging.bro b/policy/test-logging.bro index e8ccc7c22e..1a95fed7d2 100644 --- a/policy/test-logging.bro +++ b/policy/test-logging.bro @@ -16,12 +16,15 @@ export { }; } +global ssh_log: event(rec: Log); + event bro_init() { # Create the stream. # First argument is the ID for the stream. # Second argument is the log record type. - log_create_stream(LOG_SSH, SSH::Log); + # Third argument is the log event, which must receive a single argument of type arg2. + log_create_stream(LOG_SSH, SSH::Log, ssh_log); # Add a default filter that simply logs everything to "ssh.log" using the default writer. Log_add_default_filter(LOG_SSH); @@ -36,8 +39,10 @@ event bro_init() local cid = [$orig_h=1.2.3.4, $orig_p=1234/tcp, $resp_h=2.3.4.5, $resp_p=80/tcp]; + local r: Log = [$t=network_time(), $id=cid, $status="success"]; + # Log something. - log_write(LOG_SSH, [$t=network_time(), $id=cid, $status="success"]); + log_write(LOG_SSH, r); log_write(LOG_SSH, [$t=network_time(), $id=cid, $status="failure", $country="US"]); log_write(LOG_SSH, [$t=network_time(), $id=cid, $status="failure", $country="UK"]); log_write(LOG_SSH, [$t=network_time(), $id=cid, $status="success", $country="BR"]); @@ -45,10 +50,11 @@ event bro_init() } -#event log(rec: Log) -# { -# print fmt("Ran the log handler from the same module. Extracting time: %0.6f", rec$t); -# } +event ssh_log(rec: Log) + { + print fmt("Ran the log handler from the same module. Extracting time: %0.6f", rec$t); + print rec; + } # # #module WHATEVER; diff --git a/src/LogMgr.cc b/src/LogMgr.cc index b9675e4e84..0fe40620e1 100644 --- a/src/LogMgr.cc +++ b/src/LogMgr.cc @@ -1,5 +1,6 @@ #include "LogMgr.h" +#include "Event.h" #include "EventHandler.h" #include "NetVar.h" @@ -23,7 +24,6 @@ struct LogMgr::Filter { string name; Func* pred; Func* path_func; - EventHandlerPtr* event; string path; LogWriterDefinition* writer; @@ -40,6 +40,7 @@ struct LogMgr::Filter { struct LogMgr::Stream { string name; RecordType* columns; + EventHandlerPtr event; list filters; ~Stream(); @@ -72,7 +73,7 @@ LogMgr::~LogMgr() delete *s; } -bool LogMgr::CreateStream(EnumVal* stream_id, RecordType* columns) +bool LogMgr::CreateStream(EnumVal* stream_id, RecordType* columns, EventHandlerPtr handler) { // TODO: Should check that the record has only supported types. @@ -91,9 +92,10 @@ bool LogMgr::CreateStream(EnumVal* stream_id, RecordType* columns) streams[idx] = new Stream; streams[idx]->name = stream_id->Type()->AsEnumType()->Lookup(idx); streams[idx]->columns = columns; + streams[idx]->event = handler; columns->Ref(); - DBG_LOG(DBG_LOGGING, "Created new logging stream '%s'", streams[idx]->name.c_str()); + DBG_LOG(DBG_LOGGING, "Created new logging stream '%s', raising event %s", streams[idx]->name.c_str(), streams[idx]->event->Name()); return true; } @@ -242,12 +244,6 @@ bool LogMgr::AddFilter(EnumVal* stream_id, RecordVal* fval) filter->pred = path_func ? path_func->AsFunc() : 0; filter->writer = ld; - if ( event ) - { - // TODO: Implement - filter->event = 0; - } - // Build the list of fields that the filter wants included, including // potentially rolling out fields. Val* include = fval->Lookup(rtype->FieldOffset("include")); @@ -295,7 +291,6 @@ bool LogMgr::AddFilter(EnumVal* stream_id, RecordVal* fval) DBG_LOG(DBG_LOGGING, " writer : %s", ld->name); DBG_LOG(DBG_LOGGING, " path : %s", filter->path.c_str()); DBG_LOG(DBG_LOGGING, " path_func : %s", (filter->path_func ? "set" : "not set")); - DBG_LOG(DBG_LOGGING, " event : %s", (filter->event ? "set" : "not set")); DBG_LOG(DBG_LOGGING, " pred : %s", (filter->pred ? "set" : "not set")); for ( int i = 0; i < filter->num_fields; i++ ) @@ -344,6 +339,14 @@ bool LogMgr::Write(EnumVal* stream_id, RecordVal* columns) return false; } + // Raise the log event. + if ( stream->event ) + { + val_list* vl = new val_list; + vl->append(columns->Ref()); + mgr.QueueEvent(stream->event, vl, SOURCE_LOCAL); + } + // Send to each of our filters. for ( list::iterator i = stream->filters.begin(); i != stream->filters.end(); ++i ) { @@ -351,12 +354,6 @@ bool LogMgr::Write(EnumVal* stream_id, RecordVal* columns) string path = filter->path; - if ( filter->event ) - { - // XXX Raise event here. - // TODO: Actually, the filter should be an attribute of the stream, right? - } - if ( filter->pred ) { // XXX Check predicate here. diff --git a/src/LogMgr.h b/src/LogMgr.h index b62dd5ff35..28a06821db 100644 --- a/src/LogMgr.h +++ b/src/LogMgr.h @@ -5,6 +5,7 @@ #define LOGMGR_H #include "Val.h" +#include "EventHandler.h" // One value per writer type we have. namespace LogWriterType { @@ -54,7 +55,7 @@ public: // These correspond to the BiFs visible on the scripting layer. The // actual BiFs just forward here. - bool CreateStream(EnumVal* stream_id, RecordType* columns); + bool CreateStream(EnumVal* stream_id, RecordType* columns, EventHandlerPtr handler); bool AddFilter(EnumVal* stream_id, RecordVal* filter); bool RemoveFilter(EnumVal* stream_id, StringVal* filter); bool Write(EnumVal* stream_id, RecordVal* columns); diff --git a/src/bro.bif b/src/bro.bif index 52c54706c6..e52e30ab1d 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -488,21 +488,45 @@ function logging_log%(index: string, rec: any%): any #include "LogMgr.h" %%} -function log_create_stream%(id: Log_ID, columns: any%) : bool +function log_create_stream%(id: Log_ID, columns: any, ev: any%) : bool %{ if ( columns->Type()->Tag() != TYPE_TYPE ) { run_time("log columns must be a type"); - return new Val(0, TYPE_BOOL); + return new Val(0, TYPE_BOOL); } if ( columns->Type()->AsTypeType()->Type()->Tag() != TYPE_RECORD ) { run_time("log columns must be a record type"); - return new Val(0, TYPE_BOOL); + return new Val(0, TYPE_BOOL); } - bool result = log_mgr->CreateStream(id->AsEnumVal(), columns->Type()->AsTypeType()->Type()->AsRecordType()); + if ( ev->Type()->Tag() != TYPE_FUNC || ! ev->Type()->AsFuncType()->IsEvent() ) + { + run_time("event argument is not of event type"); + return new Val(0, TYPE_BOOL); + } + + FuncType* et = ev->Type()->AsFuncType(); + type_list* args = et->ArgTypes()->Types(); + + if ( args->length() != 1 ) + { + run_time("event must take a single argument"); + return new Val(0, TYPE_BOOL); + } + + if ( ! same_type((*args)[0], columns->Type()->AsTypeType()->Type()) ) + { + run_time("event argument type does not match log record type"); + return new Val(0, TYPE_BOOL); + } + + EventHandler* handler = event_registry->Lookup(ev->AsFunc()->GetID()->Name()); + assert(handler); + + bool result = log_mgr->CreateStream(id->AsEnumVal(), columns->Type()->AsTypeType()->Type()->AsRecordType(), handler); return new Val(result, TYPE_BOOL); %}