PowerShellでスクリプトを作っていると、「処理がうまくいっているか後から確認したい」「エラーが出た時に原因を追いたい」「どこで時間がかかっているのか知りたい」と思うことがありますよね。
そんなときに便利なのがログ出力です。スクリプトの動きをファイルに記録しておけば、後で見返してトラブルシュートにも役立ちますし、システム運用でも重要な証跡として活用できます。
しかし、「ログってどうやって出力するの?」「ファイルが上書きされてしまう」「エラーもログに残したい」といった疑問を持つ方も多いのではないでしょうか。
この記事では、Windows PowerShellでログを出力する基本の方法から、実際の運用で使える応用テクニックまでを、初心者向けにわかりやすく詳しく解説します。
ログ出力の基本概念
ログとは何か
ログとは、プログラムやシステムの動作状況を時系列で記録したファイルのことです。以下のような情報を記録するのが一般的です:
- 処理の開始・終了時刻
- 実行した処理の内容
- エラーや警告メッセージ
- 処理にかかった時間
- 処理したデータの件数
ログの重要性
トラブルシューティング
- エラーが発生した原因を特定できる
- いつ問題が起きたかを正確に把握
- 問題の再現に必要な情報を収集
運用管理
- システムの稼働状況を監視
- パフォーマンスの分析
- セキュリティ監査の証跡
開発・デバッグ
- スクリプトの動作確認
- 処理フローの検証
- パフォーマンス改善のためのデータ収集
PowerShellでログを出力する基本方法
Out-Fileコマンドレットを使う
PowerShellでは、画面に表示するメッセージをファイルにも簡単に書き出すことができます。最もよく使うのはOut-File
コマンドレットです。
基本的な使い方
"これはログです" | Out-File -FilePath "C:\temp\log.txt"
このコマンドを実行すると、C:\temp\log.txt
に以下の内容が書き込まれます:
これはログです
Write-Outputとの組み合わせ
Write-Output "処理が完了しました" | Out-File -FilePath "C:\temp\log.txt"
Write-Output
で標準出力したものをパイプでつなげる形でも使えます。
重要な注意点
デフォルトのOut-File
は、同じファイルに書き込むと上書きされてしまいます。つまり、前のログ内容は消えてしまうので注意が必要です。
変数を使ったログ出力
実際のスクリプトでは、処理の結果や変数の値をログに出力することが多いです:
$processName = "notepad"
$processCount = (Get-Process $processName).Count
"プロセス $processName の実行数: $processCount" | Out-File -FilePath "C:\temp\process_log.txt"
ログを追記(上書きせずに足す)する方法
-Appendパラメータを使う
ログを蓄積したい場合は、-Append
パラメータを付けるだけで追記ができます:
"次の処理を開始しました" | Out-File -FilePath "C:\temp\log.txt" -Append
これで既存のログファイルの最後にこのメッセージが追加されます。
複数のログを順次追記
$logFile = "C:\temp\daily_log.txt"
"処理1開始" | Out-File -FilePath $logFile -Append
"処理1完了" | Out-File -FilePath $logFile -Append
"処理2開始" | Out-File -FilePath $logFile -Append
"処理2完了" | Out-File -FilePath $logFile -Append
ファイルが存在しない場合の動作
-Append
パラメータを使用した場合、ファイルが存在しなければ新しく作成されます。存在すれば末尾に追加されるので、初回実行時も安心です。
Add-Contentコマンドレットの活用
Add-Contentの特徴
PowerShellには、ファイルに直接追記するAdd-Content
コマンドレットもあります。こちらは追記専用なので、ログをどんどん蓄積する用途に便利です。
Add-Content -Path "C:\temp\log.txt" -Value "処理Aが完了しました"
Out-File -AppendとAdd-Contentの違い
項目 | Out-File -Append | Add-Content |
---|---|---|
主な用途 | パイプライン処理の結果出力 | 直接的なテキスト追記 |
書き方 | "テキスト" | Out-File -Append | Add-Content -Value "テキスト" |
パフォーマンス | やや重い | 軽い |
使いやすさ | パイプラインで自然 | シンプルで直感的 |
複数行の追記
$logMessages = @(
"バッチ処理開始",
"データベース接続確立",
"データ処理実行中"
)
Add-Content -Path "C:\temp\batch_log.txt" -Value $logMessages
日時付きログの出力
Get-Dateを使った日時の追加
ログには通常、いつその処理が行われたかの情報が必要です:
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"[$timestamp] 処理を開始しました" | Out-File -FilePath "C:\temp\log.txt" -Append
出力例:
[2024-12-07 14:30:15] 処理を開始しました
ログレベル付きの出力
function Write-Log {
param(
[string]$Message,
[string]$Level = "INFO",
[string]$LogPath = "C:\temp\app.log"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
Add-Content -Path $LogPath -Value $logEntry
}
# 使用例
Write-Log -Message "アプリケーション開始" -Level "INFO"
Write-Log -Message "設定ファイル読み込み" -Level "DEBUG"
Write-Log -Message "接続エラーが発生" -Level "ERROR"
出力例:
[2024-12-07 14:30:15] [INFO] アプリケーション開始
[2024-12-07 14:30:16] [DEBUG] 設定ファイル読み込み
[2024-12-07 14:30:20] [ERROR] 接続エラーが発生
エラーログの出力
try-catchを使ったエラー処理
処理中に発生したエラーをログに残す方法も重要です:
$logFile = "C:\temp\error_log.txt"
try {
# 存在しないファイルにアクセスしてエラーを発生させる
Get-Item "C:\not_exist.txt" -ErrorAction Stop
"ファイル処理が成功しました" | Add-Content $logFile
} catch {
$errorMessage = "エラーが発生しました: $($_.Exception.Message)"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"[$timestamp] $errorMessage" | Add-Content $logFile
}
詳細なエラー情報の記録
try {
# 何らかの処理
1 / 0 # ゼロ除算エラーを意図的に発生
} catch {
$errorInfo = @{
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
ErrorMessage = $_.Exception.Message
ErrorType = $_.Exception.GetType().Name
ScriptLine = $_.InvocationInfo.ScriptLineNumber
Command = $_.InvocationInfo.MyCommand
}
$logEntry = "[$($errorInfo.Timestamp)] ERROR: $($errorInfo.ErrorMessage) | Type: $($errorInfo.ErrorType) | Line: $($errorInfo.ScriptLine)"
Add-Content -Path "C:\temp\detailed_error.log" -Value $logEntry
}
実践的なログ出力の例
基本的なスクリプトログ
$logFile = "C:\temp\script_log.txt"
# ログ関数の定義
function Write-ScriptLog {
param([string]$Message)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"[$timestamp] $Message" | Add-Content $logFile
}
Write-ScriptLog "スクリプト開始"
try {
Write-ScriptLog "処理1開始"
# 実際の処理(例:2秒待機)
Start-Sleep -Seconds 2
Write-ScriptLog "処理1完了"
Write-ScriptLog "処理2開始"
# 別の処理
$fileCount = (Get-ChildItem "C:\temp" -File).Count
Write-ScriptLog "処理2完了 - ファイル数: $fileCount"
} catch {
Write-ScriptLog "エラー発生: $($_.Exception.Message)"
} finally {
Write-ScriptLog "スクリプト終了"
}
パフォーマンス測定付きログ
$logFile = "C:\temp\performance_log.txt"
function Write-PerformanceLog {
param(
[string]$ProcessName,
[timespan]$Duration,
[string]$Status = "SUCCESS"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$durationMs = [math]::Round($Duration.TotalMilliseconds, 2)
$logEntry = "[$timestamp] $ProcessName - $Status - Duration: ${durationMs}ms"
Add-Content -Path $logFile -Value $logEntry
}
# 使用例
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
try {
# 何らかの処理
Start-Sleep -Seconds 1
$stopwatch.Stop()
Write-PerformanceLog -ProcessName "データ処理A" -Duration $stopwatch.Elapsed
} catch {
$stopwatch.Stop()
Write-PerformanceLog -ProcessName "データ処理A" -Duration $stopwatch.Elapsed -Status "FAILED"
}
CSVログの出力
構造化されたログデータ
複雑なログ情報を管理する場合は、CSV形式で出力することも有効です:
$csvLogFile = "C:\temp\structured_log.csv"
# CSVヘッダーの作成(初回のみ)
if (-not (Test-Path $csvLogFile)) {
"Timestamp,Level,Process,Message,Duration,Status" | Out-File $csvLogFile
}
function Write-CsvLog {
param(
[string]$Level,
[string]$Process,
[string]$Message,
[int]$Duration = 0,
[string]$Status = "OK"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$csvRow = "`"$timestamp`",`"$Level`",`"$Process`",`"$Message`",$Duration,`"$Status`""
Add-Content -Path $csvLogFile -Value $csvRow
}
# 使用例
Write-CsvLog -Level "INFO" -Process "FileBackup" -Message "バックアップ開始"
Write-CsvLog -Level "INFO" -Process "FileBackup" -Message "100ファイルをバックアップ" -Duration 5000 -Status "SUCCESS"
Write-CsvLog -Level "ERROR" -Process "FileBackup" -Message "アクセス権限エラー" -Status "FAILED"
PowerShellオブジェクトを使ったCSV出力
$logEntries = @()
function Add-StructuredLog {
param(
[string]$Level,
[string]$Component,
[string]$Message,
[string]$Status = "OK"
)
$logEntry = [PSCustomObject]@{
Timestamp = Get-Date
Level = $Level
Component = $Component
Message = $Message
Status = $Status
ProcessId = $PID
}
$script:logEntries += $logEntry
}
# ログエントリを追加
Add-StructuredLog -Level "INFO" -Component "Main" -Message "処理開始"
Add-StructuredLog -Level "WARN" -Component "Database" -Message "接続リトライ"
Add-StructuredLog -Level "INFO" -Component "Main" -Message "処理完了"
# CSVファイルに出力
$logEntries | Export-Csv -Path "C:\temp\object_log.csv" -NoTypeInformation -Encoding UTF8
Windows Event Logとの連携
Windowsイベントログへの書き込み
PowerShellからWindowsのイベントログにも書き込むことができます:
# カスタムイベントソースの作成(管理者権限が必要)
# New-EventLog -LogName "Application" -Source "MyPowerShellScript"
function Write-EventAndFileLog {
param(
[string]$Message,
[string]$Level = "Information"
)
# ファイルログ
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"[$timestamp] [$Level] $Message" | Add-Content "C:\temp\combined_log.txt"
# イベントログ(管理者権限が必要)
try {
$entryType = switch ($Level) {
"Error" { "Error" }
"Warning" { "Warning" }
default { "Information" }
}
Write-EventLog -LogName "Application" -Source "MyPowerShellScript" -EventId 1000 -EntryType $entryType -Message $Message
} catch {
# イベントログ書き込みに失敗した場合はファイルログのみ
"[$timestamp] [WARN] イベントログ書き込み失敗: $($_.Exception.Message)" | Add-Content "C:\temp\combined_log.txt"
}
}
ログローテーション
ファイルサイズによるローテーション
ログファイルが大きくなりすぎないよう、定期的にローテーションする仕組みも重要です:
function Write-RotatingLog {
param(
[string]$Message,
[string]$LogPath = "C:\temp\rotating.log",
[int]$MaxSizeMB = 10
)
# ファイルサイズをチェック
if (Test-Path $LogPath) {
$fileSize = (Get-Item $LogPath).Length / 1MB
if ($fileSize -gt $MaxSizeMB) {
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupPath = $LogPath -replace "\.log$", "_$timestamp.log"
Move-Item $LogPath $backupPath
}
}
# 新しいログエントリを追加
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"[$timestamp] $Message" | Add-Content $LogPath
}
日付によるローテーション
function Write-DailyLog {
param([string]$Message)
$today = Get-Date -Format "yyyyMMdd"
$logFile = "C:\temp\log_$today.txt"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"[$timestamp] $Message" | Add-Content $logFile
}
トラブルシューティング
よくある問題と解決法
ファイルアクセス権限エラー
症状: 「アクセスが拒否されました」エラーが発生 解決法:
try {
"テストログ" | Out-File "C:\temp\test.log" -Append
} catch {
# 権限エラーの場合は別の場所に出力
$userTempPath = $env:TEMP
"権限エラーのため一時フォルダに出力: $_" | Out-File "$userTempPath\fallback.log" -Append
}
文字エンコーディングの問題
症状: 日本語が文字化けする 解決法:
"日本語のログメッセージ" | Out-File "C:\temp\log.txt" -Encoding UTF8 -Append
ファイルロックの問題
症状: 「別のプロセスで使用されています」エラー 解決法:
function Write-SafeLog {
param([string]$Message, [string]$LogPath)
$retryCount = 3
$delay = 100
for ($i = 0; $i -lt $retryCount; $i++) {
try {
Add-Content -Path $LogPath -Value $Message
break
} catch {
if ($i -eq ($retryCount - 1)) {
throw $_
}
Start-Sleep -Milliseconds $delay
$delay *= 2
}
}
}
ベストプラクティス
ログ設計の考慮事項
ログレベルの統一
enum LogLevel {
DEBUG = 0
INFO = 1
WARN = 2
ERROR = 3
FATAL = 4
}
function Write-StandardLog {
param(
[string]$Message,
[LogLevel]$Level = [LogLevel]::INFO,
[string]$Component = "Main"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] [$Component] $Message"
Add-Content -Path "C:\temp\standard.log" -Value $logEntry
}
設定ファイルでのログ管理
$logConfig = @{
LogPath = "C:\temp\app.log"
MaxSizeMB = 50
RetentionDays = 30
LogLevel = "INFO"
}
function Initialize-Logging {
param($Config)
# 古いログファイルのクリーンアップ
$cutoffDate = (Get-Date).AddDays(-$Config.RetentionDays)
Get-ChildItem "C:\temp\*.log" | Where-Object { $_.LastWriteTime -lt $cutoffDate } | Remove-Item
}
まとめ
PowerShellでログを出力するための主要な方法をまとめると:
基本的なログ出力方法
Out-File
:標準的なファイル出力(-Append
で追記)Add-Content
:追記専用で軽量・高速- 日時付きログ:
Get-Date
でタイムスタンプを追加
エラー処理とログ
- **
try-catch
**でエラーを捕まえてログに記録 - 詳細なエラー情報の保存で原因究明を支援
実用的な機能
- 構造化ログ:CSV形式やPowerShellオブジェクト
- ログローテーション:ファイルサイズや日付での管理
- パフォーマンス測定:処理時間の記録
運用での活用
- ログレベルの統一
- 設定ファイルによる管理
- Windowsイベントログとの連携
コメント