ログファイルに出力
VBAの変数の値や実行状態を確認する際に「Debug.Print」を利用してイミディエイトウィンドウに結果を出力することができます。
ただし、イミディエイトウィンドウは過去の出力内容は消されていきますし、出力内容や確認したい内容が多い場合には不向きです。ウィンドウも小さいため見やすいものではありません。
そういう場合は、外部のテキストファイル(ログファイル)に出力して扱いやすくしましょう。
以下にログファイルに書き込みを行うLogクラスと、その利用サンプルのコードを紹介します。
事前準備
以下のコードではFileSystemObjectを参照設定していることが前提になっています。
VBAの画面を開き、ツールメニュー→参照設定 で「Microsoft Scripting Runtime」にチェックを付けてください。
FileSystemObjectを利用する際に多くのサイトではCreateObject関数を使う方法がよく紹介されていますが、処理速度が遅いのでここでは参照設定を使う方法を採用しています。
Logクラス
以下のログファイル出力クラスのコードはコピペしてそのまま使えます。
利用する場合は以下のコードをクラスモジュールとして追加してください。クラス名は「Log」です。
汎用的に利用する場合はPersonal.xlsbのクラスモジュールとして追加してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
Option Explicit '//---------------------------------------------------------------------------- '// 構造体:クラス内ではPrivate指定必須 '//---------------------------------------------------------------------------- '// SYSTEMTIME構造体 Private Type SYSTEMTIME wYear As Integer '// 年 wMonth As Integer '// 月 wDayOfWeek As Integer '// 曜日(0:日曜、1:月、2:火、3:水、4:木、5:金、6:土) wDay As Integer '// 日 wHour As Integer '/ 時 wMinute As Integer '// 分 wSecond As Integer '// 秒 wMilliseconds As Integer '// ミリ秒 End Type '//---------------------------------------------------------------------------- '// DLL参照:クラス内ではPrivate指定必須 '//---------------------------------------------------------------------------- '// GetLocalTime:システム日時取得 '// 64bit版 #If VBA7 And Win64 Then Private Declare PtrSafe Sub GetLocalTime Lib "kernel32" Alias "GetLocalTime" (lpSystemTime As SYSTEMTIME) '// 32bit版 #Else Private Declare Sub GetLocalTime Lib "kernel32" (lpSystemTime As SYSTEMTIME) #End If '//---------------------------------------------------------------------------- '// モジュール変数 '//---------------------------------------------------------------------------- Private m_Fs As FileSystemObject '// FileSystemObject変数 Private m_Ts As TextStream '// TextStream変数 Private m_Path As String '// ログファイルパス Private m_LogFile As File '// ログファイル制御用File変数 Private m_lMaxSize As Double '// ログファイル最大サイズ(超えた場合はバックアップ化) '//---------------------------------------------------------------------------- '// 関数名 :Class_Initialize '// 引数 :なし '// 戻り値 :なし '// 機能 :コンストラクタ '// 備考 : '//---------------------------------------------------------------------------- Private Sub Class_Initialize() End Sub '//---------------------------------------------------------------------------- '// 関数名 :Class_Terminate '// 引数 :なし '// 戻り値 :なし '// 機能 :デストラクタ '// 備考 : '//---------------------------------------------------------------------------- Private Sub Class_Terminate() End Sub '//---------------------------------------------------------------------------- '// 関数名 :Init '// 引数 :(I) sLogFilePath As String :ログファイルパス '// :(I) iMaxSize = 1000000 :ログファイル最大サイズ(省略時は約1MB) '// 戻り値 :なし '// 機能 :Logクラスを利用するための初期処理を行う '// 備考 : '//---------------------------------------------------------------------------- Public Sub Init(sLogFilePath As String, Optional iMaxSize = 1000000) '// FileSystemObjectの初期化 Set m_Fs = New FileSystemObject '// ログファイルパス保持 m_Path = sLogFilePath '// ログファイル最大サイズ指定 m_lMaxSize = iMaxSize End Sub '//---------------------------------------------------------------------------- '// 関数名 :WriteLog '// 引数 :(I) sMsg As String :書き込む内容 '// 戻り値 :なし '// 機能 :ログファイルに文字列を書き込む '// 備考 :書き込み内容の後ろで改行を行う '//---------------------------------------------------------------------------- Public Sub WriteLog(sMsg As String) On Error Resume Next Dim sNowDateTime As String '// システム日時 Dim sWriteLine As String '// 書き込みログ '// ログファイルのフォルダが存在しない場合 If (m_Fs.FolderExists(m_Fs.GetParentFolderName(m_Path)) = False) Then Debug.Print "ログファイルのフォルダが存在しないためログファイルを作成できません。[" & m_Path & "]" Exit Sub End If '// ログファイルが存在する場合 If (m_Fs.FileExists(m_Path) = True) Then '// ログファイルのFileオブジェクト取得 Set m_LogFile = m_Fs.GetFile(m_Path) '// ログファイルサイズが最大サイズに達している場合 If (m_LogFile.Size > m_lMaxSize) Then Call Rename End If End If '// ファイルオープン '// 第一引数:ログファイルのパス '// 第二引数:追加書き込みモード '// 第三引数:ファイルが無い場合は新規作成 '// 第四引数:ASCIIファイルとして扱う Set m_Ts = m_Fs.OpenTextFile(m_Path, ForAppending, True, TristateFalse) If Err.Number <> 0 Then Debug.Print "エラー発生:No." & Err.Number & "[" & Err.Description & "]" Exit Sub End If '// 現在日時取得 Call GetDateTime(sNowDateTime) '// 書き込み内容編集 sWriteLine = sNowDateTime & " " & sMsg '// 書き込み Call m_Ts.WriteLine(sWriteLine) '// ファイルを閉じる m_Ts.Close End Sub '//---------------------------------------------------------------------------- '// 関数名 :GetDateTime '// 引数 :(I) sNowDateTime As String :現在日時(YYYY/MM/DD hh:mm:ss.mmm) '// 戻り値 :なし '// 機能 :現在日時を取得する '// 備考 : '//---------------------------------------------------------------------------- Private Function GetDateTime(sNowDateTime As String) Dim t As SYSTEMTIME '// システム日時取得 Call GetLocalTime(t) sNowDateTime = CStr(t.wYear) & "/" & Format(t.wMonth, "00") & "/" & Format(t.wDay, "00") & " " & Format(t.wHour, "00") & ":" & Format(t.wMinute, "00") & ":" & Format(t.wSecond, "00") & "." & Format(t.wMilliseconds, "000") End Function '//---------------------------------------------------------------------------- '// 関数名 :Rename '// 引数 :なし '// 戻り値 :なし '// 機能 :ログファイルの名前を現在日時を付与した名前に変更する '// 備考 :変更前 a.log 変更後 a_YYYYMMDD_hhmmss.mmm.log '//---------------------------------------------------------------------------- Private Sub Rename() Dim sNowDateTime As String '// システム日時 Dim sEditDt As String '// バックアップファイル用の日時文字列 '// 現在日時取得 Call GetDateTime(sNowDateTime) sEditDt = Replace(sNowDateTime, "/", "") sEditDt = Replace(sEditDt, ":", "") sEditDt = Replace(sEditDt, " ", "_") '// ログファイル名変更 Name m_LogFile.Path As m_LogFile.ParentFolder & "\" & m_Fs.GetBaseName(m_LogFile.Path) & "_" & sEditDt & "." & m_Fs.GetExtensionName(m_LogFile.Path) End Sub |
Logクラスの構成説明
Logクラスの構成について説明します。
SYSTEMTIME構造体 | Win32APIのGetLocalTime関数の引数に利用します。 |
GetLocalTime | システム日時を取得するWin32APIの関数です。ミリ秒を使いたかったため利用しています。ログファイルに書き込む際の日時文字列とバックアップファイル作成時のファイル名に付与する日時を取得する際に利用します。ミリ秒の精度は10から20ミリ秒程度とあまり良くありません。 |
モジュール変数 | クラス全体で使用する変数をモジュール変数として定義しています。 |
Class_Initialize | Logクラスのコンストラクタ(インスタンス作成時の処理)です。特にやることはないので処理はありません。 |
Class_Terminate | Logクラスのデストラクタ(インスタンス解放時の処理)です。特にやることはないので処理はありません。 |
Init | 書き込み先のログファイル名の指定とログファイルに書き込める最大サイズを指定します。最大サイズは省略可能で、省略時は約1MBにしています。通常は書き込み先のログファイルが変わることは無いと思いますが、処理途中で書き込み先を変更したい場合は再度呼び出すことが可能です。 |
WriteLog | ログファイルに指定文字列を書き込みます。ログファイルを格納するフォルダが無い場合や既に別のアプリケーションでログファイルが開かれてロック(書き込み不可)状態になっている場合はイミディエイトウィンドウにその内容を出力してログファイルへの書き込みを行いません。Init関数で指定されたログファイル最大サイズを超えている場合はバックアップファイルを作成し、その後新しくログファイルを作成して書き込みを行います。 |
GetDateTime | GetLocalTime関数を用いてシステム日時を取得します。取得後にYYYY/MM/DD hh:mm:ss.mmmの書式にします。 |
Rename | ログファイル名を変更します。変更後の名前は「元の名前+現在日時(_YYYYMMDD_hhmmss.mmm)+拡張子」になります。 |
Logクラスの利用方法
Logクラスの利用方法を以下のコードをふまえながら説明します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Sub LogUseTest() Dim oLog As New Log '// Logクラス '// ログファイルパスの指定(と、必要な場合はログファイル最大サイズの指定) ' Call oLog.Init("C:\web\test\logtest.log", 1000) Call oLog.Init("C:\web\test\logtest.log") '// ログファイルに書き込み Call oLog.WriteLog("ここにログファイルに書き込む内容の文字列を指定します") Call oLog.WriteLog("ああああ") Call oLog.WriteLog("いいい") Call oLog.WriteLog("") Call oLog.WriteLog("ええ") Call oLog.WriteLog(CStr(12345)) '// 書き込み先変更 Call oLog.Init("C:\web\test\b.log") Call oLog.WriteLog("ああああ") Call oLog.WriteLog("いいい") Call oLog.WriteLog("") End Sub |
2行目 | 最初にLogクラス用の変数を用意します。このときにNewを指定します。 |
6行目 | Init関数を使い、書き込み先のログファイルパスを指定します。Init関数は通常このときに一度だけ利用します。5行目のようにログファイルの最大サイズを指定することも可能です。省略時は約1MBを最大サイズとしています。ログファイルがそのサイズを超えているとファイル名をバックアップ用に変更して再度新たに書き込みを行います。 |
8行目 | WriteLog関数で書き込み文字列を指定することでログファイルに書き込まれます。ログファイルには処理時間と書き込み文字列が1行で書き込まれ、改行コードが設定されます。 |
17行目 | 再度Init関数を使い、書き込み先を変更できます。これ以降のWriteLog関数は変更後の書き込み先のログファイルに書き込みます。 |
18行目 | 変更後の書き込み先に書き込みます。 |
実行結果
2つのファイルが出力されます。
logtest.log
b.log
改善点
このLogクラスには処理速度の点で改善点があります。
WriteLog関数は呼び出す度にファイルのオープンとクローズを行っています。ファイルのオープン処理は結構時間が掛かる処理のため、そこで時間をロスしています。
ただし、処理途中でログファイルを確認する場合などはこちらの方式であれば常に最新状態のログが確認できる利点もあります。
もし処理速度を優先させたい場合はWriteLog関数内でのオープン処理とクローズ処理をやめて、Init関数とRename関数でオープン、Rename関数とデストラクタでクローズするようにするとオープンとクローズが最初と最後にしか行われないようになるため処理速度は上がります。