【複数文字コード混在ログ解読】ADODB.Streamを用いたShift-JIS/UTF-8自動判定とUnicode統一パース
開発現場でレガシーシステムとモダンシステムが混在しているとき、最もエンジニアの精神を削る作業の一つが「文字コードのバラバラなログの統合・解析」だ。
あるシステムのログはShift-JIS(CP932)、別のクラウド基幹のログはBOMなしUTF-8。これをそのままVBScriptの標準 `FileSystemObject (FSO)` で読み込もうものなら、日本語は無残に文字化けし、エラーすら吐かずにサイレントでデータを破壊する。
「とりあえずメモ帳で開いてUTF-8に保存し直せば……」なんて手作業を考えていないか?
我々はエンジニアだ。数千ファイルのログを前にしてマウスを握るなど、自動化の美学に反する。
今回は、WSH(Windows Script Host)環境のVBScriptにおいて、`ADODB.Stream` オブジェクトを極限までチューニングし、文字コードを自動判定してUnicode(BSTR)へ完璧に調停・パースするプロダクションコードを授与しよう。
—
1. なぜ FSO(FileSystemObject)ではダメなのか?
VBScriptにおけるファイルの読み込みといえば `Scripting.FileSystemObject` の `OpenTextFile` が真っ先に思い浮かぶだろう。しかし、これには致命的な設計上の欠陥がある。
1. 文字コードの固定化: `OpenTextFile` の第3引数(Tristate)で指定できるのは `TristateTrue`(UTF-16)か `TristateFalse`(ASCII/システム既定)のみ。Shift-JISやBOMなしUTF-8を動的に判別する能力はゼロ。
2. 例外処理の弱さ: 不正なバイトシーケンスに出くわした際、処理が中断するか、あるいは文字化けしたゴミデータを黙々と生成する。
対して、データベース接続でおなじみの `ADODB.Stream` は、内部バッファにバイナリデータをストリーミングし、任意の文字コード(Charset)でデコードする機能を持つ。これを使いこなせば、VBScriptであってもモダンなテキスト処理が可能になるのだ。
—
2. 堅牢な設計アプローチ:自動判定とフォールバックのメカニズム
文字コードの自動判定において、最も確実なのは 「BOM(Byte Order Mark)」の有無を確認すること だが、世のログの大半を占める「Shift-JIS」や「BOMなしUTF-8」にはBOMが存在しない。
そこで、以下の戦略をとる。
1. バイナリモードでの先頭バイト読み込み:
ファイルを一度バイナリ(`adTypeBinary`)として開く。
2. UTF-8判定トライアル:
先頭数バイトをUTF-8(`utf-8`)のCharsetを指定してテキスト変換を試みる。この時、UTF-8として不正なマルチバイトシーケンスが含まれている場合、ADODB.Streamは例外(または不正文字)を発生させる。これを利用して判定する。
3. フォールバック(Shift-JIS / CP932):
UTF-8としてのデコードに失敗、あるいは文字化けの兆候が見られる場合は、自動的に `shift_jis`(実務では拡張文字に強い `ks_c_5601-1987` よりも堅牢な `ks_c_5601-1987` ではなく `CP932` を指定)にフォールバックする。
—
3. 【プロダクションコード】全自動文字コード調停・ログ集計スクリプト
以下のコードは、指定したディレクトリ内のすべての `.log` ファイルを走査し、文字コードを自動判別した上で、すべてのテキストをUTF-16(Unicode)の単一ストリームに統合してコンソール出力(またはファイル出力)する実用スクリプトだ。
実務の現場でそのままコピー&ペーストし、即座に稼働させることができる。
‘ ==============================================================================
‘ Script Name : LogParser_AutoDetect.vbs
‘ Description : 複数文字コード(SJIS/UTF-8)混在ログをADODB.Streamで自動判別・統合パース
‘ Copyright : Enterprise Automation Architecture
‘ ==============================================================================
Option Explicit
Const adTypeBinary = 1
Const adTypeText = 2
Const adSaveCreateOverWrite = 2
‘ 処理対象のディレクトリと出力先
Dim targetDir, outputFilePath
targetDir = “C:\Logs\TargetLogs”
outputFilePath = “C:\Logs\Unified_Log_Output.log”
Call Main()
Sub Main()
Dim fso, folder, file, streamOut
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(targetDir) Then
WScript.Echo “[ERROR] ターゲットディレクトリが存在しません: ” & targetDir
Exit Sub
End If
‘ 統合出力用ストリームの初期化 (Unicode形式で書き出す)
Set streamOut = CreateObject(“ADODB.Stream”)
streamOut.Type = adTypeText
streamOut.Charset = “unicode” ‘ 出力はUTF-16LEに統一
streamOut.Open
Dim fileCount
fileCount = 0
Set folder = fso.GetFolder(targetDir)
For Each file in folder.Files
If LCase(fso.GetExtensionName(file.Name)) = “log” Then
WScript.Echo “[INFO] 解析中: ” & file.Name
‘ 各ファイルのテキストデータを取得して出力ストリームに書き込む
Dim fileContent
fileContent = ReadLogFileWithAutoDetect(file.Path)
If fileContent <> “” Then
streamOut.WriteText “=== FILE START: ” & file.Name & ” ===” & vbCrLf
streamOut.WriteText fileContent
streamOut.WriteText vbCrLf & “=== FILE END: ” & file.Name & ” ===” & vbCrLf & vbCrLf
fileCount = fileCount + 1
End If
End If
Next
‘ 統合結果をファイルに保存
streamOut.SaveToFile outputFilePath, adSaveCreateOverWrite
streamOut.Close
Set streamOut = Nothing
Set folder = Nothing
Set fso = Nothing
WScript.Echo “[SUCCESS] 処理完了。統合ファイル数: ” & fileCount & ” -> 出力先: ” & outputFilePath
End Sub
‘ ——————————————————————————
‘ 文字コード自動判定・読み込みコア関数
‘ ——————————————————————————
Function ReadLogFileWithAutoDetect(filePath)
Dim streamIn
Set streamIn = CreateObject(“ADODB.Stream”)
‘ 1. バイナリモードでオープンしてBOMやバイト列を確認
streamIn.Type = adTypeBinary
streamIn.Open
streamIn.LoadFromFile filePath
Dim binData
binData = streamIn.Read
streamIn.Close
‘ 2. UTF-8 (BOM付き) の検出
‘ EF BB BF から始まる場合は確実にUTF-8
Dim detectedCharset
detectedCharset = “CP932” ‘ デフォルトはShift-JIS系(CP932)
If LenB(binData) >= 3 Then
If AscB(MidB(binData, 1, 1)) = &HEF And _
AscB(MidB(binData, 2, 1)) = &HBB And _
AscB(MidB(binData, 3, 1)) = &HBF Then
detectedCharset = “UTF-8”
End If
End If
‘ 3. BOMがない場合、UTF-8としてのデコードテストを行う
If detectedCharset = “CP932” Then
If IsValidUTF8(binData) Then
detectedCharset = “UTF-8”
End If
End If
‘ 4. 決定した文字コードでテキストとして再読み込み
streamIn.Type = adTypeText
streamIn.Charset = detectedCharset
streamIn.Open
streamIn.Write binData
streamIn.Position = 0 ‘ ストリームの先頭に戻す
Dim textData
textData = streamIn.ReadText
streamIn.Close
Set streamIn = Nothing
ReadLogFileWithAutoDetect = textData
End Function
‘ ——————————————————————————
‘ 簡易UTF-8バリデーション(不正なマルチバイトシーケンスを検知)
‘ ——————————————————————————
Function IsValidUTF8(binData)
Dim streamTest
Set streamTest = CreateObject(“ADODB.Stream”)
streamTest.Type = adTypeText
streamTest.Charset = “UTF-8”
streamTest.Open
streamTest.Write binData
streamTest.Position = 0
On Error Resume Next
Dim dummy
dummy = streamTest.ReadText ‘ UTF-8として読めない不正バイトがあるとここでエラーや化けが発生する
If Err.Number <> 0 Then
IsValidUTF8 = False
Else
IsValidUTF8 = True
End If
On Error GoTo 0
streamTest.Close
Set streamTest = Nothing
End Function
—
4. プロジェクトアーキテクチャ上の重要注意点(実務の罠)
このコードを実際のエンタープライズ環境に投入する際、プロのアーキテクトとして押さえておくべき「罠」がいくつか存在する。
① `ADODB.Stream` のメモリリークとオブジェクト解放
VBScriptのガベージコレクションは頼りにならない。特にループ内で `CreateObject(“ADODB.Stream”)` を何度も呼び出す場合、明示的に `Set stream = Nothing` を行わないと、巨大なログファイルを処理する過程でメモリ消費量が右肩上がりになり、最悪の場合OS側でメモリ不足エラーを引き起こす。
上記のコードでは、関数スコープごとに確実にオブジェクトを破棄する設計にしている。
② CP932 と Shift-JIS の違い
Windows環境における「Shift-JIS」を指定する場合、単なる `shift-jis` ではなく `cp932`(Microsoftコードページ932) を指定するのが鉄則だ。
純粋なJIS規格のShift-JISを指定すると、NEC特殊文字やIBM拡張漢字(例:『髙』や『﨑』、丸つき数字など)が含まれていた瞬間に文字化け、あるいは処理がクラッシュする。実務では `CP932` をデフォルトのフォールバックに据えるべきである。
③ 巨大ファイル(数GB超)に対するアプローチ
今回紹介したスクリプトは数十MB程度のログファイル群を想定している。もし1ファイルが数GBに達するモンスターログである場合、`streamIn.Read` でメモリ上に一気にバイナリを展開する方式はメモリを圧迫する。
その場合は `Line` 単位でのストリーミング読み込み(`ReadText -1` などの活用)にアルゴリズムを切り替える必要があるが、文字コード自動判定のロジックが複雑化するため、ログファイル側でローテーション(日別・サイズ別分割)をかけるインフラ側の設計と組み合わせるのが正しいアプローチだ。
—
総括
文字コードという、一見すると泥臭く地味なトラブルは、システム間連携の現場においてプロジェクトの進行を最も停滞させる要因の一つだ。
今回紹介した `ADODB.Stream` を駆使した自動判定・統一パースの知見をあなたの引き出しに加えることで、どんなにカオスなレガシー環境のログであっても、一撃でクリーンなデータに昇華させることができる。
「動けばいい」のコードを卒業し、環境に左右されない堅牢な自動化基盤を構築せよ。
