ねらい:Excelマクロの「誰が・いつ・何をしたか」を残す“監査ログ”の型を作る
マクロが便利になればなるほど、あとからこういうことを聞かれる場面が増えます。
誰がこのファイルをいつ更新したのか。
このボタンを押したのは誰か。
このデータが書き換わったのはいつか。
ここで目指す「監査ログツール」は、難しいことはせずに、次のような“型”を作ることです。
マクロの中で「重要な操作」が行われたタイミングで、専用のログシートに
日時、ユーザー名、処理名、対象シート・セル、前後の値、結果(OK/NG)、メッセージ
を1行追記していく。
これをテンプレ化しておけば、どんなマクロにも簡単に「監査ログ機能」を組み込めるようになります。
設計:監査ログシートの構造と「1行の意味」を決める
監査ログシートの列設計
まずは、ログを書き込む専用シートを1枚用意します。名前は「AuditLog」とします。
1行目にヘッダを置き、2行目以降にログを追記していく形です。
列の例は次の通りです。
A列:LogID(連番)
B列:LogTime(日時)
C列:UserName(Windowsユーザー名など)
D列:Action(処理名。例:IMPORT_CSV、UPDATE_MASTER など)
E列:TargetSheet(対象シート名)
F列:TargetRange(対象セル範囲。例:A2、A2:C10 など)
G列:BeforeValue(変更前の値。必要に応じて)
H列:AfterValue(変更後の値)
I列:Result(OK / NG)
J列:Message(エラー内容や補足)
ここで大事なのは、「1行が1つの“出来事”を表す」という考え方です。
マクロの中で「ここは後から追跡したい」というポイントごとに、この1行を追加していきます。
共通基盤:監査ログを書き込む汎用プロシージャ
監査ログ1行を書き込む Sub
まずは「ログ1行を書く」ことだけに特化した共通部品を作ります。
' ModAudit_Base.bas
Option Explicit
Public Sub WriteAuditLog(ByVal actionName As String, _
ByVal targetSheet As String, _
ByVal targetRange As String, _
Optional ByVal beforeValue As String = "", _
Optional ByVal afterValue As String = "", _
Optional ByVal result As String = "OK", _
Optional ByVal message As String = "")
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("AuditLog")
On Error GoTo 0
If ws Is Nothing Then
Set ws = ThisWorkbook.Worksheets.Add
ws.Name = "AuditLog"
ws.Range("A1:J1").Value = Array("LogID", "LogTime", "UserName", "Action", "TargetSheet", "TargetRange", "BeforeValue", "AfterValue", "Result", "Message")
End If
Dim nextRow As Long
nextRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
Dim logId As Long
If nextRow = 2 Then
logId = 1
Else
logId = ws.Cells(nextRow - 1, "A").Value + 1
End If
ws.Cells(nextRow, "A").Value = logId
ws.Cells(nextRow, "B").Value = Now
ws.Cells(nextRow, "C").Value = Environ$("Username")
ws.Cells(nextRow, "D").Value = actionName
ws.Cells(nextRow, "E").Value = targetSheet
ws.Cells(nextRow, "F").Value = targetRange
ws.Cells(nextRow, "G").Value = beforeValue
ws.Cells(nextRow, "H").Value = afterValue
ws.Cells(nextRow, "I").Value = result
ws.Cells(nextRow, "J").Value = message
ws.Columns("A:J").AutoFit
End Sub
VBここでの重要ポイントを少し深掘りします。
監査ログシートが存在しなければ自動で作るようにしておくと、「シートを作り忘れてエラー」が起きません。
LogID は単純な連番で構いませんが、「前行の値+1」で振ることで、途中で行を削除しても一意性が保たれます。
UserName は Environ$("Username") を使うと、Windowsログオン名が取れます。社内で「誰が押したか」を知るには十分なことが多いです。
Result と Message を分けておくことで、「成功ログ」と「エラーログ」を後からフィルタしやすくなります。
この Sub を「どのマクロからも呼べる共通部品」として置いておくのが、監査ログツールの土台です。
例題1:マクロ開始・終了をログに残す(処理単位の監査)
処理の入口と出口にログを挟む
まずは一番簡単な使い方として、「マクロの開始と終了」をログに残す例を見てみます。
' ModSample_Process.bas
Option Explicit
Public Sub Import_Csv_Sample()
Call WriteAuditLog("IMPORT_CSV_START", "N/A", "N/A", "", "", "OK", "CSV取込開始")
On Error GoTo ErrHandler
' ここにCSV取込処理を書く(例示)
' ...
Call WriteAuditLog("IMPORT_CSV_END", "Data", "A1", "", "", "OK", "CSV取込正常終了")
Exit Sub
ErrHandler:
Call WriteAuditLog("IMPORT_CSV_ERROR", "Data", "A1", "", "", "NG", "エラー: " & Err.Number & " - " & Err.Description)
Err.Clear
End Sub
VBこの例では、次の3つのタイミングでログを書いています。
処理開始時(IMPORT_CSV_START)
正常終了時(IMPORT_CSV_END)
エラー発生時(IMPORT_CSV_ERROR)
これだけでも、「いつ誰がこのマクロを走らせたか」「エラーで止まったのか、正常に終わったのか」が一目で分かるようになります。
ここで大事なのは、「ログを書くこと自体は軽い処理なので、遠慮せずに入れていい」という感覚です。
特にエラー時のログは、あとから原因を追うときの命綱になります。
例題2:セルの値変更をログに残す(データ変更の監査)
Before/After を取ってから書き込む
次に、「特定のセルの値をマクロで書き換えるとき、その前後の値をログに残す」例を見てみます。
' ModSample_Update.bas
Option Explicit
Public Sub Update_MasterCell()
Dim ws As Worksheet
Set ws = Worksheets("Master")
Dim tgt As Range
Set tgt = ws.Range("B2")
Dim beforeVal As String
beforeVal = CStr(tgt.Value)
Dim newVal As String
newVal = "新しい値"
tgt.Value = newVal
Call WriteAuditLog("UPDATE_MASTER_CELL", ws.Name, tgt.Address(False, False), beforeVal, newVal, "OK", "Master!B2 を更新")
End Sub
VBここでの重要ポイントは、「書き換える前に必ず BeforeValue を取る」ことです。
マクロが値を上書きしてしまうと、元の値はもう戻ってきません。
監査ログに Before/After を残しておけば、「いつ、何から何に変わったか」を後から確認できます。
実務では、1セルだけでなく、範囲全体を更新することも多いので、範囲版のテンプレも用意しておくと便利です。
範囲更新用テンプレ:複数セルの変更をまとめてログに残す
範囲の値を配列で保持してから更新する
複数セルを一括で更新する場合、「全部の Before/After をログに残す」のは現実的ではありません。
そこで、「代表的な情報だけをログに残す」型を作ります。
Public Sub Update_Block_WithLog()
Dim ws As Worksheet
Set ws = Worksheets("Master")
Dim rng As Range
Set rng = ws.Range("B2:D10")
Dim beforeSummary As String
beforeSummary = "Rows=" & rng.Rows.Count & ", Cols=" & rng.Columns.Count
Dim v As Variant
v = rng.Value
Dim r As Long, c As Long
For r = 1 To UBound(v, 1)
For c = 1 To UBound(v, 2)
If IsNumeric(v(r, c)) Then
v(r, c) = v(r, c) * 1.1
End If
Next
Next
rng.Value = v
Dim afterSummary As String
afterSummary = "Rows=" & rng.Rows.Count & ", Cols=" & rng.Columns.Count & ", Updated=Numeric*1.1"
Call WriteAuditLog("UPDATE_BLOCK", ws.Name, rng.Address(False, False), beforeSummary, afterSummary, "OK", "数値を1.1倍に更新")
End Sub
VBここでは、BeforeValue と AfterValue に「範囲の概要」を入れています。
全セルの中身を文字列にして詰め込むこともできますが、ログが巨大になりすぎて現実的ではありません。
監査の目的が「どの範囲にどんな処理をかけたか」を知ることなら、
このようなサマリ情報でも十分役に立ちます。
応用:監査ログを「別ファイル」や「日付ごと」に分割する
ログが増えすぎる問題への対処
長く運用していると、AuditLog シートの行数がどんどん増えていきます。
数万行を超えると、フィルタや検索が重くなってきます。
そこで、少しレベルを上げて「ログを別ブックに切り出す」テンプレも考えられます。
考え方はシンプルです。
ログを書き込む先を「このブックの AuditLog シート」ではなく、「ログ専用ブック」にする。
ログ専用ブックは、日付ごとや月ごとにファイルを分ける。
WriteAuditLog の中で、「今日の日付からログファイル名を決めて開く→追記→閉じる」という流れにする。
イメージコードだけ示すと、次のようになります。
Public Sub WriteAuditLog_ToFile(ByVal actionName As String, _
ByVal targetSheet As String, _
ByVal targetRange As String, _
Optional ByVal beforeValue As String = "", _
Optional ByVal afterValue As String = "", _
Optional ByVal result As String = "OK", _
Optional ByVal message As String = "")
Dim logFolder As String
logFolder = ThisWorkbook.Path & "\AuditLogs"
If Dir(logFolder, vbDirectory) = "" Then
MkDir logFolder
End If
Dim logFile As String
logFile = logFolder & "\Audit_" & Format(Date, "yyyymmdd") & ".xlsx"
Dim wbLog As Workbook
If Dir(logFile) = "" Then
Set wbLog = Workbooks.Add
wbLog.Worksheets(1).Name = "AuditLog"
wbLog.Worksheets(1).Range("A1:J1").Value = Array("LogID", "LogTime", "UserName", "Action", "TargetSheet", "TargetRange", "BeforeValue", "AfterValue", "Result", "Message")
wbLog.SaveAs Filename:=logFile, FileFormat:=xlOpenXMLWorkbook
Else
Set wbLog = Workbooks.Open(logFile, ReadOnly:=False)
End If
Dim ws As Worksheet
Set ws = wbLog.Worksheets("AuditLog")
Dim nextRow As Long
nextRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
Dim logId As Long
If nextRow = 2 Then
logId = 1
Else
logId = ws.Cells(nextRow - 1, "A").Value + 1
End If
ws.Cells(nextRow, "A").Value = logId
ws.Cells(nextRow, "B").Value = Now
ws.Cells(nextRow, "C").Value = Environ$("Username")
ws.Cells(nextRow, "D").Value = actionName
ws.Cells(nextRow, "E").Value = targetSheet
ws.Cells(nextRow, "F").Value = targetRange
ws.Cells(nextRow, "G").Value = beforeValue
ws.Cells(nextRow, "H").Value = afterValue
ws.Cells(nextRow, "I").Value = result
ws.Cells(nextRow, "J").Value = message
ws.Columns("A:J").AutoFit
wbLog.Save
wbLog.Close False
End Sub
VBここまで来ると、監査ログは「日別ファイル」に蓄積されていきます。
本体ブックは軽いまま、ログはログで長期保管できる、という構成です。
落とし穴と対策:監査ログを「邪魔にしない」ための工夫
ログがうるさすぎると誰も見なくなる
何でもかんでもログに書きすぎると、ログがノイズだらけになり、誰も見なくなります。
監査ログの目的は、「あとから必要になったときに、最低限の手がかりが残っていること」です。
そのために意識したいのは次のような線引きです。
マクロ開始・終了、エラー発生など「処理単位」のログは必ず残す。
データ変更は、「重要なマスタ」「金額」「ステータス変更」などに絞る。
ループの1行ごとにログを書くのではなく、「何件処理したか」をまとめて1行に書く。
この“粒度の設計”が、監査ログツールを「生きたログ」にするか、「誰も見ないログ」にするかを分けます。
ユーザーに「監査されている感」を与えすぎない
監査ログは大事ですが、「全部見張られている」と感じさせると、現場の心理的ハードルが上がります。
運用としては、
何のためにログを取っているか(事故調査・トレーサビリティ・品質向上など)を共有する。
個人攻撃ではなく、プロセス改善の材料として使うことを明言する。
といったコミュニケーションもセットで考えると、ツールが受け入れられやすくなります。
まとめ:監査ログも「1行の型」を決めて、どのマクロにも差し込める部品にする
監査ログツールの本質は、「ログ1行の型」を決めて、それを書き込む共通 Sub を用意することです。
AuditLog シート(またはログファイル)に
LogTime、UserName、Action、TargetSheet、TargetRange、Before/After、Result、Message
を1行ずつ積み上げていく。
マクロの開始・終了、エラー発生、重要なデータ変更のタイミングで、WriteAuditLog を1行差し込むだけで、どのマクロにも監査機能を持たせられます。
