Skip to content

Latest commit

 

History

History
222 lines (144 loc) · 19.4 KB

File metadata and controls

222 lines (144 loc) · 19.4 KB

十四、使用 Apache 和其他日志文件

在本章中,您将学习日志文件。您将学习如何解析日志文件。您还将了解为什么需要在程序中编写异常。解析不同文件的不同方法也很重要。您还将了解ErrorLogAccessLog。最后,您将了解如何解析其他日志文件。

在本章中,您将学习以下内容:

  • 解析复杂日志文件
  • 例外的必要性
  • 解析不同文件的技巧
  • 错误日志
  • 访问日志
  • 解析其他日志文件

解析复杂日志文件

首先,我们将研究解析复杂日志文件的概念。解析日志文件是一项具有挑战性的任务,因为大多数日志文件都是纯文本格式,并且该格式不遵循任何规则。可以在不显示任何警告的情况下修改这些文件。用户可以决定在日志文件中存储什么样的数据,以何种格式存储,以及由谁开发应用程序。

在继续讨论日志解析或更改日志文件中配置的示例之前,首先我们必须了解典型日志文件中的内容。根据我们必须做出的决定,我们将学习如何操纵或从中获取信息。我们还可以在日志文件中查找公共术语,以便使用这些公共术语获取数据。

通常,您会看到日志文件中生成的大部分内容都是由应用程序容器生成的,还包括系统访问状态的条目(换句话说,注销和登录)或通过网络访问的系统条目。因此,当通过网络远程访问系统时,此类远程连接的条目将保存到日志文件中。让我们举一个这样的例子。我们已经有一个名为access.log的文件,其中包含一些日志信息

那么,让我们创建一个read_apache_log.py脚本,并在其中写入以下内容:

def read_apache_log(logfile):
 with open(logfile) as f: log_obj = f.read() print(log_obj) if __name__ == '__main__':
 read_apache_log("access.log")

运行脚本,您将获得如下输出:

student@ubuntu:~$ python3 read_apache_log.py Output: 64.242.88.10 - - [07/Mar/2004:16:05:49 -0800] "GET /twiki/bin/edit/Main/Double_bounce_sender?topicparent=Main.ConfigurationVariables HTTP/1.1" 401 12846 64.242.88.10 - - [07/Mar/2004:16:06:51 -0800] "GET /twiki/bin/rdiff/TWiki/NewUserTemplate?rev1=1.3&rev2=1.2 HTTP/1.1" 200 4523 64.242.88.10 - - [07/Mar/2004:16:10:02 -0800] "GET /mailman/listinfo/hsdivision HTTP/1.1" 200 6291 64.242.88.10 - - [07/Mar/2004:16:11:58 -0800] "GET /twiki/bin/view/TWiki/WikiSyntax HTTP/1.1" 200 7352 64.242.88.10 - - [07/Mar/2004:16:20:55 -0800] "GET /twiki/bin/view/Main/DCCAndPostFix HTTP/1.1" 200 5253 64.242.88.10 - - [07/Mar/2004:16:23:12 -0800] "GET /twiki/bin/oops/TWiki/AppendixFileSystem?template=oopsmore&param1=1.12&param2=1.12 HTTP/1.1" 200 11382 64.242.88.10 - - [07/Mar/2004:16:24:16 -0800] "GET /twiki/bin/view/Main/PeterThoeny HTTP/1.1" 200 4924 64.242.88.10 - - [07/Mar/2004:16:29:16 -0800] "GET /twiki/bin/edit/Main/Header_checks?topicparent=Main.ConfigurationVariables HTTP/1.1" 401 12851 64.242.88.10 - - [07/Mar/2004:16:30:29 -0800] "GET /twiki/bin/attach/Main/OfficeLocations HTTP/1.1" 401 12851 64.242.88.10 - - [07/Mar/2004:16:31:48 -0800] "GET /twiki/bin/view/TWiki/WebTopicEditTemplate HTTP/1.1" 200 3732 64.242.88.10 - - [07/Mar/2004:16:32:50 -0800] "GET /twiki/bin/view/Main/WebChanges HTTP/1.1" 200 40520 64.242.88.10 - - [07/Mar/2004:16:33:53 -0800] "GET /twiki/bin/edit/Main/Smtpd_etrn_restrictions?topicparent=Main.ConfigurationVariables HTTP/1.1" 401 12851 64.242.88.10 - - [07/Mar/2004:16:35:19 -0800] "GET /mailman/listinfo/business HTTP/1.1" 200 6379 …..

在前面的示例中,我们创建了一个read_apache_log函数来读取 Apache 日志文件。在其中,我们打开了一个日志文件,然后在其中打印日志条目。在定义了read_apache_log()函数之后,我们在主函数中用 Apache 日志文件的名称调用了它。在我们的例子中,Apache 日志文件名为access.log

在读取了access.log文件中的日志条目之后,现在我们将解析日志文件中的 IP 地址。为此,创建一个parse_ip_address.py脚本,并在其中写入以下内容:

import re from collections import Counter r_e = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' with open("access.log") as f:
 print("Reading Apache log file") Apache_log = f.read() get_ip = re.findall(r_e,Apache_log) no_of_ip = Counter(get_ip) for k, v in no_of_ip.items(): print("Available IP Address in log file " + "=> " + str(k) + " " + "Count "  + "=> " + str(v))

运行脚本,您将获得如下输出:

student@ubuntu:~/work/Chapter_15$ python3 parse_ip_address.py Output: Reading Apache log file Available IP Address in log file => 64.242.88.1 Count => 452 Available IP Address in log file => 213.181.81.4 Count => 1 Available IP Address in log file => 213.54.168.1 Count => 12 Available IP Address in log file => 200.160.249.6 Count => 2 Available IP Address in log file => 128.227.88.7 Count => 14 Available IP Address in log file => 61.9.4.6 Count => 3 Available IP Address in log file => 212.92.37.6 Count => 14 Available IP Address in log file => 219.95.17.5 Count => 1 3Available IP Address in log file => 10.0.0.1 Count => 270 Available IP Address in log file => 66.213.206.2 Count => 1 Available IP Address in log file => 64.246.94.1 Count => 2 Available IP Address in log file => 195.246.13.1 Count => 12 Available IP Address in log file => 195.230.181.1 Count => 1 Available IP Address in log file => 207.195.59.1 Count => 20 Available IP Address in log file => 80.58.35.1 Count => 1 Available IP Address in log file => 200.222.33.3 Count => 1 Available IP Address in log file => 203.147.138.2 Count => 13 Available IP Address in log file => 212.21.228.2 Count => 1 Available IP Address in log file => 80.58.14.2 Count => 4 Available IP Address in log file => 142.27.64.3 Count => 7 ……

在前面的示例中,我们创建了 Apache 日志解析器来确定服务器上的某些特定 IP 地址及其请求数。因此,很明显,我们不希望 Apache 日志文件中包含完整的日志条目,我们只希望从日志文件中获取 IP 地址。要做到这一点,我们必须定义一个模式来搜索 IP 地址,我们可以通过使用正则表达式来实现。因此,我们导入了re模块。然后我们导入了Collection模块,作为 Python 内置数据类型dictlistsettuple的替代。此模块具有专用的容器数据类型。导入所需模块后,我们使用正则表达式编写一个模式,以匹配特定条件,从日志文件映射 IP 地址。

在该匹配模式中,\d可以是09之间的任意数字,\r表示原始字符串。然后,我们打开名为access.log的 Apache 日志文件并读取它。之后,我们在 Apache 日志文件上应用了正则表达式条件,然后使用collection模块的counter函数根据re条件获取每个 IP 地址的计数。最后,我们打印了操作的结果,正如我们在输出中看到的那样。

例外的必要性

在本节中,我们将研究 Python 编程中异常的需要。正常程序流由事件和信号组成。“异常”一词表示您的程序有问题。这些异常可以是任何类型,例如零除错误、导入错误、属性错误或断言错误。当指定的函数无法正确执行其任务时,将发生这些异常。当异常发生时,程序执行停止,解释器将继续异常处理过程。异常处理过程包括在try…except块中编写代码。异常处理的原因是程序中发生了意外事件。

分析异常

在本节中,我们将了解异常分析。必须处理发生的每个异常。您的日志文件也应该包含很少的异常。如果您多次遇到类似类型的异常,那么您的程序就会出现一些问题,您应该尽快进行必要的更改。

考虑下面的例子:

f = open('logfile', 'r') print(f.read()) f.close()

运行程序后,您将获得如下输出:

Traceback (most recent call last):
 File "sample.py", line 1, in <module> f = open('logfile', 'r') FileNotFoundError: [Errno 2] No such file or directory: 'logfile'

在本例中,我们试图读取目录中不存在的文件,结果显示错误。因此,从错误中我们可以分析我们必须提供什么样的解决方案。为了处理这种情况,我们可以使用异常处理技术。那么,让我们来看一个使用异常处理技术处理错误的示例。

考虑下面的例子:

try:
    f = open('logfile', 'r')
 print(f.read()) f.close()
except:
    print("file not found. Please check whether the file is present in your directory or not.") 

运行程序后,您将获得如下输出:

file not found. Please check whether the file is present in your directory or not.

在本例中,我们试图读取目录中不存在的文件。但是,由于在本例中使用了文件异常技术,我们将代码放入了try:except:块中。因此,如果try:块中发生任何错误或异常,它将跳过该错误并在except:块中执行代码。在我们的例子中,我们只是把一个print语句放在except:块中。因此,在运行脚本后,当try:块中发生异常时,它跳过该异常并执行except:块中的代码。因此,except块中的print语句被执行,正如我们在前面的输出中所看到的。

解析不同文件的技巧

在本节中,我们将学习解析不同文件的技巧。在开始实际解析之前,我们必须先读取数据。您需要了解从何处获取所有数据。但是,您还必须记住,所有日志文件都有不同的大小。为了简化您的任务,以下是一个列表:

  • 请记住,日志文件可以是纯文本,也可以是压缩的。
  • 对于纯文本文件,所有日志文件都有.log扩展名;对于bzip2文件,所有日志文件都有log.bz2扩展名。
  • 您应该根据文件名处理文件集。
  • 所有日志文件的解析都必须合并到一个报告中。
  • 您使用的工具必须对指定目录或不同目录中的所有文件进行操作。还应包括所有子目录中的日志文件。

**# 错误日志

在本节中,我们将了解错误日志。错误日志的相关指令如下所示:

  • ErrorLog
  • LogLevel

服务器日志文件的位置和名称由ErrorLog指令设置。它是最重要的日志文件。Apachehttpd发送此文件中的信息,以及处理过程中生成的记录。每当服务器出现问题时,这将是第一个查看的地方。它包含出错的细节和修复过程。

错误日志将写入一个文件中。在 Unix 系统上,错误可以由服务器发送到syslog,也可以通过管道发送到程序。日志条目中的第一件事是消息的日期和时间。第二个条目记录错误的严重性。

LogLevel指令通过限制严重性级别来处理发送到错误日志的错误。第三个条目包含有关生成错误的客户端的信息。该信息将是 IP 地址。接下来是消息本身。它包含服务器已配置为拒绝客户端访问的信息。然后,服务器将报告所请求文档的文件系统路径。

错误日志文件中可能会出现各种类型的消息。错误日志文件还包含 CGI 脚本的调试输出。任何写入stderr的信息都将直接复制到错误日志中。

错误日志文件不可自定义。错误日志中处理请求的条目将在访问日志中具有相应的条目。在测试期间,您应该始终监视错误日志中的问题。在 Unix 系统上,可以运行以下命令来完成此操作:

$ tail -f error_log

访问日志

在本节中,您将了解访问日志。服务器访问日志将记录服务器处理的所有请求。CustomLog指令控制访问日志的位置和内容。LogFormat指令用于选择日志的内容。

将信息存储在访问日志中意味着启动日志管理。下一步将是分析帮助我们获得有用统计数据的信息。Apachehttpd有各种版本,这些版本使用了一些其他模块和指令来控制访问日志记录。您可以配置访问日志的格式。此格式是使用格式字符串指定的。

通用日志格式

在本节中,我们将学习常见的日志格式。以下语法显示访问日志的配置:

 LogFormat "%h %l %u %t \"%r\" %>s %b" nick_name
 CustomLog logs/access_log nick_name

该字符串将定义一个昵称,然后将该昵称与日志格式字符串相关联。日志格式字符串由百分比指令组成。每个 percent 指令都告诉服务器记录特定信息。此字符串可能包含文字字符。这些字符将直接复制到日志输出中。

CustomLog指令将在已定义的昵称的帮助下建立一个新的日志文件。访问日志的文件名是相对于ServerRoot的,除非它以斜杠开头。

我们前面提到的配置将以通用日志格式CLF写入日志条目。这是一种标准格式,可以由许多不同的 web 服务器生成。许多日志分析程序读取这种日志格式。

现在,我们将了解每个百分比指令的含义:

  • %h显示向 web 服务器发出请求的客户端的 IP 地址。如果HostnameLookups处于打开状态,则服务器将确定主机名并将其记录在 IP 地址的位置。 *** %l:该术语用于表示所请求工件的信息不可用。* %u这是请求文档的人的用户 ID。在REMOTE_USER环境变量中为 CGI 脚本提供相同的值。*** %t该术语用于检测服务器处理请求完成的时间。格式如下:****

******```py [day/month/year:hour:minute:second zone]


`day`参数取两位数字。对于`month`,我们必须定义三个字母。对于年份,由于年份有四个字符,我们必须采用四位数字。现在在`day`、`month`和`year`之后,我们必须分别为`hour`、`minute`和`seconds`取两位数字。

*   **`\"%r\"`:此术语用作请求行,以双引号从客户处给出。此请求行包含有用的信息。请求客户端使用`GET`方法,使用的协议是 HTTP。**
***   `%>s`:定义客户的状态码。状态代码非常重要和有用,因为它指示客户机发送的请求是否成功发送到服务器。*   `%b`:该术语定义对象返回客户端时的总大小。此总大小不包括响应标头的大小。**

 **# 解析其他日志文件

在我们的系统中还有不同的日志文件,包括 Apache 日志。在我们的 Linux 发行版中,日志文件位于根文件系统的`/var/log/`文件夹中,如下所示:

![](img/42d14bf4-400a-417b-950f-1abb543b30f8.png)

在前面的屏幕截图中,我们可以很容易地看到不同操作条目可用的不同类型的日志文件(例如,身份验证日志文件`auth.log`、系统日志文件`syslog`和内核日志`kern.log`。正如前面所示,当我们对 Apache 日志文件执行操作时,我们还可以对本地日志文件执行相同的操作。让我们看一个解析以前的一个日志文件的示例。创建一个`simple_log.py`脚本,并在其中写入以下内容:

```py
f=open('/var/log/kern.log','r') lines = f.readlines() for line in lines:
 kern_log = line.split() print(kern_log) f.close()

运行脚本,您将获得如下输出:

student@ubuntu:~$ python3 simple_log.py Output:
 ['Dec', '26', '14:39:38', 'ubuntu', 'NetworkManager[795]:', '<info>', '[1545815378.2891]', 'device', '(ens33):', 'state', 'change:', 'prepare', '->', 'config', '(reason', "'none')", '[40', '50', '0]'] ['Dec', '26', '14:39:38', 'ubuntu', 'NetworkManager[795]:', '<info>', '[1545815378.2953]', 'device', '(ens33):', 'state', 'change:', 'config', '->', 'ip-config', '(reason', "'none')", '[50', '70', '0]'] ['Dec', '26', '14:39:38', 'ubuntu', 'NetworkManager[795]:', '<info>', '[1545815378.2997]', 'dhcp4', '(ens33):', 'activation:', 'beginning', 'transaction', '(timeout', 'in', '45', 'seconds)'] ['Dec', '26', '14:39:38', 'ubuntu', 'NetworkManager[795]:', '<info>', '[1545815378.3369]', 'dhcp4', '(ens33):', 'dhclient', 'started', 'with', 'pid', '5221'] ['Dec', '26', '14:39:39', 'ubuntu', 'NetworkManager[795]:', '<info>', '[1545815379.0008]', 'address', '192.168.0.108'] ['Dec', '26', '14:39:39', 'ubuntu', 'NetworkManager[795]:', '<info>', '[1545815379.0020]', 'plen', '24', '(255.255.255.0)'] ['Dec', '26', '14:39:39', 'ubuntu', 'NetworkManager[795]:', '<info>', '[1545815379.0028]', 'gateway', '192.168.0.1']

在前面的示例中,首先我们创建了一个简单的文件对象f,并以读取模式打开其中的kern.log文件。之后,我们在file对象上应用readlines()函数,在for循环中逐行读取文件中的数据。然后我们在内核日志文件的每一行上应用了**split()**函数,然后使用print函数打印整个文件,如输出所示。

就像读取内核日志文件一样,我们也可以对它执行各种操作,就像我们现在要执行一些操作一样。现在,我们将通过索引访问内核日志文件中的内容。这是可能的,因为split函数将文件中的所有信息分割为不同的迭代。让我们来看一个这样的例子。创建一个simple_log1.py脚本,并将以下脚本放入其中:

f=open('/var/log/kern.log','r') lines = f.readlines() for line in lines:
 kern_log = line.split()[1:3] print(kern_log)

运行脚本,您将获得以下输出:

student@ubuntu:~$ python3 simple_log1.py Output: ['26', '14:37:20'] ['26', '14:37:20'] ['26', '14:37:32'] ['26', '14:39:38'] ['26', '14:39:38'] ['26', '14:39:38'] ['26', '14:39:38'] ['26', '14:39:38'] ['26', '14:39:38'] ['26', '14:39:38'] ['26', '14:39:38'] ['26', '14:39:38'] 

在前面的示例中,我们只是在split函数旁边添加了[1:3],换句话说,就是切片。序列的子序列称为切片,提取子序列的操作称为切片。在我们的示例中,我们使用方括号([ ])作为切片运算符,其中有两个整数值,用冒号(:分隔)。运算符[1:3]返回从第一个元素到第三个元素的序列部分,包括第一个元素,但不包括最后一个元素。当我们切片任何序列时,我们得到的子序列总是与从中派生的原始序列具有相同的类型。 然而,列表(或元组)的元素可以是任何类型;无论我们如何对其应用切片,列表的派生切片都是一个列表。所以,在对日志文件应用切片之后,我们得到了前面显示的输出。

总结

在本章中,您学习了如何使用不同类型的日志文件。您还了解了解析复杂日志文件以及在处理这些文件时需要异常。解析日志文件的技巧将有助于顺利执行解析。您还了解了ErrorLogAccessLog

在下一章中,您将学习 SOAP 和 REST 通信。

问题

  1. Python 中运行时异常和编译时异常的区别是什么?
  2. 什么是正则表达式?
  3. 探索 Linux 命令headtailcatawk
  4. 编写一个 Python 程序,将一个文件的内容附加到另一个文件中。
  5. 编写一个 Python 程序,以相反顺序读取文件内容。
  6. 以下表达式的输出是什么?
    1. re.search(r'C\Wke', 'C@ke').group()
    2. re.search(r'Co+kie', 'Cooookie').group()
    3. re.match(r'<.*?>', '<h1>TITLE</h1>').group()

进一步阅读