bro框架-- 日志框架
原文地址:
https://www.bro.org/sphinx/frameworks/logging.html
日志框架(Logging Framework)
Bro有一个灵活的基于键值的日志接口,这个接口允许对日志记录了什么内容以及是如何记录的进行细致的控制。本文描述了如何定制及拓展日志。
术语(Terminology)
Bro的日志接口主要是基于三种抽象之上的。
流(Streams):
一个日志流对应一个日志。它定义了日志所包含的域的名称和类型的结合。比如记录连接总结的conn流以及记录HTTP活动的http流。
过滤器(Filters):
每个流都有一系列附加到它上面的过滤器,这些过滤器可以决定写出哪些信息。默认情况下每个流都有一个默认过滤器将一切直接记录到磁盘中去。然而,也可以添加其它的过滤器仅记录日志记录的一个子集,写到不同的输出中去或者设置一个轮流时间。如果一个流的所有过滤器都被移除了,这个流的输出就会被禁止。
写者(writer):
每一个过滤器都有一个写者。写者定义了正在被记录的信息的实际输出格式。默认的写者是ASCII写者,它产生了由tab分隔的ASCII文件。也有其它的写者,比如二进制输出或者直接记录到数据库中。
有好几种不同的方法来定制Bro的日志:你可以创建一个新的日志流,可以给现有的日志拓展新的域,可以把过滤器应用给已存在的日志流,或者通过设定日志写者选项来定制输出格式。这些方法都在本文中有介绍。
流(Streams)
为了将数据记录到一个新的日志流中,要做接下来的所有步骤:
1.一个record类型的定义必须包含记录了的所有域(默认情况下record是Info)
2.一个日志流ID(名为"Log::ID"的enum类型)的定义必须保证它标识一个新的日志流。
3.一个日志流必须用Log::create_stream方法来创建。
4.记录的数据变得可获取的时,必须调用Log::write方法
在下面的例子中,我们创建一个新的叫做“Foo”的模块,这个模块创建一个新的日志流。
module Foo;
export {
# Create an ID for our new stream. By convention, this is
# called "LOG".
redef enum Log::ID += { LOG };
# Define the record type that will contain the data to log.
type Info: record {
ts: time &log;
id: conn_id &log;
service: string &log &optional;
missed_bytes: count &log &default=0;
};
}
# Optionally, we can add a new field to the connection record so that
# the data we are logging (our "Info" record) will be easily
# accessible in a variety of event handlers.
redef record connection += {
# By convention, the name of this new field is the lowercase name
# of the module.
foo: Info &optional;
};
# This event is handled at a priority higher than zero so that if
# users modify this stream in another script, they can do so at the
# default priority of zero.
event bro_init() &priority=5
{
# Create the stream. This adds a default filter automatically.
Log::create_stream(Foo::LOG, [$columns=Info, $path="foo"]);
}
在Info记录的定义的上方,注意到每个域都有&log属性。如果没有这个属性的话,这个域就不会出现在日志输出里面。有一个域有&optional属性。这表示这个域在日志记录写入之前未必被赋值。最后,有一个域有&default属性有个自动赋给它的默认值。
这时唯一缺少的就是调用Log::write方法来给日志框架发送数据。实际的事件处理函数在哪里调用会取决于你的数据在哪里变得可取,在这个例子中,事件connection_established会提供我们的数据,此外我们会存储一份数据的拷贝到connection记录中去。
event connection_established(c: connection)
{
local rec: Foo::Info = [$ts=network_time(), $id=c$id];
# Store a copy of the data in the connection record so other
# event handlers can access it.
c$foo = rec;
Log::write(Foo::LOG, rec);
}
如果你用Bro运行这个脚本的话,会产生一个日志文件foo.log。尽管我们仅把四个域在上面的"Info"记录中,实际上的日志输出会包含七个域,这是因为其中一个域(名为"id"的那个)它本身也是一个记录类型。由于conn_id记录有四个域,这些域在日志输出中都是一个单独的列。注意这些域在日志输出中的命名和我们指Bro脚本中相同的域的引用名称有细微的区别。比如取Bro脚本中的第一个域conn_id,我们用id$orig_h,但这个域在日志输出中叫做id.orig_h。
当你写一个将数据添加到connection记录中的脚本的时候,必须要注意何时存储数据以及要存储多长的数据。正常情况下,存储到connection记录中的数据会一直在那里(只要这个connection还在)。在连接结束之前就把数据给删除了(经实践检验)。
给日志添加域(Add Fields to a Log)
你可以通过拓展定义日志内容的记录类型开增加新的域到日志中,并且可以在写日志记录之前给新的域设置一个值。
比如我们想给Conn::Info添加一个布尔类型域is_private,这个域表示源IP地址是否是RFC1918所定义的地址空间的一部分:
# Add a field to the connection log record.
redef record Conn::Info += {
## Indicate if the originator of the connection is part of the
## "private" address space defined in RFC1918.
is_private: bool &default=F &log;
};
正如这个例子所示,当拓展一个日志流的"Info"记录,每个域必须要么声明一个&default值,要么声明为&optional。此外,你需要添加&log属性,要不然的话这个域就不会出现在日志文件中。
现在我们需要设置这个域。随着我们扩展的日志不同,有很多细节也不同,一般来说选择一个合适的事件来设置额外的域很重要,这是因为我们需要保证在日志记录写下来之前就保证这些域已经被设置。有时,使用和写日志记录同样的事件也是正确的选择,但需要给它更高的优先级(为了保证添加额外域的事件处理器在写日志记录的事件处理器之前执行)。
在这个例子中,由于当一个连接的状态从内存中移除的时候才会生成它的总结(a connection's summary),我们可以在正确地设置我们的域的时候添加一个新的事件处理器。
event connection_state_remove(c: connection)
{
if ( c$id$orig_h in Site::private_address_space )
c$conn$is_private = T;
}
现在conn.log将会展示一个类型为bool的新的域is_private。如果你看一下定义连接日志流的Bro脚本的话,你会发现调用Log::write的事件处理器和本例中设置额外的域的事件处理器处理的是同样的事件,但是优先级比本例中的低(比如,日志记录在我们给is_private域赋值之后才做)。
这样拓展日志,我们需要一些关于脚本创建的日志流是如何组织它的状态保持的知识。大多数标准Bro脚本将它们的日志状态附加到connection记录(当它可以被访问的时候)中,就像上面的c$conn。比如,HTTP分析添加了一个类型为HTTP::Info的域http到connection记录中。
定义一个日志事件(Define a Logging Event)
有时对记录的信息做一些额外的分析也很有用。对这些例子而言,一个流可以指定一个每当有一个日志记录写入的时候就生成的事件。我们修改一下示例模块:
module Foo;
export {
redef enum Log::ID += { LOG };
type Info: record {
ts: time &log;
id: conn_id &log;
service: string &log &optional;
missed_bytes: count &log &default=0;
};
# Define a logging event. By convention, this is called
# "log_
global log_foo: event(rec: Info);
}
event bro_init() &priority=5
{
# Specify the "log_foo" event here in order for Bro to raise it.
Log::create_stream(Foo::LOG, [$columns=Info, $ev=log_foo,
$path="foo"]);
}
所有的Bro的默认日志流定义这样的一个事件。举个例子,connection日志流生成Conn::log_conn事件。你可以以此为例,当一个到特定目标的连接超出了一定的时间的时候:
redef enum Notice::Type += {
## Indicates that a connection remained established longer
## than 5 minutes.
Long_Conn_Found
};
event Conn::log_conn(rec: Conn::Info)
{
if ( rec?$duration && rec$duration > 5mins )
NOTICE([$note=Long_Conn_Found,
$msg=fmt("unusually long conn to %s", rec$id$resp_h),
$id=rec$id]);
}
这些事件可以作为对Bro日志的后期处理(从外部使用Perl脚本)的一个选择。大多数这种外部脚本在离线之后才做,也可以在Bro实时运作时在Bro内部做。
使流失效(Disable a Stream)
”关闭“日志的一种方法就是使一个流完全失效。比如,下面的例子将阻止写conn.log。
event bro_init()
{
Log::disable_stream(Conn::LOG);
}
注意这必须在流创建之后才能运行,所以这个事件处理器的优先级必须低于流创建的事件处理器的优先级。
过滤器(Filters)
一个流有一个或者多个附加于它的过滤器(没有过滤器的流不会产生任何日志输出)。当一个流被创建,它会自动获取一个附加于它的默认过滤器。这个默认过滤器可以被移除或者被设置,也可以添加别的过滤器进来。这是由Log::add_filter或者Log::remove_filter方法完成。这一节会展示如何使用过滤器来做一些任务,譬如给一个日志文件重命名,将输出分割成多个文件,控制写何种记录,或者设置一个定制周转时间。
给日志文件重命名(Rename Log File)
正常情况下,一个给定的日志流的日志文件流是由这个流是何时创建的决定,除非你显式地通过增加一个过滤器来指定一个不同的(文件名)。
改变日志文件名的最简单的方法就是用一个指定“path”域的过滤器来替换默认日志过滤器。在本例中,"conn.log"被改为"myconn.log"。
event bro_init()
{
# Replace default filter for the Conn::LOG stream in order to
# change the log filename.
local f = Log::get_filter(Conn::LOG, "default");
f$path = "myconn";
Log::add_filter(Conn::LOG, f);
}
日志过滤器的“path”域不包含文件扩展名。扩展部分有日志写者(log writer)决定。
增加一个新的日志文件(Add a New Log File)
正常情况下,一个日志流仅写到一个日志文件中。然而,你可以添加过滤器以便流可以写到多个文件中去。这在你想将写入新文件的域的集合限制起来的时候是很有用的。
在这个例子中,给Conn::LOG流增加了一个新的过滤器,这个过滤器写两个域到一个新的日志文件中。
event bro_init()
{
# Add a new filter to the Conn::LOG stream that logs only
# timestamp and originator address.
local filter: Log::Filter = [$name="orig-only", $path="origs",
$include=set("ts", "id.orig_h")];
Log::add_filter(Conn::LOG, filter);
}
注意"include"过滤器属性指定了一个集合,这个集合限制了给出的域。对应于Conn::Info记录中的名称(因为"id"本身就是一条记录,我们可以用点(dot)指定"id"的一个域)。
使用上面的代码,除了conn.log,你还会得到一个新的日志文件origs.log,这个看起来很像conn.log,但是origs.log仅含有"include"过滤器属性中指明的域。
如果你想略过一些域并保留剩下的,有个对应的exclude过滤器属性,你可以用这个属性来代替include,列出你不感兴趣的域。
如果你让origs.log成为这个流的唯一的日志文件,可以移除默认的过滤器:
event bro_init()
{
# Remove the filter called "default".
Log::remove_filter(Conn::LOG, "default");
}
动态决定日志路径(Determine Log Path Dynamically)
不使用"path"过滤器属性,过滤器可以基于日志记录的地方来动态地决定输出路径。这就使得下面的操作成为可能。将记录本地和远程连接到不同的文件中。为了达到这个目的,你可以定义一个返回所需要的路径的方法,并使用"path_func"过滤器属性:
# Note: if using BroControl then you don't need to redef local_nets.
redef Site::local_nets = { 192.168.0.0/16 };
function myfunc(id: Log::ID, path: string, rec: Conn::Info) : string
{
# Return "conn-local" if originator is a local IP, otherwise
# return "conn-remote".
local r = Site::is_local_addr(rec$id$orig_h) ? "local" : "remote";
return fmt("%s-%s", path, r);
}
event bro_init()
{
local filter: Log::Filter = [$name="conn-split",
$path_func=myfunc, $include=set("ts", "id.orig_h")];
Log::add_filter(Conn::LOG, filter);
}
运行它会产生两个新文件,conn-local.log和conn-remote.log,和对应的入口(Site::local_nets会指明你的本地网络)。你可以作更多的拓展,比如通过子网甚至通过IP地址来记录信息。然而这很容易很快地产生很多文件,要小心谨慎。
方法myfunc有一个缺点:它仅能够为Conn::LOG流所使用,由于记录类型写定在它的参数列表中了。然而,Bro允许这样。。
function myfunc(id: Log::ID, path: string,
rec: record { id: conn_id; } ) : string
{
local r = Site::is_local_addr(rec$id$orig_h) ? "local" : "remote";
return fmt("%s-%s", path, r);
}
过滤日志记录(Filter Log Records)
我们已经看到了如何定制记录的列,但你也可以通过提供一个有每条记录调用的断定函数来控制有哪些记录会写出:
unction http_only(rec: Conn::Info) : bool
{
# Record only connections with successfully analyzed HTTP traffic
return rec?$service && rec$service == "http";
}
event bro_init()
{
local filter: Log::Filter = [$name="http-only", $path="conn-http",
$pred=http_only];
Log::add_filter(Conn::LOG, filter);
}
这会产生一个新的日志文件conn-http.log,它仅包含从conn.log得到的解析HTTP流量的记录。
轮转(Rotation)
对所有的过滤器而言,日志的轮转间隔是全局可控的,通过重新定义Log::default_rotation_interval选项就可以(当使用BroControl的时候,这个选项就会自动通过BroControl配置而设置完成)。
或者Log::Filter实例设置interv域。下例改变Conn::LOG流的默认过滤器轮转。
event bro_init()
{
local f = Log::get_filter(Conn::LOG, "default");
f$interv = 1 min;
Log::add_filter(Conn::LOG, f);
}
写者(Writers)
每一个过滤器都有一个写者。如果在你给流增加过滤器的时候没有指定写者的话,就会使用默认的ASCII写者。
有两种方法来指定非默认写者。为所有的日志过滤器改变默认的写者,可以通过重定义Log::default_writer选项来实现。你也可以为过滤器设置它的“writer”域,这就是针对某个过滤器而言的。需要用其它的选项的话,请参考写者的文档。
ASCII写者(ASCII Writer)
默认情况下,ASCII写者输出以数行元数据开头的日志文件,后面跟着真正的日志输出。元数据描述了日志文件的格式,日志的路径(无扩展名),也指出了日志文件创建的时间以及Bro结束写它的时间。ASCII写者有很多很多可用于指定它的输出的格式的选项(请参考https://www.bro.org/sphinx/scripts/base/frameworks/logging/writers/ascii.bro.html)。如果你改变数据格式选项,要注意检验你的后续处理脚本能不能狗辨别你的日志文件。
有些写者选项是全局的(影响所有使用日志写者的过滤器)。比如,将所有的ASCII日志的输出格式变为JSON格式。
redef LogAscii::use_json = T;
有些写者选项是针对过滤器的(filter-specific)(仅影响显式指出选项的过滤器)。举个例子,仅改变conn.log的输出格式:
event bro_init()
{
local f = Log::get_filter(Conn::LOG, "default");
# Use tab-separated-value mode
f$config = table(["tsv"] = "T");
Log::add_filter(Conn::LOG, f);
}
其它写者(Other Writers)
Bro支持下列的内置输出格式:
https://www.bro.org/sphinx/frameworks/logging-input-sqlite.html
可以通过外部插件添加的读者:
https://www.bro.org/sphinx/components/bro-plugins/README.html
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
