Pythonでログ比較(1) ~ログ抽出編~

ベル

今回の授業は、最近進めている『ログ比較の自動化』について紹介していこうと思います。ネットワークの設定変更作業を実施すると、必ず事前と事後でログの確認を行います。そのログの確認対象機器が増えたり、確認のコマンドが増えたりすると、作業時間が足りなくなります。そして、確認を減らして対応してたりしませんか?それは非効率な目視確認をやっているからなんです。それを自動化して品質を下げずに時間を短縮するような工夫を一緒にやっていきましょう!

 

はじめに

最初に少しネットワーク作業の手順について解説しておきます。Winmergeでログ比較の基礎編でも解説させていただきましたが、ネットワーク機器の設定変更時に手順としては、以下の黒板のようにするのが一般的です。この際に、事前ログと事後ログを比較して、想定通りの差分になっていることを確認することが大切となります。

 

 

この比較対象機器が5台程度であれば、目視でも出来ますが、何百台って設定変更をしていたらすべて見るのは、かなり困難となります。また、確認すべきコマンドも増えれば増えた分だけログは長くなりますので、複雑さは増すばかりとなり、確認漏れが発生するのが、よくあります。これを出来るだけ楽にしようと言うのが、この授業の試みとなります。

 

 

前提条件

今回、作成するのは、たくさんの事前ログ/事後ログから必要なのコマンドだけを抽出して、比較するというマクロを作成してみたいと思いますが、Cisco機器のログには単純比較できるものと、そうでないものがあります。今回、教えていくのは、比較的簡単な比較ができるコマンドを対象にしていきます。

 

その上でまずは、マクロを使う上での前提条件を決めておこうと思います。というのは、プログラムを書く上で『指定の文字があったら○○しなさい』や、『○○までを抜き出しなさい』などと指定することがあります。そのため、すべてを自由にしてしまったら処理のトリガーがなくなってしまうので、ある程度のルールを決めておく必要があります。

 

 

この以降に、前提条件について解説していきますね。

 

ディレクトリ構造

今回使用するディレクトリは、『01_事前ログ』『02_事後ログ』『03_事前ログ抽出』『04_事後ログ抽出』です。このようにログ保存するディレクトリは決めておく(作っておく)ことは、マクロを使用する以前に大変重要なことで、これをやっておかないと作業後にログを探しても見つからなくなったりするので、チームでルール化しておくことをおすすめします。

 

それでは、話を戻しますね。先程、指定したディレクトリの使い方を解説していきますが、最初の『01_事前ログ』に作業前に取得したログを全て保存します。そのログから確認したいコマンドだけを抽出して、『03_事前ログ抽出』にコピーします。事後ログも同様の仕組みで使用するので準備をお願いします。

 

ディレクトリ構造は、以下の通りです。

この『work』ディレクトリ直下にPythonファイルを作成していきます。Pythonファイルの作成方法が不明の方は、以前の授業で復習しておいてくださいね。

 

Pythonでログ解析

Pythonでログ解析(1) ~パソコンにPythonを入れるまで~

 

ログファイル名

次に、ログファイル名の統一をしていこうと思います。僕がよく使うログ取得マクロでは、定番のログファイル名です。以下のようにしておかないとログのホスト名が使えないので、合わせるようにしてください。

 

 

 

指定コマンド

ログ取得する際に、省略形のコマンドを使用したりすることがあると思いますが、通常であれば問題ないのですが、マクロで比較しようとするときちんと決めたコマンドでなければいけません。今回しようするコマンドは、『show cdp neighbors』の事前/事後を比較してみようと思います。

 

 

Pythonの作成

それでは、Pythonのコードを書いていこうと思いますが、今回の裏テーマとして、なるべくモジュールを使わなず、本来のPythonコードで書いてみたいと思います。その分、重複したり分かりづらい部分もあるかもしれませんが、頑張っていきましょう。

 

インポートの指定

先程、モジュールをなるべく使用しないようにと宣言しましたが、どうしても使用しなければいけないモジュールだけインポートしておきたいと思います。コードを書いてOSモジュールと正規表現モジュールを入れておきます。

 

import os
import re

 

ログ保存先の指定

次に、事前ログと事後ログを保存しているディレクトリを宣言しておきます。この保存先の指定は、相対パスで指定できるので、Pythonファイルが保存されている場所からの指定をしておきます。先程のディレクトリ構造を参考に確認していみてください。

 

bf_dir = os.listdir(’01_事前ログ/’)
af_dir = os.listdir(’02_事後ログ/’)

 

 

事前ログから抽出

あまり詳しく書くと長くなってしまうので、簡単に解説していきます。

 

ファイルパスの作成

まずは機器のホスト名をファイル名から取得していきます。先程のログファイル名から最初の『アンダーバー(_)』を見つけて、それより前をホスト名として取得しましょう。そのために、ログファイル名を『アンダーバー』で区切って、『host_name1』という変数に入れておきます。このホスト名を使って事前ログファイルのパスを『path1』とするのと、事前ログから『show cdp neighbors』のログだけを抽出したテキストファイルを保存するパスを『path2』とします。

 

for log in bf_dir:
    host_name1 = log.split(‘_’)
    path1 = ’01_事前ログ/’ + log
    path2 = ’03_事前ログ抽出/’ + host_name1[0] + ‘_cdp_neighbors.txt’

 

ここでファイルのパスを作っておいて、後から変数を使って呼び出すようにするので、覚えておいてくださいね!

 

テキストファイルの読み込み

先程作成した事前ログが保存されているディレクトリパスを使って、ログファイルを1行ずつ読み込みましょう。そのまま使用しようとすると、1行で全てまとまってしまうので、改行単位で区切っておきたいと思います。

 

    with open(path1, ‘r’) as fr:
        lines = fr.readlines()

    lines_strip = [line.strip() for line in lines]

 

 

指定コマンドの検索

次に抽出したいコマンドが何行目に書かれているかを検索します。ここでご注意してもらいたいのは、最初の1行目が『0』と表示されてしまいますので、「あれ?1行少ない?」って思ってしまうかもしれないですが、心配しなくて大丈夫です。

 

    num_i = [i for i, line in enumerate(lines_strip,1) if ‘show cdp neighbors’ in line]
    i_s = num_i[0]

 

次にログの最後を取得します。先程の『show cdp neighbors』のコマンドを取得した次に、来るコマンドの行を検索します。しかし、次に来るコマンドなんて毎回違いますよね?そうなると検索する文字列が難しくなってしまうのですが、先程作成して変数をうまく利用します。

 

    num_e = [e for e, line in enumerate(lines_strip,1) if host_name1[0] in line]

    for i_e in num_e:
        if i_e > num_i[0]:
            break

 

『host_name1』を作ったのを覚えていますか?ファイル名からホスト名を抽出しましたよね?これを使ってCiscoのログでは必ずホスト名がプロンプトに使われているので、これを探してその前の行までをログとして抽出することとしましょう。

 

 

ログの書き出し

それでは、ここまで抽出してきたログをテキストファイルに書き出していこうと思いますが、少し工夫が必要です。黒板を見てください。

 

 

このように、本当に必要な部分だけを抽出して書き込むことで余計な差分がなくなり、チェックが容易になります。コードにすると以下の通りとなります。

 

    with open(path2, ‘w’) as fw1:
        fw1.writelines(lines[i_s – 1])
            for i in lines[i_s + 4:i_e – 1]:
            pattern1 = re.compile(r’\s\s+’)
            log_l = re.sub(pattern1, ‘ ‘, i)
            print_log = log_l.split(‘ ‘)
            print_log = print_log[0:2] + print_log[4:]

            fw1.writelines(‘ ‘.join(map(str, print_log)))

 

ここでポイントとなるのが、以下の2つになります。

【必要な行の抽出】

最初の行は『fw1.writelines(lines[i_s – 1])』は、ホスト名と『show cdp neighbors』を表示するためのコードです。その次に書かれている『for i in lines[i_s + 4:i_e – 1]:』で、実際に確認したいログの行を指定することになります。ここで『show cdp neighbors』が表示されている行から5行下から、次のホスト名までを指定して書き込むようにしてあります。

 

        fw1.writelines(lines[i_s – 1])
            for i in lines[i_s + 4:i_e – 1]:

 

【必要な部分の抽出】

ここで指定しているのが、出力されたログをスペースで区切って最初から3つ目までと、5つ目から最後までを抽出してログに書き込むようにしています。ここで指定したい部分を変更したい場合は、数値を変更してもらえばと思います。

            print_log = print_log[0:2] + print_log[4:]

 

いまいちピンと来ない方は、以下のログで確認してみてください。桃色で書かれている部分だけが不要となるので、取り除くようなコードにしてあります。

 

 0     1   2   3  4 5 6     7          8   9

RT2 Fas 0 144 R S I 1812W-P Fas 0

 

コードの紹介

これと同じことを事後ログでも行えば、必要なログだけを抽出することが出来ます。実際に作成したコードを紹介しておきますので、確認してみてください。

 

import os
import re

bf_dir = os.listdir(’01_事前ログ/’)
af_dir = os.listdir(’02_事後ログ/’)

for log in bf_dir:
    host_name1 = log.split(‘_’)
    path1 = ’01_事前ログ/’ + log
    path2 = ’03_事前ログ抽出/’ + host_name1[0] + ‘_cdp_neighbors.txt’

    with open(path1, ‘r’) as fr:
        lines = fr.readlines()

    lines_strip = [line.strip() for line in lines]

    num_i = [i for i, line in enumerate(lines_strip,1) if ‘show cdp neighbors’ in line]
    i_s = num_i[0]

    num_e = [e for e, line in enumerate(lines_strip,1) if host_name1[0] in line]

    for i_e in num_e:
        if i_e > num_i[0]:
            break

    with open(path2, ‘w’) as fw1:
        fw1.writelines(lines[i_s – 1])
        for i in lines[i_s + 4:i_e – 1]:
            pattern1 = re.compile(r’\s\s+’)
            log_l = re.sub(pattern1, ‘ ‘, i)
            print_log = log_l.split(‘ ‘)
            print_log = print_log[0:2] + print_log[4:]

            fw1.writelines(‘ ‘.join(map(str, print_log)))

for log in af_dir:
    host_name1 = log.split(‘_’)
    path1 = ’02_事後ログ/’ + log
    path2 = ’04_事後ログ抽出/’ + host_name1[0] + ‘_cdp_neighbors.txt’

    with open(path1, ‘r’) as fr:
        lines = fr.readlines()

    lines_strip = [line.strip() for line in lines]

    num_i = [i for i, line in enumerate(lines_strip,1) if ‘show cdp neighbors’ in line]
    i_s = num_i[0]

    num_e = [e for e, line in enumerate(lines_strip,1) if host_name1[0] in line]

    for i_e in num_e:
        if i_e > num_i[0]:
            break

    with open(path2, ‘w’) as fw1:
        fw1.writelines(lines[i_s – 1])
            for i in lines[i_s + 4:i_e – 1]:
            pattern1 = re.compile(r’\s\s+’)
            log_l = re.sub(pattern1, ‘ ‘, i)
            print_log = log_l.split(‘ ‘)
            print_log = print_log[0:2] + print_log[4:]

            fw1.writelines(‘ ‘.join(map(str, print_log)))

 

 

まとめ

ベル

今回の授業は、ここまでとします。以前に紹介させてもらったフリーソフト(Winmerge)で、『03_事前ログ抽出』と『04_事後ログ抽出』を比較してもらえれば、差分を簡単に確認することができますが、それではちょっと寂しいですよね。ですので、次の授業でログ比較のコードを紹介していますので、こちらから~。

 

Pythonでログ比較(2) ~ログ比較編~

 

【Pythonプログラミングのツボとコツがゼッタイにわかる本】

Pythonプログラミングのツボとコツがゼッタイにわかる本

新品価格
¥2,916から
(2018/7/4 22:36時点)

【詳細! Python 3 入門ノート】

詳細! Python 3 入門ノート

新品価格
¥2,894から
(2018/7/4 22:29時点)

【退屈なことはPythonにやらせよう】

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

新品価格
¥3,996から
(2018/7/4 22:46時点)

Follow me!

Pythonでログ比較(1) ~ログ抽出編~” に対して1件のコメントがあります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です