Pythonで書かれたsyslogdをWindowsで動かしてみる
ひさしぶりに技術系のネタを。
LANに、あるネットワーク機器が接続されていて、そいつが出力するログを手元のパソコンに保存して読みたい。色々調べてみたところ、その機器にはログをsyslogで他のマシンに送信する機能がついていた。
てことは、手元のWindowsパソコンにsyslogdをインストールして、他のマシンから送られてくるログを受信できるようにしてやればいいはずだ。ということで色々検索してみたところ、Windows用のsyslogdをいくつか見つけることができた。
がしかし、パソコンにはなるべくソフトを入れないようにしたい。とくにバイナリ(exe形式)のソフトは嫌だなーと思いつつ、さらに色々調べてみたところ、Pythonで書かれたsyslogdを発見した。
syslogdといっても、/dev/logを読む機能はないみたい。まさに俺の用途に最適っぽい。ということで試してみた。
このスクリプトは、print と sys.stdout.write でログを画面に表示する試験的なもの。そこで loggerモジュールを使ってファイルに保存するように変更してみた。
# # Original: http://www.drbeat.li/pycgi/webutil.py/html?py=py/syslogd.py.txt # (Slightly modified) # Usage: python syslogd.py c:/log.txt # """A minimalistic syslogd. 2000-06-09/bb: created 2000-06-12/bb: added constants from syslog.h, config options for time format, stop string """ import sys, string, time, socket, re from SocketServer import * import logging, logging.handlers # constants from syslog.h LOG_EMERG = 0 LOG_ALERT = 1 LOG_CRIT = 2 LOG_ERR = 3 LOG_WARNING = 4 LOG_NOTICE = 5 LOG_INFO = 6 LOG_DEBUG = 7 LOG_PRIMASK = 0x07 def LOG_PRI(p): return p & LOG_PRIMASK def LOG_MAKEPRI(fac, pri): return fac << 3 | pri LOG_KERN = 0 << 3 LOG_USER = 1 << 3 LOG_MAIL = 2 << 3 LOG_DAEMON = 3 << 3 LOG_AUTH = 4 << 3 LOG_SYSLOG = 5 << 3 LOG_LPR = 6 << 3 LOG_NEWS = 7 << 3 LOG_UUCP = 8 << 3 LOG_CRON = 9 << 3 LOG_AUTHPRIV = 10 << 3 LOG_FTP = 11 << 3 LOG_LOCAL0 = 16 << 3 LOG_LOCAL1 = 17 << 3 LOG_LOCAL2 = 18 << 3 LOG_LOCAL3 = 19 << 3 LOG_LOCAL4 = 20 << 3 LOG_LOCAL5 = 21 << 3 LOG_LOCAL6 = 22 << 3 LOG_LOCAL7 = 23 << 3 LOG_NFACILITIES = 24 LOG_FACMASK = 0x03F8 def LOG_FAC(p): return (p & LOG_FACMASK) >> 3 def LOG_MASK(pri): return 1 << pri def LOG_UPTO(pri): return (1 << pri + 1) - 1 # end syslog.h def LOG_UNPACK(p): return (p & LOG_FACMASK, LOG_PRI(p)) fac_values = {} # mapping of facility constants to their values fac_names = {} # mapping of values to names pri_values = {} pri_names = {} for i, j in globals().items(): if i[:4] == 'LOG_' and type(j) == type(0): if j > LOG_PRIMASK or i == 'LOG_KERN': n, v = fac_names, fac_values else: n, v = pri_names, pri_values i = i[4:].lower() v[i] = j n[j] = i del i, j, n, v conf_pat = re.compile(r''' ^\s* # leading space ([\w*]+) # facility \.([=!]?) # separator plus optional modifier ([\w*]+) # priority \s+ ([|@]?) # target type fifo/remote host (\S+) # target ''', re.VERBOSE) class InterruptibleServer: "Mix-in class for {TCP,UDP}Server that allows to stop the service" def serve(self): "Run the service until it is stopped with the stop() method." self.running = 1 while self.running: try: self.handle_request() except KeyboardInterrupt: self.stop() def stop(self): # called by handle_request() "Stop the service." self.running = 0 SYSLOG_PORT = socket.getservbyname('syslog', 'udp') class Syslogd(ThreadingUDPServer, InterruptibleServer): def __init__(self, addr='', port=SYSLOG_PORT, pri=LOG_DEBUG, timefmt=None, magic=None, logger=None): UDPServer.__init__(self, (addr, port), None) self.hostmap = {} # client address/name mapping self.priority = pri self.timefmt = timefmt or '%b %d %H:%M:%S' self.stop_magic = magic or '_stop' #print 'syslogd started' logger.info('syslogd started') def finish_request(self, (msg, sock), client_address): # get the time when the message arrives tm = time.time() # Messages are in the form "[[float]]<int>message text...\n" # The [[float]] and <int> parts and the newline are optional. # The float denotes the original event time in case the # event comes through a gateway from a client that is not # syslog-compatible (Windows NT comes to mind :-). # extract original event time, if present if msg[:2] == '[[': pos = msg.find(']]') try: tm = float(msg[2:pos]) except: pass msg = msg[pos+2:] # use rest of message # extract log facility and priority, if present if msg[:1] == '<': pos = msg.find('>') fac, pri = LOG_UNPACK(int(msg[1:pos])) msg = msg[pos+1:] elif msg and msg[0] < ' ': fac, pri = LOG_KERN, ord(msg[0]) msg = msg[1:] else: fac, pri = None, None # check if we can discard this message if pri is not None and pri > self.priority: return # build the client address/name mapping client = client_address[0] try: host = self.hostmap[client] except KeyError: try: host = socket.gethostbyaddr(client)[0] host = host.split('.')[0] # keep only the host name except socket.error: host = client self.hostmap[client] = host # print the message tm = time.strftime(self.timefmt, time.localtime(tm)) if 0: # fac is not None and pri is not None: fp = ' <%s,%s>' % (fac_names[fac], pri_names[pri]) else: fp = '' #sys.stdout.write(tm + ' ' + host + fp + ': ' + msg.strip() + '\n') logger.info(tm + ' ' + host + fp + ': ' + msg.strip()) # stop the server if the magic stop string appears in the message if msg.find(self.stop_magic) >= 0: # print 'stopped.' logger.info('stopped.') self.stop() def initLogger(): filename = None if len(sys.argv) >= 2: if sys.argv[1][-4:] == '.txt' or sys.argv[1][-4:] == '.log': filename = sys.argv[1] logger = logging.getLogger() logger.setLevel(logging.INFO) if filename: handler = logging.handlers.RotatingFileHandler(filename=filename, maxBytes=200000, backupCount=10) else: handler = logging.StreamHandler() logger.addHandler(handler) return logger if __name__ == '__main__': logger = initLogger() Syslogd(timefmt='%H:%M:%S', logger=logger).serve()
initLogger関数を付け加えた。 以下のように実行する。
python syslogd.py log.txt
……といった感じで実行すると、ログがlog.txtに保存される。RotatingFileHanderのところのmaxBytesがログファイルの最大サイズ。それを超えると、backupCountで指定した数だけバックアップされる。(log.txt.1, log.txt.2…… log.txt.n のように)
止めるときは、以下のようなスクリプトを使う。
import logging import logging.handlers log = logging.getLogger() syslog = logging.handlers.SysLogHandler(('localhost', 514)) log.addHandler(syslog) log.warning('_stop')
もちろん、こんなんじゃなくて、Linuxなどでloggerコマンドを使ってもよいはず。localhostから送信すると、うまく受信できないことがあるみたい。(二回実行しないと止まらなかったりする) なんでだろう。
ログを受信するのが楽しくなって (手段が目的になってしまった) 手元の iBook G4のログも飛ばしてみたくなった。 /etc/syslog.conf を書き換えて、
sudo launchctl stop com.apple.syslogd
とする。色々検索してみると、 stopしたあとにstartせよ、と書いてあるところと、startする必要はない、と書いてあるところがある。
launchctl で stop すると com.apple.syslogd は、常駐する設定になっているため、自動で再起動される。
maruko2 Note.
手元のiBook G4 (Tiger)では、stop後にstartしなくてもps axするとsyslogdがでてくるので大丈夫っぽい感じ。
17:49:55 192.168.*.*: Apr 13 17:49:55 ibookg4 mDNSResponder: -1: DNSServiceRegister("iTunes_Ctrl_70C3************", "_dacp._tcp.", "local.", 3689) failed: Client id -1 invalid (-65549)
こんなのがでてくるけどなんだろう。意味不明。(猫に小判だ)
で、ログといえば tail -f なんだけど、それもPythonで書いたやつをつかっている。
以下のスクリプトは、 これ→「ASPN : Python Cookbook : tail -f in Python」をコピって、ちょっと書き加えたもの。
import sys import os.path import time from msvcrt import * if len(sys.argv) < 2: print "ERROR: No file specified" sys.exit() filename = sys.argv[1] if not os.path.exists(filename): print "ERROR: File not found: " + filename sys.exit() if not os.path.isfile(filename): print "ERROR: This is not valid file: " + filename sys.exit() f = file(filename, 'r'); try: while 1: where = f.tell() line = f.readline() if not line: time.sleep(1) f.seek(where) else: print line, # already has newline while kbhit(): print getch() except KeyboardInterrupt: print "Done." sys.exit()
快適、快適。