2つのログファイルをマージする

2つのログファイルをマージするツールを Python で作りました。
【作った理由】
Windows にダウンロードしたりしたログファイルを簡単にドラッグアンドドロップの操作で
ログ出力のログのタイムラインに沿ってマージしたい。
→ パラメータで2個のログファイルパスと出力先を指定なんて面倒な操作をしたくない
・PyInstaller で作れば Python をインストールしていないPCでも実行できるツールを提供できる。
・手作業でログファイルを開いてコピペ作業でマージするなんて、ゾッとするおぞましさ。。。

【ツールの仕様】
・ログファイル中身の各行の先頭は、以下の日付時刻書式のいずれかで書かれている。
yyyy-MM-dd HH:mm:ss.SSS
yyyy-MM-dd HH:mm:ss.SSSSSS
yyyy-MM-dd HH:mm:ss
yyyy/MM/dd HH:mm:ss.SSS
yyyy/MM/dd HH:mm:ss.SSSSSS
yyyy/MM/dd HH:mm:ss

年月日が、'/' 区切りか、'-' 区切り どちらでも良い、秒以下は、ミリ秒かマイクロ秒の精度か、秒以下無し。
・1つのログの中に日付書式以降に改行があれば、その改行された行はその時刻のログとしてマージされる。
・マージされて出力するフォルダを指定して、出力される結果のファイル名は、
 マージ対象のファイル名(拡張子を除く)を '_' アンダーバー文字で連結したファイル名で、拡張子は .log とする
tkinterは使用しない。tcl/tk は使わない。

ツールの起動の __main__ 部分

if __name__ == '__main__':
    args = sys.argv
    if len(args)==2 or len(args)==3:
        if len(args)==2:
            if os.path.isfile(args[1]):
                print(f'1個目マージ対象ログファイル :{args[1]}')
                log2nd = input('2個目マージ対象ログファイル ->')
                if os.path.isfile(log2nd):
                    outdir = input('マージ出力先フォルダ ->')
                    if os.path.isdir(outdir):
                        logmerge = Logmerge()
                        logmerge.exec(args[1], log2nd, outdir)
                    else:
                        print(f'out directory Error : {outdir}')
                else:
                    print(f'Not Found logfile : {log2nd}')
            else:
                print(f'Not Found logfile : {args[1]}')
        else:
            if os.path.isfile(args[1]):
                print(f'1個目マージ対象ログファイル :{args[1]}')
                if os.path.isfile(args[2]):
                    print(f'2個目マージ対象ログファイル :{args[2]}')
                    outdir = input('マージ出力先フォルダ ->')
                    if os.path.isdir(outdir):
                        logmerge = Logmerge()
                        logmerge.exec(args[1], args[2], outdir)
                    else:
                        print(f'out directory Error : {outdir}')
                else:
                    print(f'Not Found logfile : {args[2]}')
            else:
                print(f'Not Found logfile : {args[1]}')
            pass
    else:
        print('Usage: logmerge.exe logfile1 logfile2')
    os.system("PAUSE")

PyInstaller で EXE化したファイルアイコンに、目的のログファイルをドラッグアンドドロップすれば、
sys.argv にそのパスが入ってくることを利用している。
さらに、起動後、input() 関数で、2個目のログファイルのドラッグ、そして
出力先フォルダのドラッグも入力される。
input() 関数で、ドラッグ入力されたら、改行キー(リターンキー)叩けば良いだけだ

class Logmerge の内容

import os
import sys
import datetime
import re

class Logmerge():
    def __init__(self):
        self.timeptns = [
(r'^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9])\.[0-9]{3,6}$', '%Y-%m-%d %H:%M:%S.%f'),
(r'^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9])$', '%Y-%m-%d %H:%M:%S'),
(r'^\d{4}/(0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9])\.[0-9]{3,6}$', '%Y/%m/%d %H:%M:%S.%f'),
(r'^\d{4}/(0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9])$', '%Y/%m/%d %H:%M:%S'),
        ]
    def exec(self, fileA, fileB, outdir)->None:
        with open(fileA, mode='r', newline='', encoding='utf-8') as fa:
            with open(fileB, mode='r', newline='', encoding='utf-8') as fb:
                outname = os.path.splitext(os.path.basename(fileA))[0] + "_" + os.path.splitext(os.path.basename(fileB))[0]
                outpath = "%s\%s.log" % (outdir, outname)
                with open(outpath, mode='w', encoding='utf-8') as out:
                    a = fa.readline()
                    b = fb.readline()
                    while len(a) > 0 or len(b) > 0:
                        dta = self.logtime(a)
                        dtb = self.logtime(b)
                        ca = dta != None
                        cb = dtb != None
                        if dta != None and dtb != None:
                            if dta < dtb:
                                out.write(a.rstrip()+'\n')
                                while True:
                                    a = fa.readline()
                                    if len(a)==0: break
                                    dta = self.logtime(a)
                                    if dta==None:
                                        out.write(a.rstrip()+'\n')
                                    else:
                                        break
                            else:
                                out.write(b.rstrip()+'\n')
                                while True:
                                    b = fb.readline()
                                    if len(b)==0: break
                                    dtb = self.logtime(b)
                                    if dtb==None:
                                        out.write(b.rstrip()+'\n')
                                    else:
                                        break
                        else:
                            if len(a) > 0:
                                out.write(a.rstrip()+'\n')
                                while True:
                                    a = fa.readline()
                                    if len(a) == 0: break
                                    dta = self.logtime(a)
                                    if dta == None:
                                        out.write(a.rstrip()+'\n')
                                    else:
                                        break
                            if len(b) > 0:
                                out.write(b)
                                while True:
                                    b = fb.readline()
                                    if len(b) == 0: break
                                    dtb = self.logtime(b)
                                    if dtb == None:
                                        out.write(b.rstrip()+'\n')
                                    else:
                                        break
                    print(f'マージ完了\n{outpath}')
        pass
    def logtime(self, data:str)->datetime.datetime:
        matches = [t for t in self.timeptns if re.fullmatch(t[0], data[:26])]
        if len(matches) == 1:
            return datetime.datetime.strptime(data[:26], matches[0][1])
        else:
            matches = [t for t in self.timeptns if re.fullmatch(t[0], data[:23])]
            if len(matches) == 1:
                return datetime.datetime.strptime(data[:23], matches[0][1])
        return None

実行例
ログファイル1個目をドラッグする

起動されて、

改行してから、2個目もドラッグする

2個入れたら、改行する

出力先フォルダをドラッグで入力して改行する

マージ完了する。
はじめに2個ログファイルを選択してドラッグすれば、出力先のフォルダのドラッグ入力だけで済む。