IBM CloudのLogDNAで収集した、30日以上過去のログを検索する方法

:::note warn
IBM Cloud Data Engine(旧SQL Query)は2024年1月18日をもって非推奨となり、2024年2月18日以降、新規インスタンスの作成が出来なくなりました。さらに、2025年1月18日をもってEoSとなるため、現在ご利用のインスタンスから後継サービスであるAnalytics Engineなどの利用へ変更する必要があります。
:::

# はじめに

IBM Cloudでは、さまざまなログをLogDNAをベースにした各サービスで収集し、検索可能としています。

+ IBM Log Analysis with LogDNA
    + 仮想サーバーやIKS/ROKSのコンテナ実行環境にAgentを導入し、アプリケーションやシステムのログの収集・検索を可能とする
    + プラットフォーム・ログを構成することで、IBM Cloudの各種サービスに関するログの収集・検索を可能とする
+ IBM Cloud Activity Tracker with LogDNA
    + IBM Cloud内部のIAM関連イベントなどのログの収集・検索を可能とする

上記の有料プランは、最大30日までGUIで検索・参照が可能で、過去のログをIBM Cloud Object Storage(ICOS)にアーカイブ保管する機能があります。

30日以上過去のログはGUIで検索・参照することができなくなるので、Watson StudioとSQL Queryを用いた検索・参照方法をご紹介します。

# 前提

+ IBM Cloudアカウント
+ LogDNAの有料版インスタンスがあり、アーカイブログの設定済み
+ 上記アーカイブログと、検索結果を保管するためのICOSバケット（別々を推奨）
+ Watson Studioインスタンス（ライト版でOK）
+ SQL Queryインスタンス（ライト版でOK）

# アーカイブログのパスについて

LogDNAのアーカイブログは、改行区切りのJSONファイルがgzip圧縮されたものが、毎時1ファイル作成されます。

ICOS上のパスは以下のようになります。

```
year=YYYY/month=MM/day=DD/<accountID>.<YYYY>-<MM>-<DD>.<HH>00.json.gz
```

なお、2021年1月14日頃より前は以下のようなパスで日次単位で保管されているので、必要に応じて入力パスを変更してください。

```
<accountID>.<YYYY>-<MM>-<DD>.<clusterId>.json.gz
```

accountIDやclusterIdについて、過去ログの参照という観点で特に意識する必要はないので、説明は省略します。

# アーカイブログの形式について

アーカイブログのJSONですが、多階層の形式になっています。具体的には以下のとおりです。

1. 第一階層のキーは唯一「_source」のみで、第二階層に各種キーと値が保管されている
2. ログ本体は「_source._line」に入っている
3. タイムスタンプは「_source._ts」で、UNIX時刻(ミリ秒)
4. その他のキーにどのようなものが存在するかは、ログ出力元の形式と、それをLogDNAが解析した結果によって異なる

その他のキーについて、よく利用するものに以下のようなものがあります。

+ _source._file
    + サーバー上のログファイルパス。/var/log/messages など。
+ _source._host
    + サーバーのホスト名
+ _source.namespace
    + IKS/ROKS(IBM Cloudのコンテナ実行基盤)のネームスペース
+ _source._app
    + IKS/ROKSのApp名

他にどのようなキーが存在するかについては、適当なアーカイブログファイルをインプットに以下のようなクエリーをSQL Queryで実行することで、ログに含まれるキー名とそのデータ型を確認することができます。

```
SELECT * FROM DESCRIBE(cos://jp-tok/[アーカイブログ保管先のバケット名]/[アーカイブログファイルのパス] STORED AS JSON)
```

# 過去ログ参照Notebook概要

まず、SQL Queryのモジュールを導入、importします。エラーメッセージが出力されることがありますが、無視して構いません。

```Python:cell-1
# SQLQuery導入
!pip -qqq install ibmcloudsql pyarrow
import ibmcloudsql
```

次に、入力元のICOS、出力先のICOS、SQL Queryへの必要な権限を持つAPI Keyと、使用するSQL QueryのCRNを指定して、クエリー実行の準備を実施します。CRNは、リソース一覧で該当のインスタンスを選択した際に表示されるダイアログから確認出来ます。

```Python:cell-2
# API Key
cloud_api_key = '**************'
# SQL Query設定
sql_crn = '**************'
# クエリー実行準備
sqlClient = ibmcloudsql.SQLQuery(cloud_api_key, sql_crn)
sqlClient.logon()
```

次にクエリーを組み立てます。例として、以下の条件で絞り込みするクエリーを示します。入力と出力のICOSバケット名は環境に応じて変更してください。また、結果はCSV形式で取得(STORED AS CSV)するようにしています。

+ FROM cos://jp-tok/INPUT-BUCKETNAME/year=2021/month=11/day=29/*
    + 2021年11月29日(UTC)のログを対象とする
+ _source._file = '/var/log/messages'
    + 対象のファイルは /var/log/messages
+ _source._host LIKE 'hostname%'
    + 対象のホスト名はhostnameで始まる
+ _source._line LIKE '%systemd%'
    + 対象のログ本文に「systemd」を含む

```Python:cell-3
# クエリー組み立て
query = """
SELECT cast(_source._ts / 1000 + 32400 as timestamp) as DATETIME , _source._line as LINE
FROM cos://jp-tok/INPUT-BUCKETNAME/year=2021/month=11/day=29/*  STORED AS JSON
WHERE _source._file = '/var/log/messages' AND _source._host LIKE 'hostname%' AND _source._line LIKE '%systemd%'
ORDER BY _source._ts
INTO cos://jp-tok/OUTPUT-BUCKETNAME/PATH STORED AS CSV
"""
```

次はクエリーを実行する部分です。クエリーの実行方法はいくつかありますが、クエリー実行の完了を待つタイプの関数（run_sql, wait_for_job）では、クエリーの実行が長時間に渡る場合にタイムアウトしてしまうことがあるので、以下のようにクエリーを投入したあと、定期的にその状況を確認して、終了していたら抜けるようなロジックで実行するようにします。

正常に完了すれば、「completed」と表示されます。

```Python:cell-4
import time, sys

# クエリ投入
jobid = sqlClient.submit_sql(query)
# 実行中になるまで待つ
time.sleep(10)
# 実行中ではなくなるまで待つ
while sqlClient.get_job(jobid)['status'] == 'running':
    time.sleep(10)
# 結果がfailedならば異常終了
if sqlClient.get_job(jobid)['status'] == 'failed':
    print('ERROR')
    sys.exit(1)
# 結果ステータス表示
print(sqlClient.get_job(jobid)['status'])
```

最後に、以下のように実行すると、SQL Queryへのリンク（`https://us.sql-query.cloud.ibm.com/sqlquery/?instance_crn=〜〜〜`）が表示されるので、リンク先のSQL Query画面からクエリーの実行結果を確認することができます。

```Python:cell-5
# SQL Queryへのリンクを表示
print(sqlClient.sql_ui_link())
```

また、PixieDustを用いてNotebook上で結果を確認することも可能です。

```Python:cell-6
# pixiedustでの結果内容表示準備
!pip -qqq install pixiedust
import pixiedust as px

# pixiedustでの結果内容表示
resultdf = sqlClient.get_result(jobid)
px.display(resultdf)
```

# まとめ

トラブル対応などで、30日以上前のログを調査しなければいけない状況になった際にあわてないように、テンプレートで1つ持っておくと安心かなと思います。

# 参考

+ [ibmcloudsql](https://github.com/IBM-Cloud/sql-query-clients/tree/master/Python)