MySQLへのクエリをApplication Insights(.NET)のDependencyに出したい
Created at:
Application InsightsにはDependencyテレメトリーというリクエスト中に発行された外部リソースへのアクセスなどを記録する仕組みがあり、.NET向けの一式を入れておけばHTTPリクエストやSQL Serverへの問い合わせが自動で記録されます。
一方、標準で対応してないものをDependencyに出すには自前で何らかの方法で記録してあげる必要があります。MySQL Connector/Netもその例にもれず自動では記録されません。SQL Serverへの問い合わせが記録されるのはSqlClient (SQL Serverクライアント)のイベントを記録しているからであって、それ以外のデータベースドライバーでは記録されないのです。
というわけで、当然MySQLへの問い合わせでもDependencyに表示されてほしくなります。
Dependencyとしての記録
まずはそもそもApplication Insights上でDependencyとして記録するにはどうすればいいのかというところからです。
ドキュメントを見るとDependencyはテレメトリーの一種で、DependencyTelemetry
というレコードを記録すればよいということになっています。
記録するには DependencyTelemetry
を生成する方法と、TrackDependencyメソッドで記録する方法があり、今回は DependencyTelemetry
を使った記録方法で実装してみます。後者は細かいことはできないもののメソッド呼び出し一発とお手軽です。
実際の手順としては次のようになります。
TelemetryClient
クラスのインスタンスを作るTelemetryClient.StartOperation<T>
メソッドをDependencyTelemetry
型を指定して呼び出す- 帰ってきた
IOperationHolder
のTelemetryのプロパティを設定する - 計測する処理を実行
TelemetryClient.StopOperation
メソッドを呼ぶ またはIOperationHolder.Dispose
メソッドを呼ぶ
これをコードにするとこうなります。
// TelemetryClientはスレッドセーフなので使いまわせる
var telemetryClient = new TelemetryClient();
using (var operation = telemetryClient.StartOperation<DependencyTelemetry>("DependencyName"))
{
var telemetry = operation.Telemetry;
telemetry.Type = "ResourceType"; // Dependencyの種類(Http, SQLなど)
telemetry.Target = "リクエスト送信先"; // エンドポイントのホスト名とか
telemetry.Data = "何か生データー"; // SQLとか
// 何か時間のかかる処理...
await Task.Delay(1000 * 3);
}
難しいことはないですね。これでApplication Insightsに記録する方法はなんとなく理解できました。
MySQLの呼び出しを記録する
Dependencyとして記録する方法がわかったので次はMySQLへの問い合わせを記録する方法です。
今回はMySQLドライバーはMySQL公式のConnector/Netを利用していて、単純にSQLのクエリを記録したいと考えていますが、それにはそのクエリのタイミングをつかむ必要があります。そこで少し調べてみると、Connector/NetにはInterceptorというExecute系メソッドに割り込んでSQLのロギングなどが行える仕組みがあったのでそれを利用します。
InterceptorにはあらかじめExecute系メソッドに割り込むためのベースクラスである CommandInterceptorBase
クラスがあるので、このクラスを継承して各種メソッドをオーバーライドします。そしてここではオーバーライドしたメソッドでDependencyの記録を行えば良さそうというわけです。
実際に実装した例はこんな感じになります。DependencyのTargetやNameといった値の設定はSQL Serverでの記録と同じような形にしました。
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using MySql.Data.MySqlClient;
namespace WebApplication2.Diagnostics
{
public class ApplicationInsightsBaseCommandInterceptor : BaseCommandInterceptor
{
private TelemetryClient _telemetryClient = new TelemetryClient();
private string _name;
public override bool ExecuteNonQuery(string sql, ref int returnValue)
{
using (var operation = _telemetryClient.StartOperation<DependencyTelemetry>(_name))
{
var telemetry = operation.Telemetry;
telemetry.Type = "SQL";
telemetry.Data = sql;
telemetry.Target = _name;
return base.ExecuteNonQuery(sql, ref returnValue);
}
}
public override bool ExecuteReader(string sql, CommandBehavior behavior, ref MySqlDataReader returnValue)
{
using (var operation = _telemetryClient.StartOperation<DependencyTelemetry>(_name))
{
var telemetry = operation.Telemetry;
telemetry.Type = "SQL";
telemetry.Data = sql;
telemetry.Target = _name;
return base.ExecuteReader(sql, behavior, ref returnValue);
}
}
public override bool ExecuteScalar(string sql, ref object returnValue)
{
using (var operation = _telemetryClient.StartOperation<DependencyTelemetry>(_name))
{
var telemetry = operation.Telemetry;
telemetry.Type = "SQL";
telemetry.Data = sql;
telemetry.Target = _name;
return base.ExecuteScalar(sql, ref returnValue);
}
}
public override void Init(MySqlConnection connection)
{
_name = String.Format("{0} | {1}", connection.DataSource, connection.Database);
base.Init(connection);
}
}
}
Interceptorを実装したら最後にアプリケーションの設定でデータベース接続文字列に commandinterceptors
パラメータを追加して読み込ませます。パラメータの値は<CommandInterceptorClass>,<Assembly>
というフォーマットで、次のようなものになります。
commandinterceptors=WebApplication2.Diagnostics.ApplicationInsightsBaseCommandInterceptor,WebApplication2
そしてこれを有効にした状態でアプリケーションを実行するとApplication Insightsに記録されます。もちろんAzureに接続していない場合でもVisual Studioで確認できます。
この例では問い合わせが1ms以下なので全く面白くなくて残念ですが、ともあれこれで取れるようになったので無いよりは全然よさそうです。