はじめに
サーバーログやアプリケーションログを解析する時、こんな悩みありませんか?
2024-02-08 10:00:00 INFO: Server started
2024-02-08 10:01:00 ERROR: Connection failed
2024-02-08 10:02:00 WARN: Timeout after 30s
2024-02-08 10:03:00 ERROR: Retry failed
2024-02-08 10:04:00 INFO: Request processed
... (数千行続く)「ERRORだけ抽出したい」
「複数のパターンを一度に抽出したい」
「マッチした行番号も記録したい」
VSCodeの標準機能では、検索結果をコピペして整形…という非効率な作業になりがち。
今回、複数の正規表現パターンで一括抽出し、行番号付きで別ファイルに出力するTypeScriptマクロを実装しました。
ログ解析が劇的に効率化する、実務で必須のツールです。
なぜこのツールが必要なのか?
VSCode標準機能の限界
標準の検索機能(Ctrl+F)では:
- 1つのパターンしか検索できない
- マッチした行をファイルに保存できない
- 行番号を一緒に記録できない
- 複数パターンを比較できない
例:エラーと警告を同時に抽出したい場合
標準機能での手順:
ERRORで検索- マッチした行を手動でコピー
- 新規ファイルに貼り付け
WARNで検索- マッチした行を手動でコピー
- 同じファイルに追記
- 行番号は手動で追加…
面倒すぎる!
このマクロで実現できること
| 項目 | 標準機能 | このマクロ |
|---|---|---|
| 複数パターン抽出 | ❌ 不可 | ✅ 一括抽出 |
| 行番号記録 | ❌ 手動 | ✅ 自動 |
| ファイル出力 | ❌ 手動 | ✅ 自動 |
| 実行履歴 | ❌ なし | ✅ 日時記録 |
| 操作数 | 7手順以上 | 1キー操作 |
実装した機能
主な機能
ショートカット: Ctrl+Shift+4
✅ 複数パターン一括抽出
- 選択範囲から複数の正規表現を読み取り
- またはカンマ区切りで入力
✅ 行番号付き出力
- マッチした行の行番号を記録
- 元ファイルへのジャンプが簡単(Ctrl+G)
✅ 実行情報の記録
- 実行日時(年月日時分秒)
- 対象ファイル名
- 各パターンのマッチ件数
✅ 別ファイル出力
- 元ファイルを汚さない
- ファイル名:
元ファイル名_extracted_20240208_123456.txt
対応する入力方法
方法1: 選択範囲から読み取る(推奨)
# パターンリスト
ERROR
WARN.*timeout
Failed to .*
Exception.*上記を選択してCtrl+Shift+4実行 → 4パターンで一括抽出
方法2: カンマ区切りで入力
選択せずにCtrl+Shift+4実行 → ダイアログ表示
ERROR, WARN.*timeout, Failed to .*出力例
============================================================
正規表現マッチ抽出結果
============================================================
実行日時: 2024-02-08 15:30:45
対象ファイル: server.log
抽出パターン数: 3
------------------------------------------------------------
パターン1: ERROR
マッチ件数: 2 件
------------------------------------------------------------
[Line 145] 2024-02-08 10:01:00 ERROR: Connection failed
[Line 389] 2024-02-08 10:03:00 ERROR: Retry failed
------------------------------------------------------------
パターン2: WARN.*timeout
マッチ件数: 1 件
------------------------------------------------------------
[Line 267] 2024-02-08 10:02:00 WARN: Timeout after 30s
------------------------------------------------------------
パターン3: Failed to .*
マッチ件数: 1 件
------------------------------------------------------------
[Line 389] 2024-02-08 10:03:00 ERROR: Retry failed
============================================================
抽出完了
============================================================
マッチ件数: パターン1=2件, パターン2=1件, パターン3=1件
合計: 4 件実務での活用例
例1:エラーログの解析
シーン:本番障害の調査
# パターンリスト
ERROR
FATAL
Exception結果:
- エラー系のログだけを抽出
- 行番号から前後の文脈を確認
- 障害の原因を特定
例2:特定ユーザーの行動追跡
シーン:不正アクセスの調査
# パターンリスト
user_id=12345
IP: 192.168.1.100
Failed login.*12345結果:
- 特定ユーザーの全アクセスログを抽出
- 不正なアクセスパターンを発見
例3:パフォーマンス問題の調査
シーン:レスポンスタイムの劣化
# パターンリスト
Slow query.*
timeout.*exceeded
Response time: [5-9]\d{3}ms結果:
- 遅いクエリやタイムアウトを抽出
- ボトルネックを特定
例4:複数サーバーのログ比較
シーン:サーバーA・B・Cでエラー率比較
# 各サーバーのログで実行
ERROR
WARN結果:
- サーバーA: ERROR=50件, WARN=30件
- サーバーB: ERROR=120件, WARN=80件 ← 異常
- サーバーC: ERROR=45件, WARN=35件
例5:定期メンテナンスの記録
シーン:夜間バッチの成否確認
# パターンリスト
Batch.*started
Batch.*completed
Batch.*failed結果:
- バッチ処理の開始・完了・失敗を記録
- 実行時刻も自動記録
TypeScriptコード解説
メイン処理(extractByRegex.ts)
import * as vscode from 'vscode';
import * as path from 'path';
/**
* 正規表現マッチ抽出ツール
*/
export async function extractByRegex() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('アクティブなエディタがありません');
return;
}
const document = editor.document;
const selection = editor.selection;
const fileName = path.basename(document.fileName);
// 選択範囲からパターンを取得
let patterns: string[];
if (!selection.isEmpty) {
// 選択範囲がある場合
patterns = getSelectedPatterns(document, selection);
if (patterns.length === 0) {
vscode.window.showWarningMessage('有効なパターンが選択されていません');
return;
}
} else {
// ダイアログで入力
const input = await vscode.window.showInputBox({
prompt: '抽出する正規表現パターンを入力(複数の場合はカンマ区切り)',
placeHolder: 'ERROR, WARN.*timeout, Failed to .*',
value: 'ERROR'
});
if (!input) {
return;
}
patterns = input.split(',')
.map(p => p.trim())
.filter(p => p.length > 0);
}
// 抽出実行
const results = await extractMatches(document, patterns);
if (results.totalCount === 0) {
vscode.window.showInformationMessage('マッチする行が見つかりませんでした');
return;
}
// 結果を新規ファイルに出力
await createResultFile(fileName, patterns, results);
vscode.window.showInformationMessage(
`抽出完了: 合計 ${results.totalCount} 件のマッチ`
);
}選択範囲からパターンを取得
/**
* 選択範囲から正規表現パターンを取得
*/
function getSelectedPatterns(
document: vscode.TextDocument,
selection: vscode.Selection
): string[] {
const patterns: string[] = [];
const startLine = selection.start.line;
const endLine = selection.end.line;
for (let i = startLine; i <= endLine; i++) {
const lineText = document.lineAt(i).text.trim();
// 空行やコメント行はスキップ
if (!lineText || lineText.startsWith('#') || lineText.startsWith('//')) {
continue;
}
// 正規表現として有効かチェック
try {
new RegExp(lineText);
patterns.push(lineText);
} catch (e) {
vscode.window.showWarningMessage(`不正な正規表現をスキップ: ${lineText}`);
}
}
return patterns;
}マッチする行を抽出
/**
* マッチした行の情報
*/
interface MatchedLine {
lineNumber: number; // 1-based行番号
text: string; // 行のテキスト
}
/**
* ドキュメントから複数パターンでマッチする行を抽出
*/
async function extractMatches(
document: vscode.TextDocument,
patterns: string[]
): Promise {
const matches: MatchResult[] = [];
let totalCount = 0;
// パターンごとに処理
for (const pattern of patterns) {
const regex = new RegExp(pattern);
const matchedLines: MatchedLine[] = [];
// 全行をチェック
for (let i = 0; i < document.lineCount; i++) {
const line = document.lineAt(i);
if (regex.test(line.text)) {
matchedLines.push({
lineNumber: i + 1, // 1-based
text: line.text
});
}
}
matches.push({
pattern,
lines: matchedLines
});
totalCount += matchedLines.length;
}
// 現在時刻
const now = new Date();
const timestamp = formatDateTime(now);
return {
timestamp,
sourceFile: path.basename(document.fileName),
matches,
totalCount
};
} 出力内容の生成
/**
* 出力内容を生成
*/
function generateOutput(
sourceFileName: string,
patterns: string[],
results: ExtractResults
): string {
const lines: string[] = [];
// ヘッダー
lines.push('=' .repeat(60));
lines.push('正規表現マッチ抽出結果');
lines.push('=' .repeat(60));
lines.push(`実行日時: ${results.timestamp}`);
lines.push(`対象ファイル: ${sourceFileName}`);
lines.push(`抽出パターン数: ${patterns.length}`);
lines.push('');
// パターンごとの結果
for (let i = 0; i < results.matches.length; i++) {
const match = results.matches[i];
lines.push('-'.repeat(60));
lines.push(`パターン${i + 1}: ${match.pattern}`);
lines.push(`マッチ件数: ${match.lines.length} 件`);
lines.push('-'.repeat(60));
if (match.lines.length > 0) {
// 行番号の桁数を計算(揃えるため)
const maxLineNumber = Math.max(...match.lines.map(l => l.lineNumber));
const lineNumberWidth = String(maxLineNumber).length;
match.lines.forEach(matchedLine => {
const lineNumberStr = String(matchedLine.lineNumber).padStart(lineNumberWidth, ' ');
lines.push(`[Line ${lineNumberStr}] ${matchedLine.text}`);
});
} else {
lines.push('(マッチなし)');
}
lines.push('');
}
// サマリー
lines.push('=' .repeat(60));
lines.push('抽出完了');
lines.push('=' .repeat(60));
const summary = results.matches
.map((m, i) => `パターン${i + 1}=${m.lines.length}件`)
.join(', ');
lines.push(`マッチ件数: ${summary}`);
lines.push(`合計: ${results.totalCount} 件`);
lines.push('');
return lines.join('\n');
}ファイル出力
/**
* 結果を新規ファイルに出力
*/
async function createResultFile(
sourceFileName: string,
patterns: string[],
results: ExtractResults
) {
// 出力内容を生成
const output = generateOutput(sourceFileName, patterns, results);
// 新規ドキュメントを作成
const doc = await vscode.workspace.openTextDocument({
content: output,
language: 'log'
});
// エディタで開く(隣に表示)
await vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.Beside,
preview: false
});
// ファイル名を提案
const suggestedName = generateFileName(sourceFileName, new Date());
// 保存ダイアログを表示
const uri = await vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file(suggestedName),
filters: {
'Text files': ['txt', 'log'],
'All files': ['*']
}
});
if (uri) {
await vscode.workspace.fs.writeFile(
uri,
Buffer.from(output, 'utf8')
);
vscode.window.showInformationMessage(`結果を保存しました: ${path.basename(uri.fsPath)}`);
}
}
/**
* 出力ファイル名を生成
*/
function generateFileName(sourceFileName: string, date: Date): string {
const nameWithoutExt = path.parse(sourceFileName).name;
const timestamp = formatDateTimeForFile(date);
return `${nameWithoutExt}_extracted_${timestamp}.txt`;
}使い方
1. 拡張機能のインストール
# GitHubからクローン
git clone https://github.com/xxxxx-sys/my-macros.git
cd my-macros
# 依存関係インストール
npm install --legacy-peer-deps
# パッケージ化
npm run compile
npx vsce package
# インストール
code --install-extension my-macros-0.0.5.vsix2. 基本的な使い方
方法1: 選択範囲から抽出(推奨)
patterns.txt
---
# エラー系
ERROR
FATAL
Exception.*
# 警告系
WARN
timeout- パターンリストを作成
- 抽出したいパターンを選択
- 対象ログファイルに切り替え
Ctrl+Shift+4で実行
方法2: カンマ区切りで入力
- 対象ログファイルを開く
Ctrl+Shift+4で実行- ダイアログに入力:
ERROR, WARN, timeout - Enter
3. 実践例
シーン1: エラーログの集計
server.log(10,000行)
---
2024-02-08 10:00:00 INFO: Started
2024-02-08 10:01:00 ERROR: Connection failed
...パターン:
ERROR
FATAL実行: Ctrl+Shift+4
結果:```server_extracted_20240208_150000.txt
パターン1: ERROR → 50件
パターン2: FATAL → 5件
合計: 55件
**シーン2: 特定期間のログ抽出**
**パターン:**2024-02-08 1[0-2]:.*ERROR
**結果:** 10時~12時台のERRORのみ抽出
**シーン3: 複数条件の組み合わせ**
**パターン:**user_id=12345.*ERROR
user_id=12345.*WARN
user_id=12345.*login
**結果:** 特定ユーザーの全アクティビティを抽出
### 4. 行番号からのジャンプ
抽出結果:[Line 145] 2024-02-08 10:01:00 ERROR: Connection failed
**元ファイルへジャンプ:**
1. 元ファイル(server.log)を開く
2. `Ctrl+G`
3. `145`を入力してEnter
4. → 該当行に即ジャンプ!
## package.jsonの設定
```json
{
"contributes": {
"commands": [
{
"command": "myMacros.extractByRegex",
"title": "Extract by Regex Pattern",
"category": "My Macros"
}
],
"keybindings": [
{
"command": "myMacros.extractByRegex",
"key": "ctrl+shift+4",
"when": "editorTextFocus"
}
]
}
}メリット・デメリット
メリット
✅ 圧倒的な効率化
- 複数パターンを一度に抽出
- 手作業なら10分以上かかる作業が数秒
✅ 行番号で前後確認が簡単
- エラーの発生箇所を即座に特定
- 前後の文脈を確認してデバッグ
✅ 実行履歴の保存
- いつ何を抽出したか記録
- 調査の再現性が高い
✅ 正規表現で柔軟な抽出
- 複雑な条件も指定可能
- パターンを保存して再利用
✅ 元ファイルを汚さない
- 別ファイルに出力
- 元のログは変更なし
デメリット
❌ 巨大ファイルは処理に時間がかかる
- 数万行を超えるログは数秒待つ
- ただし手作業よりは圧倒的に速い
❌ 正規表現の知識が必要
- 基本的なパターンは簡単
- 複雑な条件は学習コストあり
❌ リアルタイム抽出は非対応
- tail -f のようなリアルタイム監視は不可
- ファイル全体の一括抽出のみ
トラブルシューティング
Q: パターンが認識されない
A: 正規表現の構文をチェック
NG: [未完成の正規表現
OK: \[完成した正規表現\]
NG: (グループ化ミス
OK: (正しいグループ化)不正な正規表現は自動的にスキップされます。
Q: マッチする行が見つからない
A: パターンを確認
# 大文字小文字を区別
ERROR → "error"はマッチしない
# 解決策:大文字小文字を無視
[Ee][Rr][Rr][Oo][Rr]
または
(?i)errorQ: 行番号がずれる
A: 元ファイルを編集していませんか?
抽出後に元ファイルを編集すると、行番号がずれます。
抽出は編集前に実行してください。
Q: ファイルが保存できない
A: パスに権限があるか確認
# 書き込み権限のあるフォルダに保存
NG: C:\Program Files\...(権限不足)
OK: C:\Users\[ユーザー名]\Documents\...まとめ
VSCodeで複数の正規表現パターンで一括抽出し、行番号付きで別ファイルに出力する機能を実装しました。
この機能が役立つ人:
- サーバーログを頻繁に解析する
- エラー調査をよくする
- 複数パターンを同時に抽出したい
- 調査の履歴を残したい
- テキスト処理の効率化を図りたい
特にログ解析では、標準機能で10分以上かかる作業を数秒で完了できるため、実務での生産性向上に大きく貢献します。
サーバー障害の調査、パフォーマンス問題の特定、不正アクセスの追跡など、様々なシーンで活用できる革新的なツールです。
ぜひ試してみてください!
関連記事
サクラエディタからの移行経緯

TypeScriptマクロ開発環境の構築方法

選択行による一括置換マクロ

タグ: #VSCode #TypeScript #マクロ #正規表現 #ログ解析 #生産性向上 #エラー調査
コメント