일부 일반적인 Office 365 통합 감사 로그 이벤트를 표 형식 보고 형식으로 변환하는 간단한 jq 기반 도구를 게시하고 싶었지만 일부 키 배열이 중첩되는 방식에 문제가 있습니다. 특히 ID, 경로가 포함된 세트와 메시지 ID 및 크기 행이 포함된 폴더[] 세트를 드릴다운할 때 동기화/조합에서 배열의 관련 값을 유지하는 방법을 찾을 수 없습니다. 마치 의도치 않게 반복하는 것처럼 각 값의 수많은 조합을 얻습니다.
다음은 몇 가지 샘플 데이터입니다.
{"CreationTime":"2024-02-06T12:13:14","Id":"abcdabcd-1234-1234-5555-888888888888","Operation":"MailItemsAccessed","ResultStatus":"Succeeded","UserId":"[email protected]","ClientIPAddress":"5.5.5.5","Folders":[{"FolderItems":[{"InternetMessageId":"<[email protected]>","SizeInBytes":12345},{"InternetMessageId":"<[email protected]>","SizeInBytes":11122},{"InternetMessageId":"<[email protected]>","SizeInBytes":88888}],"Id":"EEEEEEEE","Path":"\\Outbox"},{"FolderItems":[{"InternetMessageId":"<[email protected]>","SizeInBytes":44444},{"InternetMessageId":"<[email protected]>","SizeInBytes":100000},{"InternetMessageId":"<[email protected]>","SizeInBytes":109000},{"InternetMessageId":"<[email protected]>","SizeInBytes":22000},{"InternetMessageId":"<[email protected]>","SizeInBytes":333333}],"Id":"FFFFFFFFFFFFFFFFFAB","Path":"\\Inbox"}]}
{"CreationTime":"2024-02-06T20:00:00","Id":"abcdabcd-1234-1234-6666-9999999999999","Operation":"MailItemsAccessed","ResultStatus":"Succeeded","UserId":"[email protected]","ClientIPAddress":"7.7.7.7","Folders":{"FolderItems":[{"InternetMessageId":"<[email protected]>","SizeInBytes":77777},{"InternetMessageId":"<[email protected]>","SizeInBytes":888888},{"InternetMessageId":"<[email protected]>","SizeInBytes":99999}],"Id":"12341234","Path":"\\Temp"}}
원하는 출력:
제작시간 | ID | 사용자 ID | 클라이언트 IP 주소 | 폴더 ID | 폴더 경로 | 인터넷 메시지 ID | 크기(바이트) |
---|---|---|---|---|---|---|---|
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | 에에에에에에에 | \보낼 편지함 | [이메일 보호됨] | 12345 |
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | 에에에에에에에 | \보낼 편지함 | [이메일 보호됨] | 11122 |
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | 에에에에에에에 | \보낼 편지함 | [이메일 보호됨] | 88888 |
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | FFFFFFFFFFFFFFFFAB | \받은 편지함 | [이메일 보호됨] | 44444 |
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | FFFFFFFFFFFFFFFFAB | \받은 편지함 | [이메일 보호됨] | 100000 |
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | FFFFFFFFFFFFFFFFAB | \받은 편지함 | [이메일 보호됨] | 109000 |
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | FFFFFFFFFFFFFFFFAB | \받은 편지함 | [이메일 보호됨] | 22000 |
2024-02-06T12:13:14 | abcdabcd-1234-1234-5555-888888888888 | [이메일 보호됨] | 5.5.5.5 | FFFFFFFFFFFFFFFFAB | \받은 편지함 | [이메일 보호됨] | 333333 |
2024-02-06T20:00:00 | 12341234 | [이메일 보호됨] | 7.7.7.7 | 12341234 | \온도 | [이메일 보호됨] | 77777 |
2024-02-06T20:00:00 | 12341234 | [이메일 보호됨] | 7.7.7.7 | 12341234 | \온도 | [이메일 보호됨] | 888888 |
2024-02-06T20:00:00 | 12341234 | [이메일 보호됨] | 7.7.7.7 | 12341234 | \온도 | [이메일 보호됨] | 99999 |
요소는 .Folders
때때로 문자열 형식일 수 있지만 fromjson
. 예를 들어:
[...]"Folders": "[{\"FolderItems\":[{\"InternetMessageId\":\""Fo<[email protected]>\",\"SizeInBytes\":12345},[...]
지금까지의 코드:
cat | jq '
if has("Folders") then
if(.Folders | type=="string") and .Folders != "" then .Folders |= fromjson end |
if(.Folders | type=="string") and .Folders == "" then .Folders = null end
end | .' | # works up to here at least
jq '
if has("Item") then .Item |= (if type=="string" and .!="" then fromjson else {} end) else .Item|={} end |
if has("Item") then
if .Item | has("Id") then .ItemId = .Item.Id else .ItemId={} end |
if .Item | has("ParentFolder") then
.ItemParentFolderId=.Item.ParentFolder.Id? |
.ItemParentFolderPath=.Item.ParentFolder.Path? |
.ItemParentFolderName=.Item.ParentFolder.Name?
end
end | . ' | cat # works up to here at least
jq '
if has("Folders") then
if (.Folders | select(type=="array")) then
.Folders[].Id? |
.FoldersPath=.Folders[].Path? |
.FoldersFolderItems=.Folders[].FolderItems?
else . end
end
' |
jq -r '. | (.TimeGenerated // .CreationTime) as $EventTime |
.ClientIP = if .ClientIP == "" then null else .ClientIP end |
.ClientIP_ = if .ClientIP_ == "" then null else .ClientIP_ end |
.Client_IPAddress = if .Client_IPAddress == "" then null else .Client_IPAddress end |
.ClientIPAddress = if .ClientIPAddress == "" then null else .ClientIPAddress end |
.ActorIpAddress = if .ActorIpAddress == "" then null else .ActorIpAddress end |
(.ClientIP // .ClientIP_ // .Client_IPAddress // .ClientIPAddress // .ActorIpAddress) as $IPAddress |
(.UserId // .UserId_) as $LogonUser |
.FFIIMI as $InternetMessageId |
.FFISIB as $SizeInBytes |
{EventTime: $EventTime, IPAddress: $IPAddress, LogonUser: $LogonUser, InternetMessageId: $InternetMessageId, SizeInBytes: $SizeInBytes} + . |
[.Id, .EventTime, .IPAddress, .LogonUser, .MailboxOwnerUPN, .Operation, .InternetMessageId, .SizeInBytes] | @csv'
답변1
샘플 데이터로 제공해주신 JSON으로 시작했습니다. 이 JSON이 어떤 방식으로든 사전 처리되었는지는 확실하지 않습니다.
최상위 Folders
배열이 보이기 때문에아니요단일 항목이 포함되어 있으면 배열이고, 아직 배열이 아닌 경우 먼저 배열로 변환해야 합니다. 에서 jq
우리는 이것을 할 수 있습니다
.Folders |= (if type == "array" then . else [.] end)
나머지 변환의 일반적인 아이디어는 상위 수준 데이터(즉, 최상위 수준의 일부 키+값 쌍과 각 요소의 키)를 Id
하위 수준(즉, 요소)에 복사하는 것입니다. 그런 다음 각 요소를 CSV 레코드로 변환할 수 있습니다.Path
Folders
FolderItems
FolderItems
키의 이름이 중복되는 것을 방지하려면 Folders
요소의 키 이름을 로 Id
바꿔야 합니다 FolderId
(그리고 일관성을 위해 Path
동일한 레벨의 이름을 로 바꿔야 합니다 ).FolderPath
우리가 원하는 최상위 데이터를 선택하고 내부 변수를 사용하여 아래로 전송할 수 있습니다.
pick(.CreationTime, .Id, .UserId, .ClientIPAddress) as $record
이것은 $record
생성 됩니다
{
"CreationTime": "2024-02-06T12:13:14",
"Id": "abcdabcd-1234-1234-5555-888888888888",
"UserId": "[email protected]",
"ClientIPAddress": "5.5.5.5"
}
...첫 번째 JSON 개체입니다. 우리가 얻은 키는 최종 CSV 출력에 필요한 순서가 아닙니다.
Folders
그런 다음 요소 만 추출 하고 .Folders[]
각 요소의 Id
합계를 선택할 수 있습니다 Path
. 다시 사용할 수 없도록 이름을 바꾸므로 pick()
좀 더 실무적인 작업이 필요합니다.
.Folders[] | { FolderId: .Id, FolderPath: .Path } as $folder
그런 다음 이를 사용하여 합계 앞에 추가할 수 있는 요소 .FolderItems[]
집합을 얻을 수 있습니다 .FolderItems
$record
$folder
.FolderItems[] | $record + $folder + .
단일 jq
표현으로:
.Folders |= (if type == "array" then . else [.] end) |
pick(.CreationTime, .Id, .UserId, .ClientIPAddress) as $record |
.Folders[] | { FolderId: .Id, FolderPath: .Path } as $folder |
.FolderItems[] | $record + $folder + .
질문의 데이터를 고려하면 결과는 다음과 같습니다.
{
"CreationTime": "2024-02-06T12:13:14",
"Id": "abcdabcd-1234-1234-5555-888888888888",
"UserId": "[email protected]",
"ClientIPAddress": "5.5.5.5",
"FolderId": "EEEEEEEE",
"FolderPath": "\\Outbox",
"InternetMessageId": "<[email protected]>",
"SizeInBytes": 12345
}
{
"CreationTime": "2024-02-06T12:13:14",
"Id": "abcdabcd-1234-1234-5555-888888888888",
"UserId": "[email protected]",
"ClientIPAddress": "5.5.5.5",
"FolderId": "EEEEEEEE",
"FolderPath": "\\Outbox",
"InternetMessageId": "<[email protected]>",
"SizeInBytes": 11122
}
(등.)
개인적으로 저는 다음 방법을 사용하여 이 JSON 개체 집합을 CSV로 변환합니다.밀러:
$ jq '.Folders |= (if type == "array" then . else [.] end) | pick(.CreationTime, .Id, .UserId, .ClientIPAddress) as $record | .Folders[] | { FolderId: .Id, FolderPath: .Path } as $folder | .FolderItems[] | $record + $folder + .' file | mlr --j2c cat
CreationTime,Id,UserId,ClientIPAddress,FolderId,FolderPath,InternetMessageId,SizeInBytes
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,EEEEEEEE,\Outbox,<[email protected]>,12345
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,EEEEEEEE,\Outbox,<[email protected]>,11122
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,EEEEEEEE,\Outbox,<[email protected]>,88888
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,FFFFFFFFFFFFFFFFFAB,\Inbox,<[email protected]>,44444
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,FFFFFFFFFFFFFFFFFAB,\Inbox,<[email protected]>,100000
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,FFFFFFFFFFFFFFFFFAB,\Inbox,<[email protected]>,109000
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,FFFFFFFFFFFFFFFFFAB,\Inbox,<[email protected]>,22000
2024-02-06T12:13:14,abcdabcd-1234-1234-5555-888888888888,[email protected],5.5.5.5,FFFFFFFFFFFFFFFFFAB,\Inbox,<[email protected]>,333333
2024-02-06T20:00:00,abcdabcd-1234-1234-6666-9999999999999,[email protected],7.7.7.7,12341234,\Temp,<[email protected]>,77777
2024-02-06T20:00:00,abcdabcd-1234-1234-6666-9999999999999,[email protected],7.7.7.7,12341234,\Temp,<[email protected]>,888888
2024-02-06T20:00:00,abcdabcd-1234-1234-6666-9999999999999,[email protected],7.7.7.7,12341234,\Temp,<[email protected]>,99999
jq
Miller를 사용하여 CSV로 변환하려면 mlr
명령을 다음과 같이 바꾸십시오.
jq -s -r '(first|keys|@csv), map([.[]]|@csv)[]'
그러면 첫 번째 입력 개체에서 키가 CSV 헤더로 선택되고 모든 값의 형식이 개체당 하나의 레코드로 지정됩니다.