亚马逊AWS官方博客

预处理日志以便在 Amazon ES 中进行异常检测

Amazon Elasticsearch Service(Amazon ES)支持实时的异常检测,它使用机器学习(ML)主动检测实时流数据中的异常情况。当分析应用程序日志时,它可以用来检测例如异常高的错误率或请求数量的突然变化等异常状况。例如,来自特定地区的食品配送订单数量的突增可能是由于天气变化或该地区用户遇到技术故障造成的。发现这种异常情况可以促进对事件的快速调查和补救。

Amazon ES 的异常检测功能使用 Random Cut Forest 算法。这是一种无监督学习算法,它通过数值类型的输入数据点构造决策树,以检测数据中的离群值,这些离群值被视为异常。为了检测日志中的异常情况,我们必须将基于文本的日志文件转换为数值,以便可以用此算法解释它们。在机器学习术语中,这种转换通常称为数据预处理。有很多种数据预处理方法可供使用,在这篇博文中,我将介绍了一些适合日志的方法。

要实现本文中描述的方法,您需要一个将日志文件提取到 Amazon ES 域的日志聚合管道。有关提取 Apache Web 日志的信息,请参阅博客使用 Kinesis Firehose 将 Apache Web 日志发送到 Amazon Elasticsearch Service。有关获取和分析 Amazon Simple Storage Service (Amazon S3) 服务器访问日志的类似方法,请参阅博客使用 Amazon ES 分析 Amazon S3 服务器访问日志

现在,让我们讨论一些在处理日志文件的复杂结构时可以使用的数据预处理方法。

 

将行记录到 JSON 文档

尽管日志都是文本文件,但通常日志文件对日志消息有一些记录结构,例如每行有一个日志条目。如下图所示,可以解析日志文件中的一行并将其作为包含多个字段的文档存储在 Amazon ES 索引中。此图是如何将 Amazon S3 访问日志中的条目转换为 JSON 文档的示例。

尽管您可以将 JSON 文档(例如上图)原样提取到 Amazon ES,但是某些文本字段需要进一步的预处理才能用于执行异常检测。

 

定类型(nominal)

假设您的应用程序收到的主要是 GET 请求,POST 请求的数量要少得多。根据 OWASP 安全建议,也建议禁用 TRACE 和 TRACK 请求方法,因为这些方法可能被滥用于跨站点跟踪。如果您想检测服务器日志中何时出现异常的 HTTP 请求,或者先前数量通常很少的 HTTP 请求数何时出现激增,则可以使用上述 JSON 文档中的 request_uri 或 operation 字段。这些字段包含 HTTP 请求方法,但是您必须提取此类字段,然后将其转换为可用于异常检测的数值格式。

这些字段只有少数不同的值,而且这些值没有任何特定的先后顺序。如果我们简单地将 HTTP 方法转换为有序的数字列表,例如 GET = 1、POST = 2 等,我们可能会令异常检测算法误以为 POST 比 GET 大,或 GET + GET 等于 POST。预处理这些字段的更好方法是独热编码(one-hot encoding)。这种方法是将单个文本字段转换为多个二进制字段,每个二进制字段代表原始文本字段的一种可能值。在我们的例子中,这种独热编码的结果是一组共九个二进制字段。如果原始日志中字段的值为 HEAD,则只有预处理数据中的 HEAD 字段的值为 1,而所有其他字段均为零。下表显示了一些示例。

原始日志消息 预处理成多个独热编码字段
HTTP 请求方法 GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATCH
GET 1 0 0 0 0 0 0 0 0
POST 0 0 1 0 0 0 0 0 0
OPTIONS 0 0 0 0 0 0 1 0 0

然后,Amazon ES 异常检测功能可以处理这些生成的字段数据,以便在应用程序接收的 HTTP 请求模式发生变化时检测到异常情况,例如,数量异常高的 DELETE 请求。

 

大量定类型

许多日志文件包含 HTTP 响应代码、错误代码或某些其他类型的数字代码。这些代码没有任何特定的顺序,但可能取值的数量相当大。在这种情况下,单独使用独热编码不合适,因为它可能导致预处理数据中的字段数量爆炸。

以 HTTP 响应代码为例。这些值是无序的,这意味着并没有特别的理由将 200 设为 OK,400 设为错误请求。就 HTTP 响应代码而言,200 + 200 != 400。但是,字段可能的取值数量相当多 — 超过 60 个。如果我们使用独热编码技术,我们最终会在这 1 个字段中创建 60 多个字段,这会让数据迅速变得无法管理。

然而,根据对 HTTP 状态代码的了解,我们知道这些代码从定义上可以分为五个范围。范围为 100–199 的代码是信息性响应,代码 200–299 表示成功完成请求,300–399 是重定向,400–499 是客户端错误,500–599 是服务器错误。我们可以利用这些知识,将原始值减少到五个值,每个范围一个值(1xx、2xx、3xx、4xx 和 5xx)。现在,这一组五个可能值更容易处理。这些值纯粹是名义的。因此,我们还可以按照上一节所述对这些值进行独热编码。在进行这样的归类和独热编码过程之后的结果类似下表。

原始日志消息 在归类和独热编码后预处理成多个字段
HTTP 响应状态代码 1xx 2xx 3xx 4xx 5xx
100(继续) 1 0 0 0 0
101(切换协议) 1 0 0 0 0
200(OK) 0 1 0 0 0
202(已接受) 0 1 0 0 0
301(永久移动) 0 0 1 0 0
304(未修改) 0 0 1 0 0
400(错误请求) 0 0 0 1 0
401(未经授权) 0 0 0 1 0
404(未找到) 0 0 0 1 0
500(内部服务器错误) 0 0 0 0 1
502(错误网关) 0 0 0 0 1
503(服务不可用) 0 0 0 0 1

此预处理的数据现在适用于异常检测。4xx 错误数量的上升或 2xx 响应数量的下降可能对检测特别重要。

以下 Python 代码片段显示了如何对 HTTP 响应状态代码进行归类和独热编码:

def http_status_bin_one_hot_encoding(http_status):
    # returns one hot encoding based on http response status bin
    # bins are: 1xx, 2xx, 3xx, 4xx, 5xx
    if 100 <= http_status <= 199: # informational responses
        return (1, 0, 0, 0, 0)
    elif 200 <= http_status < 299: # successful responses
        return (0, 1, 0, 0, 0)
    elif 300 <= http_status < 399: # redirects
        return (0, 0, 1, 0, 0)
    elif 400 <= http_status < 499: # client errors
        return (0, 0, 0, 1, 0)
    elif 500 <= http_status < 599: # server errors
        return (0, 0, 0, 0, 1)

http_1xx, http_2xx, http_3xx, http_4xx, http_5xx = http_status_bin_one_hot_encoding(status)

log_entry = {
    'timestamp': timestamp,
    'bucket': "somebucket",
    'key': "somekey",
    'operation': "REST.GET.VERSIONING",
    'request_uri': "GET /awsexamplebucket1?versioning HTTP/1.1",
    'status_code': status,
    'http_1xx': http_1xx,
    'http_2xx': http_2xx,
    'http_3xx': http_3xx,
    'http_4xx': http_4xx,
    'http_5xx': http_5xx,
    'error_code': "-",
    'bytes_sent': 113,
    'object_size': 0
}

 

定序型(ordinal)

日志文件中的某些文本字段包含具有相对顺序的值。例如,日志级别字段可能包含 TRACE、DEBUG、INFO、WARN、ERROR 和 FATAL 等值。这也是日志消息严重性递增的顺序。如下表所示,这些字符串值可以保留此相对顺序转换为数值。

日志级别(原始日志消息) 预处理的日志级别
TRACE 1
DEBUG 2
INFO 3
WARN 4
ERROR 5
FATAL 6

 

IP 地址

日志文件通常具有可以包含大量值的 IP 地址,使用上一段中描述的方法将这些值分类到一起是没有意义的。但是,从地理位置的角度来看这些 IP 地址可能是有意义的。如果应用程序开始从不寻常的地理位置访问,这对检测异常情况可能非常重要。如果日志中没有直接提供国家/地区或城市代码等地理信息,则可以通过使用第三方服务对 IP 地址进行地理定位来获取此信息。实际上,这是将大量 IP 地址分类为少得多的国家/地区或城市代码的过程。尽管这些国家/地区和城市代码仍然是定类型数值,但它们可以与 Amazon ES 的基数(cardinality)汇总一起使用。

在将这些预处理技术应用于示例 Amazon S3 服务器访问日志之后,我们将获得处理完成的 JSON 日志数据:

{
    "bucket_owner": "", //string
    "bucket": "awsexamplebucket1", //string
    "timestamp": "06/Feb/2019:00:00:38 +0000",
    "remote_ip": "192.0.2.3", //string
    "country_code": 100, //numeric field generated during pre-processing
    "requester": "", //string
    "request_id": "3E57427F3EXAMPLE",
    "operation": "REST.GET.VERSIONING",
    "key": "-",
    "request_uri": "GET /awsexamplebucket1?versioning HTTP/1.1",
    "http_method_get": 1, //nine one-hot encoded fields generated during pre-processing
    "http_method_post": 0,
    "http_method_put": 0,
    "http_method_delete": 0,
    "http_method_head": 0,
    "http_method_connect": 0,
    "http_method_options": 0,
    "http_method_trace": 0,
    "http_method_patch": 0,
    "http_status": 200,
    "http_1xx": 0, //five one-hot encoded fields generated during pre-processing
    "http_2xx": 1,
    "http_3xx": 0,
    "http_4xx": 0,
    "http_5xx": 0,
    "error_code": "-",
    "bytes_sent": 113,
    "object_size": "-",
    "total_time": 7,
    "turn_around_time": "-",
    "referer": "-",
    "user_agent": "S3Console/0.4",
    "version_id": "-",
    "host_id": "", //string
    "signature_version": "SigV2",
    "cipher_suite": "ECDHE-RSA-AES128-GCM-SHA256",
    "authentication_type": "AuthHeader",
    "host_header": "awsexamplebucket1.s3.us-west-1.amazonaws.com",
    "tls_version": "TLSV1.1"
}

这些数据现在可以被提取并索引到 Amazon ES 域中。设置日志预处理管道后,下一个要配置的是异常检测器。Amazon ES 异常检测允许您在单个异常检测器中指定最多五个特征(数据中的字段)。这意味着异常检测器可以根据最多五个字段的值来学习数据中的模式。

 

聚合

需要为每个特征指定适当的聚合函数。这是因为异常检测器汇总了每个检测器间隔内提取的所有文档的值以产生单个聚合值,然后该值用作自动学习数据模式的算法的输入。下图描述了此过程。

配置正确的特征和相应的聚合函数后,异常检测器开始初始化。在处理足够大量的数据后,检测器进入运行状态。

为了帮助您开始对自己的日志进行异常检测,下表显示了可能对某些常见日志字段有意义的预处理技术和聚合函数。

日志字段名称 预处理 聚合
HTTP 响应状态代码 独热编码 总和
客户端 IP 地址 将 IP 地理位置对应到国家/地区或城市代码 基数
日志消息级别(INFO、WARN、ERR、FATAL 等) 独热编码 总和
错误或异常名称 如果有大量可能的值,则映射到数字代码、额外的分类和独热编码 如果使用单个数字代码字段则用基数;如果使用独热编码则用总和
对象大小/发送的字节/内容长度 无,使用数值本身 最小、最大、平均
要监控一般流量水平,您可以使用任何数值字段(如响应代码或发送的字节数)来计算每个检测器间隔的日志条目数 无,使用数值本身 count (value_count) — 只需计算在此字段中具有值的文档数

 

结论

IT 团队可以使用 Amazon ES 的异常检测功能对应用程序和基础设施日志实施主动监控和警报。只需要基本脚本或编程技能就能够实现本文中讨论的日志预处理技术 — 无需对机器学习或数据科学有深入的了解。异常检测功能在运行 Elasticsearch 7.4 版或更高版本的 Amazon ES 域中可用。要开始使用,请参阅 Amazon Elasticsearch Service 中的异常检测

 

本篇作者

Kapil Pendse

Amazon Web Services(新加坡)的高级解决方案构架师,拥有超过 15 年的跨多个领域构建技术解决方案的经验,例如云计算、嵌入式系统和机器学习。在空闲时间,Kapil 喜欢沿着新加坡的沿海公园骑行,偶尔享受水獭的陪伴。