Extracción rápida de un intervalo de tiempo desde syslog logfile?

Tengo un archivo de registro en el formato estándar de syslog. Parece esto, excepto con cientos de líneas por segundo:

Jan 11 07:48:46 blahblahblah... Jan 11 07:49:00 blahblahblah... Jan 11 07:50:13 blahblahblah... Jan 11 07:51:22 blahblahblah... Jan 11 07:58:04 blahblahblah... 

No gira exactamente a la medianoche, pero nunca tendrá más de dos días en ella.

A menudo tengo que extraer un timeslice de este archivo. Me gustaría escribir un script de propósito general para esto, que puedo llamar como:

 $ timegrep 22:30-02:00 /logs/something.log 

… y sacar las líneas desde las 22:30, hacia adelante a través del límite de la medianoche, hasta las 2 de la mañana del día siguiente.

Hay algunas advertencias:

  • No quiero tener que molestar a escribir la fecha (s) en la línea de comandos, sólo los tiempos. El programa debe ser lo suficientemente inteligente como para resolverlos.
  • El formato de fecha de registro no incluye el año, por lo que debe adivinar sobre la base del año en curso, pero sin embargo hacer lo correcto alrededor del Día de Año Nuevo.
  • Quiero que sea rápido – debe utilizar el hecho de que las líneas son con el fin de buscar en el archivo y utilizar una búsqueda binaria.

Antes de pasar un montón de tiempo escribiendo esto, ¿ya existe?

5 Solutions collect form web for “Extracción rápida de un intervalo de tiempo desde syslog logfile?”

Actualización: he reemplazado el código original con una versión actualizada con numerosas mejoras. Vamos a llamar a esta (real?) Calidad alfa.

Esta versión incluye:

  • Manejo de opciones de línea de comandos
  • Validación de formato de fecha de línea de comandos
  • Algunos bloques de try
  • La lectura de línea se trasladó a una función

Texto original:

¿Bueno, qué sabes? "¡Busca y encontrarás! Aquí está un programa de Python que busca en el archivo y utiliza una búsqueda binaria más o menos. Es considerablemente más rápido que el guión AWK que otro tipo escribió.

Es (pre?) Calidad alfa. Debe tener bloques de try y validación de entrada y un montón de pruebas y sin duda podría ser más Pythonic. Pero aquí está para su diversión. Ah, y está escrito para Python 2.6.

Nuevo código:

 #!/usr/bin/env python # -*- coding: utf-8 -*- # timegrep.py by Dennis Williamson 20100113 # in response to http://serverfault.com/questions/101744/fast-extraction-of-a-time-range-from-syslog-logfile # thanks to serverfault user http://serverfault.com/users/1545/mike # for the inspiration # Perform a binary search through a log file to find a range of times # and print the corresponding lines # tested with Python 2.6 # TODO: Make sure that it works if the seek falls in the middle of # the first or last line # TODO: Make sure it's not blind to a line where the sync read falls # exactly at the beginning of the line being searched for and # then gets skipped by the second read # TODO: accept arbitrary date # done: add -l long and -s short options # done: test time format version = "0.01a" import os, sys from stat import * from datetime import date, datetime import re from optparse import OptionParser # Function to read lines from file and extract the date and time def getdata(): """Read a line from a file Return a tuple containing: the date/time in a format such as 'Jan 15 20:14:01' the line itself The last colon and seconds are optional and not handled specially """ try: line = handle.readline(bufsize) except: print("File I/O Error") exit(1) if line == '': print("EOF reached") exit(1) if line[-1] == '\n': line = line.rstrip('\n') else: if len(line) >= bufsize: print("Line length exceeds buffer size") else: print("Missing newline") exit(1) words = line.split(' ') if len(words) >= 3: linedate = words[0] + " " + words[1] + " " + words[2] else: linedate = '' return (linedate, line) # End function getdata() # Set up option handling parser = OptionParser(version = "%prog " + version) parser.usage = "\n\t%prog [options] start-time end-time filename\n\n\ \twhere times are in the form hh:mm[:ss]" parser.description = "Search a log file for a range of times occurring yesterday \ and/or today using the current time to intelligently select the start and end. \ A date may be specified instead. Seconds are optional in time arguments." parser.add_option("-d", "--date", action = "store", dest = "date", default = "", help = "NOT YET IMPLEMENTED. Use the supplied date instead of today.") parser.add_option("-l", "--long", action = "store_true", dest = "longout", default = False, help = "Span the longest possible time range.") parser.add_option("-s", "--short", action = "store_true", dest = "shortout", default = False, help = "Span the shortest possible time range.") parser.add_option("-D", "--debug", action = "store", dest = "debug", default = 0, type = "int", help = "Output debugging information.\t\t\t\t\tNone (default) = %default, Some = 1, More = 2") (options, args) = parser.parse_args() if not 0 <= options.debug <= 2: parser.error("debug level out of range") else: debug = options.debug # 1 = print some debug output, 2 = print a little more, 0 = none if options.longout and options.shortout: parser.error("options -l and -s are mutually exclusive") if options.date: parser.error("date option not yet implemented") if len(args) != 3: parser.error("invalid number of arguments") start = args[0] end = args[1] file = args[2] # test for times to be properly formatted, allow hh:mm or hh:mm:ss p = re.compile(r'(^[2][0-3]|[0-1][0-9]):[0-5][0-9](:[0-5][0-9])?$') if not p.match(start) or not p.match(end): print("Invalid time specification") exit(1) # Determine Time Range yesterday = date.fromordinal(date.today().toordinal()-1).strftime("%b %d") today = datetime.now().strftime("%b %d") now = datetime.now().strftime("%R") if start > now or start > end or options.longout or options.shortout: searchstart = yesterday else: searchstart = today if (end > start > now and not options.longout) or options.shortout: searchend = yesterday else: searchend = today searchstart = searchstart + " " + start searchend = searchend + " " + end try: handle = open(file,'r') except: print("File Open Error") exit(1) # Set some initial values bufsize = 4096 # handle long lines, but put a limit them rewind = 100 # arbitrary, the optimal value is highly dependent on the structure of the file limit = 75 # arbitrary, allow for a VERY large file, but stop it if it runs away count = 0 size = os.stat(file)[ST_SIZE] beginrange = 0 midrange = size / 2 oldmidrange = midrange endrange = size linedate = '' pos1 = pos2 = 0 if debug > 0: print("File: '{0}' Size: {1} Today: '{2}' Now: {3} Start: '{4}' End: '{5}'".format(file, size, today, now, searchstart, searchend)) # Seek using binary search while pos1 != endrange and oldmidrange != 0 and linedate != searchstart: handle.seek(midrange) linedate, line = getdata() # sync to line ending pos1 = handle.tell() if midrange > 0: # if not BOF, discard first read if debug > 1: print("...partial: (len: {0}) '{1}'".format((len(line)), line)) linedate, line = getdata() pos2 = handle.tell() count += 1 if debug > 0: print("#{0} Beg: {1} Mid: {2} End: {3} P1: {4} P2: {5} Timestamp: '{6}'".format(count, beginrange, midrange, endrange, pos1, pos2, linedate)) if searchstart > linedate: beginrange = midrange else: endrange = midrange oldmidrange = midrange midrange = (beginrange + endrange) / 2 if count > limit: print("ERROR: ITERATION LIMIT EXCEEDED") exit(1) if debug > 0: print("...stopping: '{0}'".format(line)) # Rewind a bit to make sure we didn't miss any seek = oldmidrange while linedate >= searchstart and seek > 0: if seek < rewind: seek = 0 else: seek = seek - rewind if debug > 0: print("...rewinding") handle.seek(seek) linedate, line = getdata() # sync to line ending if debug > 1: print("...junk: '{0}'".format(line)) linedate, line = getdata() if debug > 0: print("...comparing: '{0}'".format(linedate)) # Scan forward while linedate < searchstart: if debug > 0: print("...skipping: '{0}'".format(linedate)) linedate, line = getdata() if debug > 0: print("...found: '{0}'".format(line)) if debug > 0: print("Beg: {0} Mid: {1} End: {2} P1: {3} P2: {4} Timestamp: '{5}'".format(beginrange, midrange, endrange, pos1, pos2, linedate)) # Now that the preliminaries are out of the way, we just loop, # reading lines and printing them until they are # beyond the end of the range we want while linedate <= searchend: print line linedate, line = getdata() if debug > 0: print("Start: '{0}' End: '{1}'".format(searchstart, searchend)) handle.close() 

De una búsqueda rápida en la red, hay cosas que se extraen sobre la base de palabras clave (como FIRE o tal 🙂 pero nada que extrae un intervalo de fechas del archivo.

No parece difícil hacer lo que usted propone:

  1. Busca la hora de inicio.
  2. Imprima esa línea.
  3. Si el tiempo de finalización <hora de inicio y la fecha de una línea es> fin y <inicio, entonces detenga.
  4. Si el tiempo de finalización es> hora de inicio, y la fecha de una línea es> end, deténgase.

Parece directo, y podría escribir para ti si no te importa Ruby 🙂

Esto imprimirá el intervalo de entradas entre una hora de inicio y una hora de fin, basada en cómo se relacionan con la hora actual ("ahora").

Uso:

 timegrep [-l] start end filename 

Ejemplo:

 $ timegrep 18:47 03:22 /some/log/file 

La opción -l (larga) produce la salida más larga posible. La hora de inicio se interpretará como ayer si el valor de horas y minutos de la hora de inicio es menor que la hora de finalización y ahora. El tiempo de finalización se interpretará como si hoy día tanto el tiempo de inicio como el tiempo de finalización de los valores HH: MM son mayores que "ahora".

Suponiendo que "now" es "Jan 11 19:00", así es como se interpretarán varios tiempos de inicio y fin de ejemplo (sin -l excepto como se indica):

 Comienzo fin comienzo principio fin
 19:01 23:59 Ene 10 Ene 10
 19:01 00:00 Jan 10 Ene 11
 00:00 18:59 Jan 11 Ene 11
 18:59 18:58 Jan 10 Ene 10
 19:01 23:59 Ene 10 Ene 11 # -l
 00:00 18:59 Jan 10 Jan 11 # -l
 18:59 19:01 Jan 10 Jan 11 # -l

Casi todo el script está configurado. Las dos últimas líneas hacen todo el trabajo.

Advertencia: no se realiza validación de argumentos o comprobación de errores. Los casos de bordes no han sido probados a fondo. Esto fue escrito usando gawk otras versiones de AWK pueden gritar.

 #!/usr/bin/awk -f BEGIN { arg=1 if ( ARGV[arg] == "-l" ) { long = 1 ARGV[arg++] = "" } start = ARGV[arg] ARGV[arg++] = "" end = ARGV[arg] ARGV[arg++] = "" yesterday = strftime("%b %d", mktime(strftime("%Y %m %d -24 00 00"))) today = strftime("%b %d") now = strftime("%R") if ( start > now || start > end || long ) startdate = yesterday else startdate = today if ( end > now && end > start && start > now && ! long ) enddate = yesterday else enddate = today fi startdate = startdate " " start enddate = enddate " " end } $1 " " $2 " " $3 > enddate {exit} $1 " " $2 " " $3 >= startdate {print} 

Creo que AWK es muy eficiente en la búsqueda a través de archivos. No creo que otra cosa necesariamente va a ser más rápido en la búsqueda de un archivo de texto sin indexar .

Un programa de C ++ que aplica una búsqueda binaria – necesitaría algunas modificaciones simples (es decir, llamar a strptime) para trabajar con fechas de texto.

http://gitorious.org/bs_grep/

Tenía una versión anterior con soporte para las fechas de texto, pero todavía era demasiado lento para la escala de nuestros archivos de registro; Perfilado, dijo que más del 90% del tiempo se gastó en strptime, por lo que, sólo hemos modificado el formato de registro para incluir un timestamp numérico unix también.

A pesar de que esta respuesta es demasiado tarde, podría ser beneficioso para algunos.

He convertido el código de @Dennis Williamson en una clase Python que se puede utilizar para otras cosas de python.

He añadido soporte para múltiples soportes de fecha.

 import os from stat import * from datetime import date, datetime import re # @TODO Support for rotated log files - currently using the current year for 'Jan 01' dates. class LogFileTimeParser(object): """ Extracts parts of a log file based on a start and enddate Uses binary search logic to speed up searching Common usage: validate log files during testing Faster than awk parsing for big log files """ version = "0.01a" # Set some initial values BUF_SIZE = 4096 # self.handle long lines, but put a limit to them REWIND = 100 # arbitrary, the optimal value is highly dependent on the structure of the file LIMIT = 75 # arbitrary, allow for a VERY large file, but stop it if it runs away line_date = '' line = None opened_file = None @staticmethod def parse_date(text, validate=True): # Supports Aug 16 14:59:01 , 2016-08-16 09:23:09 Jun 1 2005 1:33:06PM (with or without seconds, miliseconds) for fmt in ('%Y-%m-%d %H:%M:%S %f', '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%b %d %H:%M:%S %f', '%b %d %H:%M', '%b %d %H:%M:%S', '%b %d %Y %H:%M:%S %f', '%b %d %Y %H:%M', '%b %d %Y %H:%M:%S', '%b %d %Y %I:%M:%S%p', '%b %d %Y %I:%M%p', '%b %d %Y %I:%M:%S%p %f'): try: if fmt in ['%b %d %H:%M:%S %f', '%b %d %H:%M', '%b %d %H:%M:%S']: return datetime.strptime(text, fmt).replace(datetime.now().year) return datetime.strptime(text, fmt) except ValueError: pass if validate: raise ValueError("No valid date format found for '{0}'".format(text)) else: # Cannot use NoneType to compare datetimes. Using minimum instead return datetime.min # Function to read lines from file and extract the date and time def read_lines(self): """ Read a line from a file Return a tuple containing: the date/time in a format supported in parse_date om the line itself """ try: self.line = self.opened_file.readline(self.BUF_SIZE) except: raise IOError("File I/O Error") if self.line == '': raise EOFError("EOF reached") # Remove \n from read lines. if self.line[-1] == '\n': self.line = self.line.rstrip('\n') else: if len(self.line) >= self.BUF_SIZE: raise ValueError("Line length exceeds buffer size") else: raise ValueError("Missing newline") words = self.line.split(' ') # This results into Jan 1 01:01:01 000000 or 1970-01-01 01:01:01 000000 if len(words) >= 3: self.line_date = self.parse_date(words[0] + " " + words[1] + " " + words[2],False) else: self.line_date = self.parse_date('', False) return self.line_date, self.line def get_lines_between_timestamps(self, start, end, path_to_file, debug=False): # Set some initial values count = 0 size = os.stat(path_to_file)[ST_SIZE] begin_range = 0 mid_range = size / 2 old_mid_range = mid_range end_range = size pos1 = pos2 = 0 # If only hours are supplied # test for times to be properly formatted, allow hh:mm or hh:mm:ss p = re.compile(r'(^[2][0-3]|[0-1][0-9]):[0-5][0-9](:[0-5][0-9])?$') if p.match(start) or p.match(end): # Determine Time Range yesterday = date.fromordinal(date.today().toordinal() - 1).strftime("%Y-%m-%d") today = datetime.now().strftime("%Y-%m-%d") now = datetime.now().strftime("%R") if start > now or start > end: search_start = yesterday else: search_start = today if end > start > now: search_end = yesterday else: search_end = today search_start = self.parse_date(search_start + " " + start) search_end = self.parse_date(search_end + " " + end) else: # Set dates search_start = self.parse_date(start) search_end = self.parse_date(end) try: self.opened_file = open(path_to_file, 'r') except: raise IOError("File Open Error") if debug: print("File: '{0}' Size: {1} Start: '{2}' End: '{3}'" .format(path_to_file, size, search_start, search_end)) # Seek using binary search -- ONLY WORKS ON FILES WHO ARE SORTED BY DATES (should be true for log files) try: while pos1 != end_range and old_mid_range != 0 and self.line_date != search_start: self.opened_file.seek(mid_range) # sync to self.line ending self.line_date, self.line = self.read_lines() pos1 = self.opened_file.tell() # if not beginning of file, discard first read if mid_range > 0: if debug: print("...partial: (len: {0}) '{1}'".format((len(self.line)), self.line)) self.line_date, self.line = self.read_lines() pos2 = self.opened_file.tell() count += 1 if debug: print("#{0} Beginning: {1} Mid: {2} End: {3} P1: {4} P2: {5} Timestamp: '{6}'". format(count, begin_range, mid_range, end_range, pos1, pos2, self.line_date)) if search_start > self.line_date: begin_range = mid_range else: end_range = mid_range old_mid_range = mid_range mid_range = (begin_range + end_range) / 2 if count > self.LIMIT: raise IndexError("ERROR: ITERATION LIMIT EXCEEDED") if debug: print("...stopping: '{0}'".format(self.line)) # Rewind a bit to make sure we didn't miss any seek = old_mid_range while self.line_date >= search_start and seek > 0: if seek < self.REWIND: seek = 0 else: seek -= self.REWIND if debug: print("...rewinding") self.opened_file.seek(seek) # sync to self.line ending self.line_date, self.line = self.read_lines() if debug: print("...junk: '{0}'".format(self.line)) self.line_date, self.line = self.read_lines() if debug: print("...comparing: '{0}'".format(self.line_date)) # Scan forward while self.line_date < search_start: if debug: print("...skipping: '{0}'".format(self.line_date)) self.line_date, self.line = self.read_lines() if debug: print("...found: '{0}'".format(self.line)) if debug: print("Beginning: {0} Mid: {1} End: {2} P1: {3} P2: {4} Timestamp: '{5}'". format(begin_range, mid_range, end_range, pos1, pos2, self.line_date)) # Now that the preliminaries are out of the way, we just loop, # reading lines and printing them until they are beyond the end of the range we want while self.line_date <= search_end: # Exclude our 'Nonetype' values if not self.line_date == datetime.min: print self.line self.line_date, self.line = self.read_lines() if debug: print("Start: '{0}' End: '{1}'".format(search_start, search_end)) self.opened_file.close() # Do not display EOFErrors: except EOFError as e: pass 
  • Detección del uso de la unidad Thumb en una máquina Windows
  • ¿Es este server hackeado o solo bashs de inicio de session? Ver logging
  • Analizar archivos de registro de IIS
  • Buscar logging de acceso lighttpd
  • ¿Es una buena idea bloquear las direcciones IP sin resolver?
  • Obtener métricas de session simultánea de los loggings de IIS Raw?
  • logs de apache - las requestes se enumeran con una demora larga
  • Apache Acceso a variables de entorno del sistema (para la ubicación del archivo de registro)
  • Diferentes permissions por file de logging al utilizar rsyslog
  • ¿Puede cron escribir la salida del trabajo en un logging * por defecto * (en lugar del correo)?
  • Rsyslogd envía nueva información o todo el file de logging al reiniciar
  • El linux y los temas del servidor de Windows, como ubuntu, centos, apache, nginx, debian y consejos de red.