关于本文
本文是通过利用生成式 AI 的自动化工作流创建的。我们查阅了 JSON Lines 的格式要求和 Microsoft Learn 的 PowerShell 命令规范,并将其整理成了可用于学习追加虚拟日志、读取日志以及处理异常行的代码。
验证状态:📘 已确认官方规范・PowerShell 实机未验证
目标环境为 PowerShell 7 及更高版本。所展示的执行结果是从代码中预期的输出示例,并非实机运行记录。
在 PowerShell 中保存处理历史记录时,如果每次都重写整个 JSON 文件,事件越多管理就会越麻烦。
在这种情况下,最方便的就是JSON Lines(JSONL,行分隔 JSON)。普通的 JSON 是“整个文件作为一个 JSON 值”,而 JSONL 则是每行放置一个独立的 JSON 值。
例如,格式如下。
{"event_id":"evt-001","status":"START","message":"開始"}
{"event_id":"evt-002","status":"DONE","message":"完了"}
这两行可以逐行提取并分别解析为 JSON。在这里,我们将使用 PowerShell 追加两条记录,最后故意添加一条无效行,以便能够确认能够正常读取到哪个位置。
JSON 与 JSONL 有何不同?
| 视角 | 普通JSON数组 | JSONL |
|---|---|---|
| 保存示例 | [{...},{...}] | 每行{...}一条 |
| 追加 | 注意数组末尾、逗号和括号 | 在末尾添加新的JSON值和换行符 |
| 读取 | 通常处理整个文件 | 易于逐行按顺序处理 |
| 中间损坏的行 | 可能导致整体解析失败 | 可按行隔离错误 |
| 适用用途 | 统一配置、API响应 | 事件记录、批处理结果、日志传输 |
JSONL有三个重要的格式条件。
字符编码为UTF-8且无BOM。BOM是附加在开头的特殊识别字节。
每行本身就是一个有效的 JSON 值。空行不是有效的 JSON。
使用换行符分隔记录。默认使用 LF,同时也支持 CRLF。
这在JSON Lines 格式说明中有所记载。扩展名通常使用 .jsonl。
首先尝试:追加 2 个虚拟事件
无需管理员权限或外部服务。请打开 PowerShell 7 或更高版本,并在新控制台中运行以下代码。由于只使用一个新的临时目录,因此不会修改现有的日志文件。
$ErrorActionPreference = 'Stop'
# 毎回異なる一時フォルダーを作り、既存ファイルを触らない
$work = Join-Path ([IO.Path]::GetTempPath()) (
'jsonl-try-' + [guid]::NewGuid().ToString('N')
)
New-Item -ItemType Directory -Path $work | Out-Null
$file = Join-Path $work 'events.jsonl'
try {
$events = @(
[ordered]@{ event_id='evt-001'; status='START'; message='開始' }
[ordered]@{ event_id='evt-002'; status='DONE'; message='完了' }
)
foreach ($event in $events) {
# -Compressで「1イベント=1行」にする
$json = ConvertTo-Json -InputObject $event -Compress -Depth 5
Add-Content -LiteralPath $file -Value $json -Encoding utf8NoBOM
}
# 壊れた3行目を、教材用にだけ追加
Add-Content -LiteralPath $file -Value '{broken json' -Encoding utf8NoBOM
$valid = 0
$invalid = 0
$lineNo = 0
foreach ($line in Get-Content -LiteralPath $file -Encoding utf8) {
$lineNo++
try {
$record = ConvertFrom-Json -InputObject $line -ErrorAction Stop
if ($null -eq $record -or
[string]::IsNullOrWhiteSpace([string]$record.event_id)) {
throw 'event_id is missing'
}
$valid++
Write-Output ("[OK] {0} {1}" -f $record.event_id, $record.status)
}
catch {
$invalid++
Write-Warning ("line {0}: invalid JSON/record" -f $lineNo)
}
}
Write-Output ("[RESULT] valid={0} invalid={1}" -f $valid, $invalid)
}
finally {
# この実行で作成したフォルダーだけを削除する
if (Test-Path -LiteralPath $work) {
Remove-Item -LiteralPath $work -Recurse -Force
}
}
预期的显示示例
[OK] evt-001 START [OK] evt-002 DONE WARNING: line 3: invalid JSON/record [RESULT] valid=2 invalid=1
警告的显示颜色和首字符因主机而异。希望您注意到的是,即使第 3 行损坏,第 1 到 2 行的事件仍然可以被读取这一点。
这段代码并不是“修复损坏的行”,而是一个区分正常行与异常行的示例。在生产系统中,除了不静默丢弃非法行之外,还需要做出将其记录到其他错误日志或隔离文件中的决策。
代码拆解后分别做了什么?
ConvertTo-Json -Compress:压缩为单行
将 PowerShell 哈希表转换为 JSON 字符串。
$data = [ordered]@{
event_id = 'evt-003'
status = 'WARN'
message = "1行目" + [Environment]::NewLine + "2行目"
}
$data | ConvertTo-Json -Compress -Depth 5
-Compress可以省去外观上的换行和多余空白。由于值中的换行会在 JSON 字符串内被转义,因此即使 message 中包含换行,也很容易将一条记录保持在单行中这是它的优点。
-Depth用于指定嵌套对象要转换的层数。当处理深层结构时,请不要盲目增大数字,而是要确认转换后的 JSON 中是否保留了所需的属性。Microsoft Learn 上的 ConvertTo-Json是规范的依据。
Add-Content:追加而非覆盖
Add-Content -LiteralPath $file -Value $json -Encoding utf8NoBOM
Set-Content用于替换现有内容,而Add-Content用于在末尾追加内容。-LiteralPath指定此参数后,文件名中的符号将不会被解释为通配符。
在 PowerShell 7 中明确指定 utf8NoBOM 的原因是为了使字符编码与 JSONL 的格式要求保持一致。由于 Windows PowerShell 5.1 对字符编码的处理方式不同,因此不在本示例的适用范围内。有关字符编码差异的详细信息,请参阅Microsoft Learn 的字符编码说明。
Get-Content 与 ConvertFrom-Json:逐行解析
foreach ($line in Get-Content -LiteralPath $file -Encoding utf8) {
$record = ConvertFrom-Json -InputObject $line -ErrorAction Stop
$record.event_id
}
对于正常的文件,这样就可以提取出各个事件 ID。但是,上面的简化示例如果遇到无效行会中途停止因此在实际工作中,请像最初的示例那样,以行为单位使用 try/catch。使用的工具是:
PowerShell 的 Get-Content 和 ConvertFrom-Json 的规范可以结合起来进行确认。
修改一处并观察结果的差异
将最初代码中用于添加非法一行的下面这行代码注释掉。
# Add-Content -LiteralPath $file -Value '{broken json' -Encoding utf8NoBOM
接下来再次运行时的预期结果是:[RESULT] valid=2 invalid=0。
通过对比得出的结论并非 JSONL 本身能自动修复损坏的数据,而是读取端实现能将故障范围限定在单行内。
注意空行与架构违规
在 JSONL 中,空行也是非法的记录。不过,ConvertFrom-Json 在传入空字符串时可能不会输出内容。在进行严格的 JSONL 验证时,请在解析前显式过滤掉空白行。
此外,即使作为 JSON 完全正确,如果事件缺少必需的 event_id,在业务上也是不完整的。虽然本次代码最低限度地确认了 event_id 的存在,但并未检查 status 的允许值、时间以及事件 ID 是否重复。
如果在工作中应用:保存批处理执行历史
例如,在自动化处理 CSV 汇总时,可以逐条记录以下内容:
{"event_id":"job-042-start","job":"daily-report","status":"START","rows":0}
{"event_id":"job-042-end","job":"daily-report","status":"DONE","rows":145}
事后可以使用 event_id 或 status 提取失败的处理。它的优势在于,它既是人类可读的日志,又是可供其他工具依次导入的格式。
不过,当多个 PowerShell 进程同时写入同一个文件时,本示例无法保证行级别的原子写入。需要另外进行排他控制、文件锁定、基于事件ID的去重、应对磁盘满或中途退出等处理。JSONL 仅是一种便捷的存储格式,不能替代数据库事务。
| 实际业务中的挑战 | 应对思路 |
|---|---|
| 重复执行 | 赋予事件ID和执行ID,以便在重新导入时去除重复 |
| 写入中途停止 | 检测末尾行损坏,定义重新执行时的处理方式 |
| 多进程写入 | 将写入职责集中到一个进程,或进行互斥处理 |
| 文件膨胀 | 按日期或大小轮转,并备份旧日志 |
| 混入敏感信息 | 设计上不保存令牌、姓名和正文数据 |
| 防篡改对策 | 访问权限、传输目的地保护、必要时的防篡改检测 |
完整版示例
正文中的代码是用于观察运行情况的简化版。在 GitHub 的教学版中,-KeepFiles 包含了用于确认保存结果的功能、空行与非法行的处理以及后续清理逻辑。
该示例已实现,但尚未在实际的 PowerShell 环境中进行运行确认。在用于业务生产日志之前,请先使用虚拟数据确认其运行情况和输出结果。

