Power Query のトレースログ を Power Query でパースしてみた

Power Query のトレースログをパースしてみたなど。そのまま眺めても何となく見えてくるのだけど整理された状態で見えたらよいよね。コミュニティイベントでチラ見せしたものを後生大事に塩漬けしててもしょうがないので公開しておこう。修行のお供にでも。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Power Query ってあるでしょ、あれは魔法の杖だから修業せずに使えるようにはならないんだよ。 <a href="https://t.co/9gy8gzkiGQ">https://t.co/9gy8gzkiGQ</a> <a href="https://twitter.com/hashtag/PowerBI?src=hash&amp;ref_src=twsrc%5Etfw">#PowerBI</a> <a href="https://twitter.com/hashtag/PowerQuery?src=hash&amp;ref_src=twsrc%5Etfw">#PowerQuery</a> <a href="https://twitter.com/hashtag/PBIJP?src=hash&amp;ref_src=twsrc%5Etfw">#PBIJP</a></p>&mdash; Takeshi Kagata (@PowerBIxyz) <a href="https://twitter.com/PowerBIxyz/status/1113638308981039104?ref_src=twsrc%5Etfw">2019年4月4日</a></blockquote>


**Power BI Desktop に "クエリ診断" 機能が追加されました**
Power Query エディターで診断開始から終了、パースされたファイル(JSON)からログなど参照できるようになりました :smile: 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/a7d7e0bf-edb5-bde7-b17c-18da4ea61491.png)

# パーサー本体

トレースログ出力先は [Power Query エディター] ⇒ [オプションと設定] ⇒ [診断]
トレース開始は [トレースを有効にする] を ON

```text:解析、リファクタリングなどお好きにどうぞ
let
    Source = Folder.Contents(" <<  >> "),
    FilteredRows = Table.SelectRows(Source, each Text.Lower([Extension]) = ".log"),
    Content = List.Combine(List.Transform(FilteredRows[Content], Lines.FromBinary)),
    TableFromContent = Table.FromColumns({Content}, {"Value"}),
    SplitColumnByDelimiter1 = Table.SplitColumn(
        TableFromContent,
        "Value",
        Splitter.SplitTextByEachDelimiter(
            {":"},
            QuoteStyle.None,
            false
        ),
        {"Value.1", "Value.2"}
    ),
    SplitColumnByDelimiter2 = Table.SplitColumn(
        SplitColumnByDelimiter1,
        "Value.2",
        Splitter.SplitTextByEachDelimiter(
            {":"},
            QuoteStyle.None,
            false
        ),
        {"Value.2", "Value.3"}
    ),
    TryParseJSON = Table.TransformColumns(
        SplitColumnByDelimiter2,
        {
            "Value.3",
            each List.Last(
                List.Generate(
                    ()=>[SourceText = _, Condition = true, Limit = 0],
                    each [Condition] and [Limit] <= 5,
                    each [
                        Limit = [Limit] + 1,
                        _try = try Json.Document([SourceText]),
                        Condition = _try[HasError],
                        _field = Text.BetweenDelimiters(_try[Error][Message], "'", "'"),
                        _SourceText = Text.Split([SourceText], """" & _field & """"),
                        SourceText = Text.Combine(
                            List.Combine(
                                List.Zip(
                                    {
                                        _SourceText,
                                        List.Transform(
                                            {1 .. List.Count(_SourceText) - 1},
                                            each """" & _field & Text.From(_) & """"
                                        )
                                    }
                                )
                            )
                        )
                    ],
                    each [SourceText]
                )
            )
        }
    ),
    ParsedJSON = Table.TransformColumns(
        TryParseJSON,
        {"Value.3", Json.Document}
    ),
    RemovedErrors = Table.RemoveRowsWithErrors(ParsedJSON),
    // ここで展開するフィールドを調整
    ExpandedValue3 = Table.ExpandRecordColumn(
        RemovedErrors,
        "Value.3",
        {"Start", "Action", "Duration", "Message", "RequestUri"},
        {"Start", "Action", "Duration", "Message", "RequestUri"}
    ),
    ChangedType = Table.TransformColumnTypes(
        ExpandedValue3,
        {
            {"Start", type text},
            {"Action", type text},
            {"Duration", type text},
            {"Message", type text}
        }
    ),
    SortedRows = Table.Sort(ChangedType,{{"Start", Order.Ascending}}),
    AddedIndex = Table.AddIndexColumn(SortedRows, "Index", 1, 1)
in
    AddedIndex
```

## ざっくり注釈
- Folder.Contents の引数には、トレースログが出力されるパス。Folder.Files ではないので サブフォルダーのファイルは見えません。
- ファイル拡張子 ".log" でフィルタしパースしないファイルを除外。
- ログファイル (binary)ごとに Lines.FromBinary でテキスト (text)へ変換しつつリスト (list)にして、それらを List.Combine でひとつのリスト (list) におまとめ
- このあと列 (column)の分解などするので テーブル (table) に変換
- おまとめ済みリストのアイテムごとに見たとき、デリミタ ":" で 3つに展開できそうなのがわかるので、さきにふたつ抽出。このとき、QuoteStyle.None にしておかないと困ることが発生する。
- 残る最後の列 (column)をみたとき、もしかして JSON ? で、Json.Document でパースすると Expression.Error が頻発。調査したら フィールド名が重複することがわかったので、フィールド名をリネームしながら Expression.Error が発生しなくなるまで List.Generate で繰り返し
- おおむね JSONとしてパースしても問題ない状態なったのでテーブル(table)になるよう展開
- 時系列でソートしインデックスをつけておいた

# 思ったこと🙄
Power BI Desktop のログは Excel で、Excel のログは Power BI Desktop で 眺めた方が楽かも。
どこまで細かに把握できるかはわからない。ログのフォーマットはいつか変わるかもしれないし。でも、どのように処理がされるのかヒントを得ることは可能じゃないかな。Diagnostics.Trace については別にポストしようか。

# その他
- [Power BI 勉強会 - connpass](https://powerbi.connpass.com/)
- [Power BI Desktop を使い始めるには](http://qiita.com/PowerBIxyz/items/6c4735cb5b5712e70981)
- [Excel で Power Query を使い始めるには](http://qiita.com/PowerBIxyz/items/52e563037b855a099ca9)