Azureの小ネタ (改)

~Azureネタを中心に、色々とその他の技術的なことなどを~

Postgresql のログをApplication Insightsに出力する

.NETフレームワークの場合、SQL DBの情報は勝手にApplication Insightsに送信してくれますが、Postgresの場合は自動で送信してくれません。ちょっと調べたのですが、執筆時点でそういう仕組みとかサンプルも無さそうでした。

逆にJavaなどは、JDBCレベルでApplication Insightsにデータを送信してくれるので、DBがMySQLやPostgresの場合でも問題なく情報を送信してくれます。

ということで、ここでは NET + Postgres を使った場合のデータ送信の方法について試したことを備忘録としての残してオコますが、もっと良いやり方があるかもしれまえんので、ご参考程度に。

Postgres

Windows環境を汚したくないので、Dockerとかでインストールして接続しましょう。以下の例ですと、postgresというDBが作成されます。適当にテーブル作成し、データをINSERTしておきましょう。

docker run --name my-postgres -e POSTGRES_PASSWORD=mypassword -p 5432:5432 -d postgres
docker container exec -it my-postgres bash

NET

Postgresへの接続ライブラリは以下を利用します。

www.npgsql.org

また幸いEntity Frameworkに対応しているので試してみます。以下のコマンドでコンテキストクラスとPOCOクラスが生成されでしょう。

dotnet ef dbcontext scaffold "Host=127.0.0.1;Database=postgres;Username=postgres;Password=mypassword" Npgsql.EntityFrameworkCore.PostgreSQL -o database

ログクラスの実装

ログについては、以下の通りですが、ログプロパイダとログ出力クラス自身を実装する必要があります。

www.npgsql.org

適当にログを垂れ流すサンプルを作成してみました。単純にStringが渡されてくるだけなので、ログに意味を持たせることは難しいです。

以下の実装では、

  • 例外は例外テレメトリへ
  • 普通のメッセージは、トレーステレメトリに出力すればいいのですが、Application Insightsで依存関係を見たい場合は、依存テレメトリにも出力できます(例では2つ書いてかります)

ただ、依存テレメトリ出力する場合、Durationが分らないのでゼロを指定していますが、Azure ポータルで見たときにちょっとかっこ悪いです。

sing System;
using Microsoft.ApplicationInsights;
using Npgsql.Logging;

namespace webapi.Logger
{
    public class PgsqlAppInsightsLoggingProvider : INpgsqlLoggingProvider
    {
        private TelemetryClient _client;
        private NpgsqlLogLevel _minLevel;

        public PgsqlAppInsightsLoggingProvider(TelemetryClient client, NpgsqlLogLevel minLevel)
        {
            _client = client;
            _minLevel = minLevel;
        }

        public NpgsqlLogger CreateLogger(string name)
        {
            return new PgsqlAppInsightLogger(name, _client, _minLevel);
        }
    }

    public class PgsqlAppInsightLogger : NpgsqlLogger
    {
        private string _name;
        private TelemetryClient _client;
        private NpgsqlLogLevel _minLevel;

        public PgsqlAppInsightLogger(string name, TelemetryClient client, NpgsqlLogLevel minLevel)
        {
            _name = name;
            _client = client;
            _minLevel = minLevel;
        }

        public override bool IsEnabled(NpgsqlLogLevel level) => level >= _minLevel;


        public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception exception = null)
        {
            if (!IsEnabled(level))
            {
                return;
            }

            if (exception == null)
            {
                _client.TrackTrace(msg);
                _client.TrackDependency("postgres", "sql", msg, DateTimeOffset.UtcNow, TimeSpan.Zero, true);
            }
            else
            {
                _client.TrackException(exception);
            }

            Console.WriteLine(msg);
        }
    }
}

ポータルで確認

依存テレメトリに出した場合は以下ように、リクエスト配下にちゃんと表示されます。3つ出ているのは、Connection Open/クエリ実行/Connection Closeとなっています。

そして、Durationがゼロなので時間軸の帯が正しく出力されなくて、ちょっとかっこ悪いです。

f:id:StateMachine:20201029110840p:plain

まとめ

たんに文字列の垂れ流しなので、いまいちSQL DBと同等のメトリクスにはならないのが残念ですが、もうちょっとましなイベントを拾ったりできるのかもしれません。

パフォーマンスカウンタ機能もあるようなのですが、今後その機能は廃止されそうなので使ってません。

Performance | Npgsql Documentation

Npgsql 3.2 includes support for performance counters, which provide visibility into connections and the connection pool - this helps you understand what your application is doing in real-time, whether there's a connection leak, etc. Npgsql counter support is very similar to that of other ADO.NET providers, such as SqlClient, it's recommended that your read that page first. The feature was removed in Npgsql 5.0.

と言うわけで以上です。

Azure Cosmos DB Spring Boot Starter を使う

Azure Spring Boot Starter ライブラリの一つに、Cosmos DBへアクセスするライブラリが存在するのですが、今回はこれについて。

Docsの解説記事としては以下の通りです。

docs.microsoft.com

初期設定

Web Application でなくてもいいので、普通にCommandLineRunnerを使って簡易に実装しつつ、挙動を確かめるのがお手軽かと思います。

ライブラリは以下の者を使います。

mvnrepository.com

以下のような依存をつければよいでしょう(執筆時点)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-cosmosdb-spring-boot-starter</artifactId>
            <version>2.3.5</version>
        </dependency>

CosmosDBの用意

現在、AzureサブスクリプションにはFreeでCosmosDBを1つだけ作れるのでこれを利用すると良いと思います。

azure.microsoft.com

ここで適宜DBとコンテナを作成すると以下の情報が手に入るとおもいます。

  • URL
  • キー
  • DB名
  • コンテナ名

サンプルなので、パーティションキーは /id にしてもらっても問題ないでしょう。

Azure Cosmos DB でのパーティション分割 | Microsoft Docs

構成ファイル

構成は、application.propertiesに設定しますが、yamlでも構いません。先ほど取得した情報のうちURI、キー、DB名を定義します。

azure.cosmosdb.uri=
azure.cosmosdb.key=
azure.cosmosdb.database=

POJOクラスの定義

POJOクラスを定義します。めんどくさいGetter/Setter的なコードはlombokにお任せします。注目すべきは、クラスに指定したDocumentアノテーションです。これにコレクション(コンテナ)を指定します。 あと、PartitionKeyアノテーションはDBを作成したときのパーティションキーに指定します。

package com.example.demo;

import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;

import org.springframework.data.annotation.Id;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
@Document(collection = "Container1")
public class Pet {
    
    @Id
    @PartitionKey
    private String id;
    private String owner ;
    private String name;
    private PetKind kind;
}

リポジトリの定義

リポジトリは同期、非同期に二つが用意されています。該当パッケージのJavadocは以下の通りです。

docs.microsoft.com

同期で利用したいならCosmosRepository<T,ID> 、非同期で利用したいならReactiveCosmosRepository<T,K> を使います。

リポジトリはインタフェースを継承して中身は空、実装クラスも用意しなくて問題ないです。面倒くさいところはライブラリがやってくれます。

@Repository
public interface PetRepository extends CosmosRepository<Pet, String> {
}

CRUD

CRUDはリポジトリ経由で操作できます。DIされるので、それを利用します(実装いらず)。以下は新規作成例です。

    @Autowired
    private PetRepository repository;

    @Override
    public void run(String... args) throws Exception {

        // Crete
        var pet1 = repository.save(new Pet(UUID.randomUUID().toString(), "suzuki-san", "pochi", PetKind.Doc));
        var pet2 = repository.save(new Pet(UUID.randomUUID().toString(), "tanaka-san", "tama", PetKind.Cat));

        System.out.println(pet1);
        System.out.println(pet2);
        
    }

実行後、ポータルを参照するとItemが作られていることが確認できます。

提供されている操作自身は、Spring Bootのリポジトリインタフェースを継承しているので、それらが利用出来ると思います。

f:id:StateMachine:20200930115850p:plain

以上

Azure SDK for Java におけるログなど

Azure SDK for JavaのことがBlogで記事になっていたのでザックリ内容を確認しました。

devblogs.microsoft.com

原題は「in Azure Functions」とはなっていますが、Functionsに限らないSDK Loggingの話だと捉えた方がよさそうです。気をつけなくてはいけないのは、JavaのFunctionsでは、ロガーインスタンスはDIされないので、Contextオブジェクトから取得することになるくらいですちなみに、context.getLogger().info("hoge") みたいになります。

話をSDKに戻しますと、SDKのログを取得できるのは内部動作の理解にも繋がりますし、デバッグ時にも大変有効な手段です。依然は、SDK毎にバラバラだった気がしますが、最近のAzure SDKはガイドラインに沿って作られているせいか、機能毎にそれほどばらつき無く実装されているような気がします。

Key Vaultの場合

大体のJava SDKはクライアントをBuilderパターンで構築しますが、そこにhttpLogOptionsを挟み込むだけでログが取得できます。無論、slf4jを介したログ出力になっているので、このあたりの設定は必要ですが、主要なフレームワーク(Spring Bootとかは)には組み込まれていると思うので、そのまま出力されるでしょう。

        var kvCreds = new ClientSecretCredentialBuilder()
                .tenantId("")
                .clientId("")
                .clientSecret("")
                .build();

        var logOptions = new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS);

        var secretClient = new SecretClientBuilder()
                .httpLogOptions(logOptions)
                .vaultUrl(kvUri)
                .credential(kvCreds)
                .buildClient();

Azure上にデプロイして動かないなど、Managed Idの設定ではまったりするので、ログが取得できるのは、どこに設定ミスがあるかを確認したりするうえで便利だと思います。

ログの種類

https://github.com/Azure/azure-sdk-for-java/blob/master/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogDetailLevel.java

のとおり、NONE/BASIC/HEADERS/BODY/BODY_AND_HEADERSの5種類です。

  • BASICは、URLとHTTPメソッドとリクエスト
  • HEADERSは、BASICに加えてリクエストとレスポンスヘッダ
  • BODYは、BASICにくわえリクエストとレスポンスボディ
  • BODY_AND_HEADERSは、全部という感じです。

BASICの例は以下のような感じになります。

2020-09-29 11:08:59.585  INFO 60628 --- [           main] c.a.s.k.secrets.SecretAsyncClient        : Retrieving secret - ConnectionString
2020-09-29 11:08:59.597  INFO 60628 --- [           main] c.a.s.k.secrets.SecretService.getSecret  : --> GET https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED
Try count: 1

2020-09-29 11:09:01.201  INFO 60628 --- [ctor-http-nio-1] c.a.s.k.secrets.SecretService.getSecret  : <-- 401 https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED (1603 ms, 87-byte body)
<-- END HTTP
2020-09-29 11:09:01.764  INFO 60628 --- [nPool-worker-19] c.azure.identity.ClientSecretCredential  : Azure Identity => getToken() result for scopes [https://vault.azure.net/.default]: SUCCESS
2020-09-29 11:09:01.764  INFO 60628 --- [nPool-worker-19] c.a.s.k.secrets.SecretService.getSecret  : --> GET https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED
Try count: 1

2020-09-29 11:09:02.141  INFO 60628 --- [ctor-http-nio-3] c.a.s.k.secrets.SecretService.getSecret  : <-- 200 https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED (377 ms, 302-byte body)
<-- END HTTP
2020-09-29 11:09:02.175  INFO 60628 --- [ctor-http-nio-3] c.a.s.k.secrets.SecretAsyncClient        : Retrieved secret - connectionString

Storageの場合

Storage SDKの場合も同じく、httpLogOptionsメソッドを挟み込めばOKです。

        var storageClient = new BlobServiceClientBuilder()
                .httpLogOptions(logOptions)
                .endpoint(endpoint)
                .credential(storageCreds)
                .buildClient();

他のSDK

これ以外にCosmos SDKを試してみましたが、BuilderクラスにhttpLogOptionsメソッドは実装されていませんでした。まだ全てのSDKで使えるわけではなさそうです。(CosmosにはCosmos SDKのログの方法が提供されているとは思いますが)

というわけで、今回も誰得なブログでした。以上。