diff options
author | Youniverse <y0un1verse@yandex.ru> | 2019-03-21 18:03:56 +0300 |
---|---|---|
committer | Youniverse <y0un1verse@yandex.ru> | 2019-03-21 18:03:56 +0300 |
commit | 6b81e9f75376c56b0a252696d18be1f209df8b70 (patch) | |
tree | 7ad8f20fc7349905fe35db4ec2b78044202cd24b | |
parent | 41b1c39603dcf9d3d83b648e66b2d918126a9d28 (diff) |
pygtail script
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | pygtail.py | 188 |
2 files changed, 188 insertions, 2 deletions
diff --git a/README.md b/README.md deleted file mode 100644 index 8c5a17e..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# zabbix-postfix-template -Zabbix template for Postfix SMTP server diff --git a/pygtail.py b/pygtail.py new file mode 100644 index 0000000..26c285d --- /dev/null +++ b/pygtail.py @@ -0,0 +1,188 @@ +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- + +# pygtail - a python "port" of logtail2 +# Copyright (C) 2011 Brad Greenlee <brad@footle.org> +# +# Derived from logcheck <http://logcheck.org> +# Copyright (C) 2003 Jonathan Middleton <jjm@ixtab.org.uk> +# Copyright (C) 2001 Paul Slootman <paul@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from os import stat +from os.path import exists +import sys +import glob +import string +from optparse import OptionParser + +__version__ = '0.2' + +class Pygtail(object): + """ + Creates an iterable object that returns only unread lines. + """ + def __init__(self, filename, offset_file=None, paranoid=False): + self.filename = filename + self.paranoid = paranoid + self._offset_file = offset_file or "%s.offset" % self.filename + self._offset_file_inode = 0 + self._offset = 0 + self._fh = None + self._rotated_logfile = None + + # if offset file exists, open and parse it + if exists(self._offset_file): + offset_fh = open(self._offset_file, "r") + (self._offset_file_inode, self._offset) = \ + [string.atoi(line.strip()) for line in offset_fh] + offset_fh.close() + if self._offset_file_inode != stat(self.filename).st_ino: + # The inode has changed, so the file might have been rotated. + # Look for the rotated file and process that if we find it. + self._rotated_logfile = self._determine_rotated_logfile() + + def __iter__(self): + return self + + def next(self): + """ + Return the next line in the file, updating the offset. + """ + try: + line = self._filehandle().next() + except StopIteration: + # we've reached the end of the file; if we're processing the + # rotated log file, we can continue with the actual file; otherwise + # update the offset file + if self._rotated_logfile: + self._rotated_logfile = None + self._fh.close() + self._offset = 0 + # open up current logfile and continue + try: + line = self._filehandle().next() + except StopIteration: # oops, empty file + self._update_offset_file() + raise + else: + self._update_offset_file() + raise + + if self.paranoid: + self._update_offset_file() + + return line + + def readlines(self): + """ + Read in all unread lines and return them as a list. + """ + return [line for line in self] + + def read(self): + """ + Read in all unread lines and return them as a single string. + """ + lines = self.readlines() + if lines: + return ''.join(lines) + else: + return None + + def _filehandle(self): + """ + Return a filehandle to the file being tailed, with the position set + to the current offset. + """ + if not self._fh or self._fh.closed: + filename = self._rotated_logfile or self.filename + self._fh = open(filename, "r") + self._fh.seek(self._offset) + + return self._fh + + def _update_offset_file(self): + """ + Update the offset file with the current inode and offset. + """ + offset = self._filehandle().tell() + inode = stat(self.filename).st_ino + fh = open(self._offset_file, "w") + fh.write("%s\n%s\n" % (inode, offset)) + fh.close() + + def _determine_rotated_logfile(self): + """ + We suspect the logfile has been rotated, so try to guess what the + rotated filename is, and return it. + """ + rotated_filename = self._check_rotated_filename_candidates() + if (rotated_filename and exists(rotated_filename) and + stat(rotated_filename).st_ino == self._offset_file_inode): + return rotated_filename + else: + return None + + def _check_rotated_filename_candidates(self): + """ + Check for various rotated logfile filename patterns and return the first + match we find. + """ + # savelog(8) + candidate = "%s.0" % self.filename + if (exists(candidate) and exists("%s.1.gz" % self.filename) and + (stat(candidate).st_mtime > stat("%s.1.gz" % self.filename).st_mtime)): + return candidate + + # logrotate(8) + candidate = "%s.1" % self.filename + if exists(candidate): + return candidate + + # dateext rotation scheme + candidates = glob.glob("%s-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]" % self.filename) + if candidates: + candidates.sort() + return candidates[-1] # return most recent + + # no match + return None + +def main(): + # command-line parsing + cmdline = OptionParser(usage="usage: %prog [options] logfile", + description="Print log file lines that have not been read.") + cmdline.add_option("--offset-file", "-o", action="store", + help="File to which offset data is written (default: <logfile>.offset).") + cmdline.add_option("--paranoid", "-p", action="store_true", + help="Update the offset file every time we read a line (as opposed to" + " only when we reach the end of the file).") + + options, args = cmdline.parse_args() + + if (len(args) != 1): + cmdline.error("Please provide a logfile to read.") + + pygtail = Pygtail(args[0], + offset_file=options.offset_file, + paranoid=options.paranoid) + for line in pygtail: + sys.stdout.write(line) + + +if __name__ == "__main__": + main() |