More Related Content Similar to Implements OpenTelemetry Collector in DotNet (20) More from Yoshifumi Kawai (16) Implements OpenTelemetry Collector in DotNet2. 河合 宜文 / Kawai Yoshifumi / @neuecc
Cysharp, Inc.
Cygames
C#大統一理論
C#
4. Unified Realtime/API Engine for .NET Core and Unity
https://github.com/Cysharp/MagicOnion/
gRPCベースのC#特化ネットワークフレームワーク
.NET Core(Server) - Unity(Client)における
ハイパフォーマンスなAPI通信とリアルタイム通信を実現
gRPCなのにProtocol Buffersを使わない(スキーマ共有としてC#
コードそのものをサーバー/クライアントでシェアする)という
C#ファーストな設計(シリアライズ自体はMessagePackで行う)
5. public class TestService : ITestService
{
public async UnaryResult<int> Sum(int x, int y)
{
return x + y;
}
}
var client = MagicOnionClient.Create<ITestService>(channel);
var result = await client.Sum(100, 200);
public interface ITestService
{
UnaryResult<int> Sum(int x, int y);
}
Share Service Definition(and
Requst/Response Message) written in C#
Server Implementation
Client Implementation
12. public class OpenTelemetryCollectorFilter : MagicOnionFilterAttribute
{
public override async ValueTask Invoke(ServiceContext context)
{
try
{
// ここに前処理
await Next(context);
// ここに正常時処理
}
catch (Exception ex)
{
// ここに例外時処理
}
finally
{
// ここに後処理
}
}
}
MagicOnionではFilterとしてリクエスト前後のフック
ポイントを用意しているので、これに引っ掛ける
13. var tracer = context.ServiceLocator.GetService<ITracer>();
var sampler = context.ServiceLocator.GetService<ISampler>();
var spanBuilder = tracer.SpanBuilder(context.CallContext.Method, SpanKind.Server);
if (sampler != null)
{
spanBuilder.SetSampler(sampler);
}
using (spanBuilder.StartScopedSpan(out var span))
{
try
{
span.SetAttribute("component", "grpc");
span.SetAttribute("request.size", context.GetRawRequest().LongLength);
await Next(context);
// 次ページに続く
ITracerとISamplerを取得(これは
MagicOnionのDIより。取得方法は
なんでもいい)
名前をつけてSpanを作る。フレー
ムワークのルートなのでルートで
しょという扱い(分散トレーシン
グ対応する場合は親の設定などが
必要ですが今回は割愛)
名前はサービスメソッド名(gRPC
なのでTestService/Sumなどになる)
14. var tracer = context.ServiceLocator.GetService<ITracer>();
var sampler = context.ServiceLocator.GetService<ISampler>();
var spanBuilder = tracer.SpanBuilder(context.CallContext.Method, SpanKind.Server);
if (sampler != null)
{
spanBuilder.SetSampler(sampler);
}
using (spanBuilder.StartScopedSpan(out var span))
{
try
{
span.SetAttribute("component", "grpc");
span.SetAttribute("request.size", context.GetRawRequest().LongLength);
await Next(context);
// 次ページに続く
Trace処理は軽い処理ではないので、間引く
場合はSamplerを渡せば、それのルール
(1/10の確率、とか)に則って処理される
スコープで囲んでいる範囲が自分のSpanにぶら
下がる雰囲気になる。サービスフレームワーク
なのでリクエスト開始から完了までを囲む
SetAttributeでSpanに情報を付与。この場合
gRPCですよ、とかリクエストサイズは何
バイトとでしたよ、とか。命名は自由、に
みえて仕様である程度は決まってる。
15. using (spanBuilder.StartScopedSpan(out var span))
{
try
{
// 中略
await Next(context);
span.SetAttribute(“response.size”, context.GetRawResponse().LongLength);
span.SetAttribute(“status_code”, (long)context.CallContext.Status.StatusCode);
span.Status = ConvertStatus(context.CallContext.Status.StatusCode)
.WithDescription(context.CallContext.Status.Detail);
}
catch (Exception ex)
{
span.SetAttribute(“exception”, ex.ToString());
span.SetAttribute(“status_code”, (long)context.CallContext.Status.StatusCode);
span.Status = ConvertStatus(context.CallContext.Status.StatusCode)
.WithDescription(context.CallContext.Status.Detail);
}
}
ステータスコードの指定。このステータスコード
はgRPCと一緒(と、仕様に書いてある)で、
Ok, Unknown, NotFoundなどがある
16. MagicOnionではIMagicOnionLoggerとして構造化ログ
の口を用意しているので、これを用いる。
public interface IMagicOnionLogger
{
void BeginBuildServiceDefinition();
void EndBuildServiceDefinition(double elapsed);
void BeginInvokeMethod(ServiceContext context, byte[] request, Type type);
void EndInvokeMethod(ServiceContext context, byte[] response, Type type, double elapsed,
void BeginInvokeHubMethod(StreamingHubContext context, ArraySegment<byte> request, Type t
void EndInvokeHubMethod(StreamingHubContext context, int responseSize, Type type, double
void InvokeHubBroadcast(string groupName, int responseSize, int broadcastGroupCount);
void WriteToStream(ServiceContext context, byte[] writeData, Type type);
void ReadFromStream(ServiceContext context, byte[] readData, Type type, bool complete);
}
17. public class OpenTelemetryCollectorLogger : IMagicOnionLogger
{
static readonly IMeasureDouble UnaryElapsed = MeasureDouble.Create
("MagicOnion/measure/UnaryElapsed", "Unary API elapsed time.", "ms");
static readonly IMeasureLong UnaryResponseSize = MeasureLong.Create
("MagicOnion/measure/UnaryResponseSize", "Unary API response size.", "
static readonly IMeasureLong UnaryErrorCount = MeasureLong.Create
("MagicOnion/measure/UnaryErrorCount", "Unary API error Count.", "num"
static readonly TagKey MethodKey = TagKey.Create("MagicOnion/keys/Method");
readonly IStatsRecorder statsRecorder;
readonly ITagger tagger;
readonly ITagContext defaultTags;
public OpenTelemetryCollectorLogger(IStatsRecorder statsRecorder, ITagger tagger, ITagConte
{
this.statsRecorder = statsRecorder;
this.tagger = tagger;
this.defaultTags = defaultTags ?? TagContext.Empty;
}
// 実装は次ページ
}
メトリックに使うKeyの類は事前用意
使う型はIStatsRecorderとITagger
18. public class OpenTelemetryCollectorLogger : IMagicOnionLogger
{
ITagContext CreateTag(ServiceContext context)
{
return tagger.ToBuilder(defaultTags)
.Put(MethodKey, TagValue.Create(context.CallContext.Method)).Build();
}
public void EndInvokeMethod(ServiceContext context, byte[] response,
Type type, double elapsed, bool isErrorOrInterrupted)
{
var map = statsRecorder.NewMeasureMap();
map.Put(UnaryElapsed, elapsed);
map.Put(UnaryResponseSize, response.LongLength);
if (isErrorOrInterrupted)
{
map.Put(UnaryErrorCount, 1);
}
map.Record(CreateTag(context));
}
// 他の実装は省略
}
MeasureMapを作って、値をPut。キーは事
前定義しているもの。
最後にTagと共にRecord。
ダッシュボード作りにはTagの設計が肝
要なのですが本題ではないので割愛