アポロStudioをGraphSQLと統合する.ネット- Part 2


その中で、私は、GraphSQLの実装をサポートするという課題と、Protobufインタフェースを介したアポロスタジオへの統合を開始する方法を取り上げました.サポートコードはGithubとして利用可能ですmattjhosking/graphql-dotnet-apollo-studio .

クライアントの作成
我々が必要とする最初のものは、DIと単体テストのためのクライアントのためのインターフェースです.
public interface IGraphQLTraceLogger
{
  void LogTrace(HttpContext httpContext, string? operationName, string? query, ExecutionResult result);
  AsyncAutoResetEvent ForceSendTrigger { get; }
  Task Send();
}
The LogTrace メソッドはHttpContext(ヘッダーを取得するための)、操作名と問い合わせ(リクエストハッシュを決定するために、後により多く)とアポロ追跡情報を含んでいる実行結果をとります.Send 単にすべてのキュートレースをアポロスタジオに送信しますForceSendTrigger 送信バッチ間の遅延をオーバーライドすることができます.非同期式の詳細についてはthis Microsoft Dev blogs article .

トレースの作成
まず、前の記事からトレースをトレースするメトリクスを使用して、実行結果をトレースオブジェクトに変換する必要があります.これが成功するならば、私たちは、指定されたヘッダー(「ユーザーエージェント」の最初の部分にさかのぼっている)とClientVersionと共にClientVersion(「ユーザーエージェント」の第2の部分に逆戻りしている「アポログラフィスクライアント版」)で、CatentNameをセットします.

グループトレース
各トレースは、操作とクエリでグループ化する必要があります.このような方法でアポロが機能するのは、以下のような形式です.
(演算名)
{クエリ}
操作名はGraphSQLの操作名で、クエリは空白を減らしたクエリ全体です.次のメソッドを使用します.
private static string MinimalWhitespace(string? requestQuery)
{
  return Regex.Replace((requestQuery ?? "").Trim().Replace("\r", "\n").Replace("\n", " "), @"\s{2,}", " ");
}

スレッド安全性
トレースロガーはスレッドセーフである必要があります.
private const int BATCH_THRESHOLD_SIZE = 2 * 1024 * 1024; 
...
lock (_tracesLock)
{
  var tracesAndStats = _traces.GetOrAdd($"# {(string.IsNullOrWhiteSpace(operationName) ? "-" : operationName)}\n{MinimalWhitespace(query)}", key => new TracesAndStats());
  tracesAndStats.Traces.Add(trace);

  // Trigger sending now if we exceed the batch threshold size (2mb)
  if (Serializer.Measure(CreateReport(_traces)).Length > BATCH_THRESHOLD_SIZE)
      ForceSendTrigger.Set();
}
GetOrAdd既存のTraceStaStatsがこのクエリのグループ化を取得します(最後のバッチが送信されてからトレースが存在する場合)または新しいものを作成します.このグループに現在のトレースを追加します.次に、Protobuf Serializer静的クラスを使用して、現在のキューバッチを送信した場合のレポート数を測定できます.それがバッチサイズしきい値(2 MB、アポロStudioによって推薦されるように)を超えているならば、我々はForceSendTrigger 遅延をオーバーライドする(この後の詳細).CreateReport は次のセクションで定義されます.

アポロスタジオ
The Send メソッドはすべてのキューをトレースし、HTTP経由でアポロStudioサーバーにディスパッチする責任があります.

スレッド安全性
トレースフィールドが変更されないようにするには、その値を新しい空の辞書で交換する必要があります.これを行う最良の方法(そして、原子的にそれをする唯一の方法)はInterlocked.Exchange . これは私たちが必要とするものです.1つの操作において、スレッド実行が真ん中で得られない(そして、結果的に爆発する).
IDictionary<string, TracesAndStats> traces;
lock (_tracesLock)
  traces = Interlocked.Exchange(ref _traces, new ConcurrentDictionary<string, TracesAndStats>());

レポートの作成
我々は最初に送信する前にすべてのトレースで完全なレポートを作成する必要があります.The CreateReport 前述のメソッドは、Report 静的ヘッダーを持つクラスです.次のように初期化します.
_reportHeader = new ReportHeader
{
  Hostname = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME") ?? Environment.MachineName,
  AgentVersion = "engineproxy 0.1.0",
  ServiceVersion = Assembly.GetExecutingAssembly().FullName,
  RuntimeVersion = $".NET Core {Environment.Version}",
  Uname = Environment.OSVersion.ToString(),
  GraphRef = graphRef,
  ExecutableSchemaId = ComputeSha256Hash(new SchemaPrinter(schema).Print())
};
  • ホスト名は、Azureアプリケーションサービスホスト名または現在のマシン名を使用します.
  • 私が同じ値に設定したエージェントバージョンは、クライアントが使用しているNODEJSバージョンを使用します.
  • サービスのバージョンは、完全なアセンブリの詳細として設定されます.
  • ランタイム版はバージョンです.使用中のネットコア(これは本当に"5.0 "のコア要素ではない).
  • UnameはOSのバージョンです.
  • グラフREFは、使用中のバージョンと組み合わせてアポロStudioに登録されたグラフ名として'@'で区切られた構成で提供されます.
  • 実行可能スキーマIDは、完全なスキーマ(これに使用されるGraphql .ユーティリティからのスキーマAPIユーティリティ)でSHA 256ハッシュを計算することによって決定されます.ハッシュは次のように計算されます.
  • private static string ComputeSha256Hash(string rawData)
    {
      using SHA256 sha256Hash = SHA256.Create();
      byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
      StringBuilder builder = new StringBuilder();
      foreach (var t in bytes)
        builder.Append(t.ToString("x2"));
      return builder.ToString();
    }
    
    これはストレートフォワードSHA 256ハッシュを実行し、結果のバイトを16進数に変換します.

    メッセージの準備
    それはかなりまっすぐ進むfollow the protobuf-net docs レポートをシリアル化するには、帯域幅の消費を減らし、パフォーマンスを向上させるためにストリームをgzipで送信する必要があります.
    byte[] bytes;
    await using (var memoryStream = new MemoryStream())
    {
      await using (var gzipStream = new GZipStream(memoryStream, CompressionLevel.Fastest))
        Serializer.Serialize(gzipStream, report);
      bytes = memoryStream.ToArray();
    }
    
    我々は、CPU負荷を最小化するためにここで最速の圧縮レベルを使用しています.gzipストリームはシステムの一部です.入出力圧縮し、下にあるストリームに書き込むときに圧縮可能なストリームをラップします.私は、我々がこれをどこにも格納する必要がないので、memoryreamを使っています(また、我々はそうしなければなりません).残りはかなり単純です.
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri("https://engine-report.apollodata.com/api/ingress/traces"));
    httpRequestMessage.Headers.Add("X-Api-Key", _apiKey);
    
    httpRequestMessage.Content = new ByteArrayContent(bytes)
    {
      Headers =
      {
        ContentType = new MediaTypeHeaderValue("application/protobuf"),
        ContentEncoding = {"gzip"}
      }
    };
    
    var client = _httpClientFactory.CreateClient();
    var response = await client.SendAsync(httpRequestMessage);
    if (!response.IsSuccessStatusCode)
      _logger.LogWarning("Failed to send traces to Apollo Studio with error code {errorCode}", response.StatusCode);
    
    注意は"application/protobuf "のメディアタイプと"GZIP "として設定されたコンテントエンコーディングです.「X - API -キー」ヘッダーは彼らが使用する標準的な認証メカニズムです、そして、URLは固定されます(他のサーバーは存在しません).

    定期的にバッチを送る
    それで、我々が一緒にこれを置いたあと、何も実際に何も実際に呼び出していないので、何も送られていませんSend メソッド.ここから派生したクラスを作成する必要がありますBackgroundService ExecuteAsyncをオーバーライドします.
    while (!stoppingToken.IsCancellationRequested)
    {
      using (var scope = _serviceProvider.CreateScope())
      {
        var graphQlTraceLogger = scope.ServiceProvider.GetRequiredService<IGraphQLTraceLogger>();
    
        // Send every 20 seconds or when forced due to size threshold reached
        var nextExecution = DateTime.Now.AddSeconds(20);
        await Task.WhenAny(graphQlTraceLogger.ForceSendTrigger.WaitAsync(), Task.Delay(Math.Max((int)(nextExecution - DateTime.Now).TotalMilliseconds, 0), stoppingToken));
    
        _logger.LogDebug("Sending queued traces...");
        await graphQlTraceLogger.Send();
      }
    }
    
    サービス自体がsingleton(したがって、我々のトレースロガーでなければなりません)として、我々は厳密にサービスプロバイダーで範囲をつくる必要はありません、しかし、バックグラウンドサービスの範囲からあなたのクラスを解決するのは良い実行です.また、ForceSendTrigger ここに入ります-それは20秒の待ち時間を越えて、すぐに送ることができます.このすべてを中心に行う理由は、複数の送信が一度に起こる可能性がある競合条件を避けることです.このアプローチは完全にその可能性を排除する.

    次へ
    この記事は現在、あなたが完全にグラフィカルなアプローチからアポロスタジオにトレースを送信するアプローチを提供します.NETの実装ですが、どのように設定するか、どのように表示されるかを見ていません.意志はそれらのポイントをカバーします.