Office 365 로그의 중첩 배열을 테이블로 변환

Office 365 로그의 중첩 배열을 테이블로 변환

일부 일반적인 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 레코드로 변환할 수 있습니다.PathFoldersFolderItemsFolderItems

키의 이름이 중복되는 것을 방지하려면 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

jqMiller를 사용하여 CSV로 변환하려면 mlr명령을 다음과 같이 바꾸십시오.

jq -s -r '(first|keys|@csv), map([.[]]|@csv)[]'

그러면 첫 번째 입력 개체에서 키가 CSV 헤더로 선택되고 모든 값의 형식이 개체당 하나의 레코드로 지정됩니다.

관련 정보