Python»Python实践问题。 为下一次面试做好准备。 日志分析器。 第三部分

Python 4实践挑战:日志分析器

日志解析器的问题通常发生在软件开发中。 许多系统在正常运行期间都会创建日志文件,有时您需要分析这些文件以查找异常或有关正在运行的系统的常规信息。

问题描述

对于此问题,您需要以指定的格式解析日志文件并生成报告:

# logparse.py
""" log parser
    Accepts a filename on the command line. The file is a Linux-like log file
    from a system you are debugging. Mixed in among the various statements are
    messages indicating the state of the device. They look like this:
        Jul 11 16:11:51:490 [139681125603136] dut: Device State: ON
    The device state message has many possible values, but this program cares
    about only three: ON, OFF, and ERR.

    Your program will parse the given log file and print out a report giving
    how long the device was ON and the timestamp of any ERR conditions.
"""

请注意,提供的代码框架不包括单元测试。 由于报告的确切格式取决于您,因此已将其省略。 一路思考并编写自己的内容。

包含示例test.log文件。 您正在研究的解决方案将提供以下输出:

$./logparse.py test.log
Device was on for 7 seconds
Timestamps of error events:
   Jul 11 16:11:54:661
   Jul 11 16:11:56:067

尽管此格式是由Real Python解决方案生成的,但是您可以创建自己的输出格式。 样本输入文件应生成等效信息。

完整的解决方案

由于此解决方案的时间长于整数和Caesar密码问题的解决方案,因此让我们从完整的程序开始:

# logparse.py
""" log parser
    Accepts a filename on the command line. The file is a Linux-like log file
    from a system you are debugging. Mixed in among the various statements are
    messages indicating the state of the device. They look like this:
        Jul 11 16:11:51:490 [139681125603136] dut: Device State: ON
    The device state message has many possible values, but this program cares
    about only three: ON, OFF, and ERR.

    Your program will parse the given log file and print out a report giving
    how long the device was ON and the timestamp of any ERR conditions.
"""
import datetime
import sys

def get_next_event(filename):
    with open(filename, "r") as datafile:
        for line in datafile:
            if "dut: Device State: " in line:
                line = line.strip()
                # Parse out the action and timestamp
                action = line.split()[-1]
                timestamp = line[:19]
                yield (action, timestamp)

def compute_time_diff_seconds(start, end):
    format = "%b %d %H:%M:%S:%f"
    start_time = datetime.datetime.strptime(start, format)
    end_time = datetime.datetime.strptime(end, format)
    return (end_time - start_time).total_seconds()

def extract_data(filename):
    time_on_started = None
    errs = []
    total_time_on = 0

    for action, timestamp in get_next_event(filename):
        # First test for errs
        if "ERR" == action:
            errs.append(timestamp)
        elif ("ON" == action) and (not time_on_started):
            time_on_started = timestamp
        elif ("OFF" == action) and time_on_started:
            time_on = compute_time_diff_seconds(time_on_started, timestamp)
            total_time_on += time_on
            time_on_started = None
    return total_time_on, errs

if __name__ == "__main__":
    total_time_on, errs = extract_data(sys.argv[1])
    print(f"Device was on for {total_time_on} seconds")
    if errs:
        print("Timestamps of error events:")
        for err in errs:
            print(f"t{err}")
    else:
        print("No error events found.")

这是您的完整决定。 您可以看到该程序具有三个功能和一个主要部分。 您将从上方通过它们进行操作。

辅助函数:get_next_event()

首先是get_next_event():

# logparse.py
def get_next_event(filename):
    with open(filename, "r") as datafile:
        for line in datafile:
            if "dut: Device State: " in line:
                line = line.strip()
                # Parse out the action and timestamp
                action = line.split()[-1]
                timestamp = line[:19]
                yield (action, timestamp)

因为它包含yield语句,所以此函数是生成器。 这意味着您可以使用它一次从日志文件生成一个事件。

您可以只在数据文件中使用line,但是要添加一些过滤。 例行调用将仅接收其中包含以下内容的事件:设备状态:。 这使您可以保留单个函数中包含的所有特定于文件的解析。

这可能会使get_next_event()有点困难,但这是一个相对较小的函数,因此它仍然足够短以至于无法阅读和理解。 它还将这种复杂的代码封装在一个地方。

您可能想知道何时关闭数据文件。 在调用生成器之前,直到从数据文件中读取了所有行,for循环将完成,从而允许您离开with块并退出函数。

辅助函数:compute_time_diff_seconds()

第二个函数是compute_time_diff_seconds(),顾名思义,该函数计算两个时间戳之间的秒数:

# logparse.py
def compute_time_diff_seconds(start, end):
    format = "%b %d %H:%M:%S:%f"
    start_time = datetime.datetime.strptime(start, format)
    end_time = datetime.datetime.strptime(end, format)
    return (end_time - start_time).total_seconds()

关于此功能有一些有趣的观点。 首先,两个datetime对象的这种减法导致datetime.timedelta。 对于此问题,您将报告总秒数,因此从timedelta返回.total_seconds()是合适的。

其次,应该注意的是,Python中有许多软件包可以使处理日期和时间更加容易。 在这种情况下,您的用法模型足够简单,以致于当标准库函数足够时,它就不能保证引入外部库的复杂性。

就是这样,datetime.datetime.strptime()值得一提。 当传递字符串和特定格式时,.strptime()将使用给定格式解析该字符串,并生成一个datetime对象。

在另一个地方,如果您不记得Python标准库函数的确切名称,在面试时不要惊慌很重要。

辅助函数:extract_data()

下一步是extract_data(),它将完成该程序中的大部分工作。 在深入研究代码之前,让我们回过头来谈论政府机器。

状态机是根据特定输入从一种状态转移到另一种状态的软件(或硬件)设备。 这是一个非常宽泛的定义,可能很难理解,因此让我们在下面看一下将要使用的状态机图:

在此图中,状态由标记的矩形表示。 只有两个设备状态,ON和OFF,与设备状态相对应。 还有两个输入,设备状态:ON和设备状态:OFF。 该图使用箭头显示当机器处于每种状态时发生输入时会发生什么。

例如,如果机器处于“设备状态:打开”,则输入数据,然后机器保持打开状态。 没有变化。 相反,如果机器在开启状态下收到设备状态:关闭输入,则它将关闭。

虽然这里的状态机只有两个输入两个状态,但是状态机通常要复杂得多。 创建预期行为的图表可以帮助使状态机代码更简洁。

让我们回到extract_data():

# logparse.py
def extract_data(filename):
    time_on_started = None
    errs = []
    total_time_on = 0

    for action, timestamp in get_next_event(filename):
        # First test for errs
        if "ERR" == action:
            errs.append(timestamp)
        elif ("ON" == action) and (not time_on_started):
            time_on_started = timestamp
        elif ("OFF" == action) and time_on_started:
            time_on = compute_time_diff_seconds(time_on_started, timestamp)
            total_time_on += time_on
            time_on_started = None
    return total_time_on, errs

在这里可能很难看到。 通常状态机需要一个变量来存储状态。 在这种情况下,您可以使用time_on_started来实现两个目的:

  1. 指示状态:time_on_started包含计算机的状态。 如果为无,则机器关闭。 如果不是None,则机器将打开。
  2. 开始时间:如果为ON,则time_on_started还包含打开设备电源的时间戳。 您可以使用此时间戳来调用compute_time_diff_seconds()。

extract_data()的顶部设置状态变量time_on_started以及所需的两个输出。 errs是找到ERR消息的时间戳的列表,total_time_on是设备打开时所有周期的总和。

完成初始设置后,调用get_next_event()生成器以检索每个事件和时间戳。 行动。 它习惯于控制状态机,但是在检查状态更改之前,它首先使用if块过滤掉任何ERR条件并将其添加到errs中。

检查错误后,第一个elif块将转换为ON。 您只有在处于OFF状态时才能转到下一个ON,这由time_on_started False的存在来表示。 如果您尚未处于ON状态并且操作为“ ON”,则可以通过将机器置于ON状态来存储时间戳。

第二个ELIF处理到OFF状态的转换。 关于此过渡,extract_data()需要计算设备已开启的秒数。 如上所见,它使用compute_time_diff_seconds()完成此操作。 这会将这段时间添加到运行total_time_on中,并将time_on_started设置回None,从而有效地将汽车返回OFF状态。

主功能

最后,您可以继续进行下一个__main__部分。 最后一部分通过sys.argv[1],这是第一个命令行参数extract_data(),然后报告结果:

# logparse.py
if __name__ == "__main__":
    total_time_on, errs = extract_data(sys.argv[1])
    print(f"Device was on for {total_time_on} seconds")
    if errs:
        print("Timestamps of error events:")
        for err in errs:
            print(f"t{err}")
    else:
        print("No error events found.")

要调用此解决方案,请运行脚本并传递日志文件的名称。 运行您的代码示例将产生以下输出:

$ python3 logparse.py test.log
Device was on for 7 seconds
Timestamps of error events:
   Jul 11 16:11:54:661
   Jul 11 16:11:56:067

您的解决方案的格式可能不同,但是示例日志文件的信息应该相同。

有很多方法可以解决此问题。 请记住,在面试情况下,对问题和思考过程的讨论可能比您决定实施的解决方案更为重要。

这就是日志分析解决方案的全部内容。

以前的文章:

  • Python实践问题。 为下一次面试做好准备。 第1部分
  • Python实践问题。 为下一次面试做好准备。 凯撒的密码。 第2部分

未完待续 …

Sidebar