Buy My Book on Amazon Buy My Book Direct (PDF) GitHub Profile LinkedIn Profile

Extending the LogMessage class

Extend LogMessage and Operations Classes

By Shahzad Qadir, Posted on: Nov. 3, 2025, 4:07 p.m.

In this post, we will carry on writing our LogMessage class. We will implement the methods is_old_message and is_new_message. We will also extend our Operations class to include a method, parse_log_file, which will take a text file and extract log messages from it.

Let's get started.

from datetime import datetime, timedelta


class LogMessage:
    def __init__(self, date_time, facility, severity, event_type, message):
        self._date_time = date_time
        self._facility = facility
        self._severity = severity
        self._event_type = event_type
        self._message = message

    @property
    def date_time(self):
        return self._date_time

    @date_time.setter
    def date_time(self, value):
        self._date_time = value

    @property
    def facility(self):
        return self._facility

    @facility.setter
    def facility(self, value):
        self._facility = value

    @property
    def severity(self):
        return self._severity

    @severity.setter
    def severity(self, value):
        if value < 0 or value > 7:
            raise ValueError("Severity ranges from 0 to 7")
        self._severity = value

    @property
    def event_type(self):
        return self._event_type

    @event_type.setter
    def event_type(self, value):
        self._event_type = value

    @property
    def message(self):
        return self._message

    @message.setter
    def message(self, value):
        self._message = value

    def is_old_message(self, reference_time=None):
        reference_time = reference_time or datetime.now()
        two_days_old = reference_time - timedelta(days=2)
        return self.date_time <= two_days_old

    def is_new_message(self):
        one_day_old = datetime.now() - timedelta(days=1)
        return self.date_time >= one_day_old

    def is_critical(self):
        return self.severity >= 5

    def __str__(self):
        return f"{self.date_time}: %{self.facility}-{self.severity}-{self.event_type}: {self.message}"

is_old_message uses methods from Python's built-in datetime class: now(), which returns the current date and time, and timedelta, which can take arguments like timedelta(days=0, seconds=0, ...). We provide two days in timedelta format, which we then subtract from the current date. If the log message's date is older than or equal to that two-day old date, is_old_message returns True; otherwise, it returns False.

is_new_message() uses a similar logic but compares the log message's date and time against a one-day old date, returning True if the log message is newer than or equal to that one-day old date and time.

Let's create the main.py file and test to see if it works as expected.

# main.py
from base_classes import LogMessage
from operations_classes import Operations
from datetime import datetime, timedelta

def main():
    log = "*Oct 30 2025 05:26:18.327: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1, changed state to down"
    log_obj = Operations.parse_log_message(log)

    print(f"Is it old message: {log_obj.is_old_message()}")
    print(f"Is it new message: {log_obj.is_new_message()}")

if __name__ == "__main__":
    main()
Execution Output
$ python3 main.py 
Is it old message: True
Is it new message: False

I ran this on 3 Nov 2025, and since the message's date is Oct 30 2025, it is clearly an old message.

Extending the Operations Class

The second thing we want to do is extend our Operations class to include the parse_log_file(filename) method.

from datetime import datetime
from base_classes import LogMessage


class Operations:
    @classmethod
    def parse_log_message(cls, log_message):
        if log_message[0] == '*':
            log_message = log_message[1:]
        message_list = log_message.split(': ')
        date_time_str = message_list[0]
        date_time = datetime.strptime(date_time_str, "%b %d %Y %H:%M:%S.%f")
        facility = message_list[1].split('-')[0][1:]
        severity = int(message_list[1].split('-')[1])
        event_type = message_list[1].split('-')[2]
        if len(message_list) > 3:
            message = ' '.join(message_list[2:])
        else:
            message = message_list[2]
        return LogMessage(
            date_time=date_time,
            facility=facility,
            severity=severity,
            event_type=event_type,
            message=message
            )

    @classmethod
    def parse_log_file(cls, filename):
        log_message_objs = []
        with open(filename, 'r') as file:
            for line in file:
                log_message = cls.parse_log_message(line)
                log_message_objs.append(log_message)
        return log_message_objs
Example Execution

Create a text file called log_messages.txt with the following log messages:

*Oct 30 2025 05:25:13.402: %IOSXE-3-PLATFORM: R0/0: kernel: Warning: unable to open an initial console.
*Oct 30 2025 05:25:13.448: %IOSXE-4-PLATFORM: R0/0: kernel: cpldha: loading out-of-tree module taints kernel.
*Oct 30 2025 05:25:13.544: %IOSXE-4-PLATFORM: R0/0: kernel: ACPI: PCI Interrupt Link [LNKC] enabled at IRQ 11
*Oct 30 2025 05:25:52.544: %CPPDRV-4-CPU_FEATURE: R0/0: cpp_driver: CPP0: CPU lacks feature (Population Count HW-Assist (POPCNT)).  Performance may be sub-optimal.
*Oct 30 2025 05:25:52.545: %CPPDRV-4-CPU_FEATURE: R0/0: cpp_driver: CPP0: CPU lacks feature (AES HW-Assist (AES-NI)).  Performance may be sub-optimal.
*Oct 30 2025 05:25:52.545: %CPPDRV-3-GUEST_CPU_FEATURE: R0/0: cpp_driver: CPP0: Guest CPU lacks feature (Supplemental Streaming SIMD Extensions 3 (SSSE3)).
*Oct 30 2025 05:26:17.048: %SYS-5-CONFIG_I: Configured from memory by console
*Oct 30 2025 05:26:17.144: %IOSXE_OIR-6-INSCARD: Card (rp) inserted in slot R1
*Oct 30 2025 05:26:17.323: %SSH-5-ENABLED: SSH 1.99 has been enabled
*Oct 30 2025 05:26:17.328: %LINK-3-UPDOWN: Interface LIIN0, changed state to up
*Oct 30 2025 05:26:17.328: %LINK-5-CHANGED: Interface GigabitEthernet1, changed state to administratively down
*Oct 30 2025 05:26:17.328: %LINEPROTO-5-UPDOWN: Line protocol on Interface Lsmpi0, changed state to up
*Oct 30 2025 05:26:17.329: %LINK-5-CHANGED: Interface GigabitEthernet3, changed state to administratively down
*Oct 30 2025 05:26:17.353: %CRYPTO-6-ISAKMP_ON_OFF: ISAKMP is OFF
*Oct 30 2025 05:26:17.353: %CRYPTO-6-GDOI_ON_OFF: GDOI is OFF
*Oct 30 2025 05:26:17.401: %SYS-6-BOOTTIME: Time taken to reboot after reload =   94 seconds
*Oct 30 2025 05:26:18.327: %LINEPROTO-5-UPDOWN: Line protocol on Interface LIIN0, changed state to up
*Oct 30 2025 05:26:18.327: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1, changed state to down
*Oct 30 2025 05:26:18.329: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet2, changed state to down
*Oct 30 2025 05:26:18.329: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet3, changed state to down
*Oct 30 2025 05:26:18.351: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet4, changed state to down
*Oct 30 2025 05:26:22.585: %PNP-6-PNP_DISCOVERY_STOPPED: PnP Discovery stopped (Startup Config Present)

Add these lines to main.py:

from base_classes import LogMessage
from operations_classes import Operations
from datetime import datetime, timedelta

def main():
    log_message_objs = Operations.parse_log_file("log_messages.txt")
    for obj in log_message_objs:
        print(obj.message.strip())

if __name__ == "__main__":
    main()
Output
$ python3 main.py 
R0/0 kernel Warning unable to open an initial console.
R0/0 kernel cpldha loading out-of-tree module taints kernel.
R0/0 kernel ACPI PCI Interrupt Link [LNKC] enabled at IRQ 11
R0/0 cpp_driver CPP0 CPU lacks feature (Population Count HW-Assist (POPCNT)).  Performance may be sub-optimal.
R0/0 cpp_driver CPP0 CPU lacks feature (AES HW-Assist (AES-NI)).  Performance may be sub-optimal.
R0/0 cpp_driver CPP0 Guest CPU lacks feature (Supplemental Streaming SIMD Extensions 3 (SSSE3)).
Configured from memory by console
Card (rp) inserted in slot R1
SSH 1.99 has been enabled
Interface LIIN0, changed state to up
Interface GigabitEthernet1, changed state to administratively down
Line protocol on Interface Lsmpi0, changed state to up
Interface GigabitEthernet3, changed state to administratively down
ISAKMP is OFF
GDOI is OFF
Time taken to reboot after reload =   94 seconds
Line protocol on Interface LIIN0, changed state to up
Line protocol on Interface GigabitEthernet1, changed state to down
Line protocol on Interface GigabitEthernet2, changed state to down
Line protocol on Interface GigabitEthernet3, changed state to down
Line protocol on Interface GigabitEthernet4, changed state to down
PnP Discovery stopped (Startup Config Present)

We have successfully retrieved log messages from the file. If we want to only see critical messages, we can amend main.py:

from base_classes import LogMessage
from operations_classes import Operations
from datetime import datetime, timedelta

def main():
    log_message_objs = Operations.parse_log_file("log_messages.txt")
    for obj in log_message_objs:
        if obj.is_critical():
            print(obj.message.strip())

if __name__ == "__main__":
    main()

Now, when we loop through the log messages, the code only prints logs which are critical.

Configured from memory by console
Card (rp) inserted in slot R1
SSH 1.99 has been enabled
Interface GigabitEthernet1, changed state to administratively down
Line protocol on Interface Lsmpi0, changed state to up
Interface GigabitEthernet3, changed state to administratively down
ISAKMP is OFF
GDOI is OFF
Time taken to reboot after reload =   94 seconds
Line protocol on Interface LIIN0, changed state to up
Line protocol on Interface GigabitEthernet1, changed state to down
Line protocol on Interface GigabitEthernet2, changed state to down
Line protocol on Interface GigabitEthernet3, changed state to down
Line protocol on Interface GigabitEthernet4, changed state to down
PnP Discovery stopped (Startup Config Present)

We could also print only the new messages or old messages, and classify them based on their severity.

Conclusion

With the addition of time-based checks and the robust parse_log_file method in our Operations class, we have successfully completed the core functionality of our Object-Oriented Log Handler series. The LogMessage class now stands as a complete, self-contained, and validated object, capable of representing, comparing, and reporting on individual log entries.

We have proven that applying Object-Oriented Programming (OOP) principles can bring clarity, structure, and powerful functionality to the often-messy task of log analysis. While the Operations class offers endless possibilities for extension—perhaps with methods for data aggregation, outputting to different formats like JSON, or connecting to a database—the fundamental LogMessage object itself has reached a solid and highly usable endpoint. I encourage you to use this robust foundation to build your own advanced log management tools!