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.

と言うわけで以上です。

UserPrincipal 情報を取得する

C#でUserPrincipal情報の取得について備忘録、例によってLINQPadでの実行コード。System.DirectoryServices.AccountManagement の参照設定とNamespaceの設定が必要です。

void Main()
{
    // 対象はLocalMachine
    using (var context = new PrincipalContext(ContextType.Machine))
    {
        // こんな方法や
        using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
        {
            searcher.QueryFilter.SamAccountName = "Guest";
            searcher.FindOne().Dump();
        }

        // こんな方法で検索できます
        var up = UserPrincipal.FindByIdentity(context, "Guest");
        up.Dump();
    }
}

ちなみにDumpメソッドはLINQPadの拡張で、オブジェクトの中身をダンプしてくれる便利なメソッドです。

Domainの場合や、全部だす場合はこんな感じ。

void Main()
{
    // ドメインの場合
    using (var context = new PrincipalContext(ContextType.Domain, "hoge.local"))
    {
        // 全部出す
        using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
        {
            foreach (var result in searcher.FindAll()) 
            {
                  result.Dump()
            }
        }
    }
}