다음을 통해 공유


분산 추적 계측 추가

이 문서는 .NET Core 2.1 이상 버전 .NET Framework 4.5 이상 버전✔️적용됩니다 ✔️.

.NET 애플리케이션은 System.Diagnostics.Activity API를 사용하여 계측하여 분산 추적 원격 분석을 생성할 수 있습니다. 일부 계측은 표준 .NET 라이브러리에 기본 제공되지만 코드를 보다 쉽게 진단할 수 있도록 더 추가할 수 있습니다. 이 자습서에서는 새 사용자 지정 분산 추적 계측을 추가합니다. 이 계측에서 생성된 원격 분석을 기록하는 방법에 대한 자세한 내용은 컬렉션 자습서 를 참조하세요.

필수 조건

초기 앱 만들기

먼저 OpenTelemetry를 사용하여 원격 분석을 수집하지만 아직 계측이 없는 샘플 앱을 만듭니다.

dotnet new console

.NET 5 이상을 대상으로 하는 애플리케이션에는 이미 필요한 분산 추적 API가 포함되어 있습니다. 이전 .NET 버전을 대상으로 하는 앱의 경우 System.Diagnostics.DiagnosticSource NuGet 패키지 버전 5를 추가합니다. netstandard를 대상으로 하는 라이브러리의 경우 여전히 지원되고 라이브러리에 필요한 API를 포함하는 패키지의 가장 오래된 버전을 참조하는 것이 좋습니다.

dotnet add package System.Diagnostics.DiagnosticSource

원격 분석을 수집하는 데 사용할 OpenTelemetryOpenTelemetry.Exporter.Console NuGet 패키지를 추가합니다.

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console

생성된 Program.cs 내용을 다음 예제 원본으로 바꿉니다.

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                .AddSource("Sample.DistributedTracing")
                .AddConsoleExporter()
                .Build();

            await DoSomeWork("banana", 8);
            Console.WriteLine("Example work done");
        }

        // All the functions below simulate doing some arbitrary work
        static async Task DoSomeWork(string foo, int bar)
        {
            await StepOne();
            await StepTwo();
        }

        static async Task StepOne()
        {
            await Task.Delay(500);
        }

        static async Task StepTwo()
        {
            await Task.Delay(1000);
        }
    }
}

앱에는 아직 계측이 없으므로 표시할 추적 정보가 없습니다.

> dotnet run
Example work done

모범 사례

앱 개발자만 이 예제의 OpenTelemetry와 같은 분산 추적 원격 분석을 수집하기 위한 선택적 타사 라이브러리를 참조해야 합니다. .NET 라이브러리 작성자는 .NET 런타임의 일부인 System.Diagnostics.DiagnosticSource의 API만 사용할 수 있습니다. 이렇게 하면 원격 분석 수집에 사용할 라이브러리 또는 공급업체에 대한 앱 개발자의 기본 설정에 관계없이 다양한 .NET 앱에서 라이브러리가 실행됩니다.

기본 계측 추가

애플리케이션 및 라이브러리는 System.Diagnostics.ActivitySourceSystem.Diagnostics.Activity 클래스를 사용하여 분산 추적 계측을 추가합니다.

활동 출처

먼저 ActivitySource의 인스턴스를 만듭니다. ActivitySource는 활동 개체를 만들고 시작하는 API를 제공합니다. Main()using System.Diagnostics; 위에 있는 정적 ActivitySource 변수를 using 지시문에 추가합니다.

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        private static ActivitySource source = new ActivitySource("Sample.DistributedTracing", "1.0.0");

        static async Task Main(string[] args)
        {
            // ...

모범 사례

  • ActivitySource를 한 번 만들고, 정적 변수에 저장하고, 필요한 경우 해당 인스턴스를 사용합니다. 각 라이브러리 또는 라이브러리 하위 구성 요소에서는 자체 원본을 만들 수 있으며 종종 만들어야 합니다. 앱 개발자가 소스에서 활동 관련 원격 분석을 독립적으로 활성화하고 비활성화할 수 있도록 하는 것을 기대하는 경우, 기존 소스를 다시 사용하는 대신 새 소스를 만드는 것이 좋습니다.

  • 생성자에 전달된 원본 이름은 다른 원본과의 충돌을 방지하기 위해 고유해야 합니다. 동일한 어셈블리 내에 여러 원본이 있는 경우 어셈블리 이름과 필요에 따라 구성 요소 이름을 포함하는 계층적 이름을 Microsoft.AspNetCore.Hosting사용합니다. 어셈블리가 두 번째 독립 어셈블리에서 코드 계측을 추가하는 경우 이름은 코드가 계측되는 어셈블리가 아니라 ActivitySource를 정의하는 어셈블리를 기반으로 해야 합니다.

  • 버전 매개 변수는 선택 사항입니다. 여러 버전의 라이브러리를 릴리스하고 계측된 원격 분석을 변경하는 경우 버전을 제공하는 것이 좋습니다.

비고

OpenTelemetry는 대체 용어 'Tracer' 및 'Span'을 사용합니다. .NET에서 'ActivitySource'는 Tracer의 구현이며 활동은 'Span'의 구현입니다. . NET의 활동 유형은 오래 전부터 OpenTelemetry 사양과 원래 .NET 명명이 .NET 에코시스템 및 .NET 애플리케이션 호환성 내에서 일관성을 위해 유지되었습니다.

활동

ActivitySource 개체를 사용하여 의미 있는 작업 단위를 중심으로 작업 개체를 시작하고 중지합니다. 여기에 표시된 코드로 DoSomeWork()를 업데이트합니다.

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        await StepOne();
        await StepTwo();
    }
}

이제 앱을 실행하면 기록 중인 새 활동이 표시됩니다.

> dotnet run
Activity.Id:          00-f443e487a4998c41a6fd6fe88bae644e-5b7253de08ed474f-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:36:51.4720202Z
Activity.Duration:    00:00:01.5025842
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 067f4bb5-a5a8-4898-a288-dec569d6dbef

비고

  • ActivitySource.StartActivity 는 작업을 만들고 동시에 시작합니다. 나열된 코드 패턴은 using 블록을 사용합니다. 이 블록은 실행된 후 생성된 Activity 객체를 자동으로 삭제합니다. 작업 개체를 삭제하면 코드에서 명시적으로 호출 Activity.Stop()할 필요가 없도록 해당 개체가 중지됩니다. 이를 통해 코딩 패턴이 간소화되었습니다.

  • ActivitySource.StartActivity 내부적으로 활동을 기록하는 수신기가 있는지 여부를 확인합니다. 등록된 수신기가 없거나 관심이 없는 수신기가 있는 경우 StartActivity()null을 반환하고 Activity 객체 생성을 피합니다. 코드 패턴을 자주 호출하는 함수에서 계속 사용할 수 있도록 성능 최적화입니다.

선택 사항: 태그 채우기

활동은 진단에 유용할 수 있는 작업의 매개 변수를 저장하는 데 일반적으로 사용되는 Tags라는 키-값 데이터를 지원합니다. DoSomeWork()를 포함하도록 업데이트합니다.

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        await StepTwo();
    }
}
> dotnet run
Activity.Id:          00-2b56072db8cb5a4496a4bfb69f46aa06-7bc4acda3b9cce4d-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:37:31.4949570Z
Activity.Duration:    00:00:01.5417719
Activity.TagObjects:
    foo: banana
    bar: 8
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 25bbc1c3-2de5-48d9-9333-062377fea49c

Example work done

모범 사례

  • 위에서 activity 설명한 대로 반환되는 ActivitySource.StartActivity 값은 null일 수 있습니다. C#의 null 병합 연산자 ?.activity가 null이 아닌 경우에만 Activity.SetTag를 호출하게 하는 편리한 약식입니다. 동작은 쓰기와 동일합니다.
if(activity != null)
{
    activity.SetTag("foo", foo);
}
  • OpenTelemetry는 일반적인 유형의 애플리케이션 작업을 나타내는 활동에서 태그를 설정 하기 위한 권장 규칙 집합을 제공합니다.

  • 고성능이 요구되는 함수를 Activity.IsAllDataRequested으로 계측하는 경우, 활동을 수신하는 코드 중 어떤 코드라도 태그와 같은 보조 정보를 읽을 의향이 있는지를 나타내는 힌트입니다. 리스너가 읽지 않는다면, 계측된 코드가 그것을 채우기 위해 CPU 주기를 사용할 필요가 없습니다. 간단히 하기 위해 이 샘플은 해당 최적화를 적용하지 않습니다.

선택 사항: 이벤트 추가

이벤트는 활동에 추가 진단 데이터의 임의 스트림을 연결할 수 있는 타임스탬프 메시지입니다. 활동에 일부 이벤트를 추가합니다.

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));
    }
}
> dotnet run
Activity.Id:          00-82cf6ea92661b84d9fd881731741d04e-33fff2835a03c041-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:39:10.6902609Z
Activity.Duration:    00:00:01.5147582
Activity.TagObjects:
    foo: banana
    bar: 8
Activity.Events:
    Part way there [3/18/2021 10:39:11 AM +00:00]
    Done now [3/18/2021 10:39:12 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: ea7f0fcb-3673-48e0-b6ce-e4af5a86ce4f

Example work done

모범 사례

  • 이벤트는 전송될 때까지 메모리 내 목록에 저장되므로 이 메커니즘은 적당한 수의 이벤트를 기록하는 데만 적합합니다. 대규모 또는 바인딩되지 않은 이벤트 볼륨의 경우 ILogger와 같이 이 작업에 초점을 맞춘 로깅 API를 사용하는 것이 좋습니다. 또한 ILogger는 앱 개발자가 분산 추적을 사용하도록 선택했는지 여부에 관계없이 로깅 정보를 사용할 수 있도록 합니다. ILogger는 활성 활동 ID를 자동으로 캡처할 수 있도록 지원하므로 해당 API를 통해 기록된 메시지는 분산 추적과 계속 상호 연결될 수 있습니다.

선택 사항: 상태 추가

OpenTelemetry를 사용하면 각 작업이 작업의 통과/실패 결과를 나타내는 상태를 보고할 수 있습니다. .NET에는 다음과 같은 용도로 강력한 형식의 API가 있습니다.

값은 ActivityStatusCode 둘 중 하나, Unset, OkError.로 표시됩니다.

DoSomeWork()를 업데이트하여 상태를 설정합니다.

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));

        // Pretend something went wrong
        activity?.SetStatus(ActivityStatusCode.Error, "Use this text give more information about the error");
    }
}

선택 사항: 추가 활동 추가

작업을 중첩하여 더 큰 작업 단위의 일부를 설명할 수 있습니다. 이는 신속하게 실행되지 않거나 특정 외부 종속성에서 발생하는 오류를 더 잘 지역화하기 위해 코드의 일부에 유용할 수 있습니다. 이 샘플은 모든 메서드에서 활동을 사용하지만, 이는 추가 코드가 최소화되었기 때문입니다. 더 크고 현실적인 프로젝트에서는 모든 메서드에서 작업을 사용하면 매우 자세한 추적이 생성되므로 권장되지 않습니다.

StepOne 및 StepTwo를 업데이트하여 다음과 같은 개별 단계를 중심으로 더 많은 추적을 추가합니다.

static async Task StepOne()
{
    using (Activity activity = source.StartActivity("StepOne"))
    {
        await Task.Delay(500);
    }
}

static async Task StepTwo()
{
    using (Activity activity = source.StartActivity("StepTwo"))
    {
        await Task.Delay(1000);
    }
}
> dotnet run
Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-39cac574e8fda44b-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepOne
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4278822Z
Activity.Duration:    00:00:00.5051364
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-4ccccb6efdc59546-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepTwo
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.9441095Z
Activity.Duration:    00:00:01.0052729
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4256627Z
Activity.Duration:    00:00:01.5286408
Activity.TagObjects:
    foo: banana
    bar: 8
    otel.status_code: ERROR
    otel.status_description: Use this text give more information about the error
Activity.Events:
    Part way there [3/18/2021 10:40:51 AM +00:00]
    Done now [3/18/2021 10:40:52 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Example work done

StepOne과 StepTwo 모두 SomeWork를 참조하는 ParentId를 포함합니다. 콘솔은 중첩된 작업 트리를 잘 시각화하지는 않지만 Zipkin 과 같은 많은 GUI 뷰어는 이를 Gantt 차트로 표시할 수 있습니다.

Zipkin Gantt 차트

선택 사항: ActivityKind

활동에는 Activity.Kind 속성이 있으며, 이 속성은 활동과 그 부모 및 자식 간의 관계를 설명합니다. 기본적으로 모든 새 활동은 원격 부모 또는 자식이 없는 애플리케이션 내에서 내부 작업인 활동에 적합하도록 설정 Internal됩니다. 에 대한 종류 매개 변수 ActivitySource.StartActivity를 사용하여 다른 종류를 설정할 수 있습니다. 다른 옵션은 을 참조하세요 System.Diagnostics.ActivityKind.

일괄 처리 시스템에서 작업이 수행될 때, 단일 활동은 여러 다른 요청을 동시에 처리하는 작업을 나타낼 수 있으며, 각 요청은 고유의 추적 ID를 가집니다. 활동은 단일 부모만 가질 수 있도록 제한되지만, System.Diagnostics.ActivityLink을(를) 사용하여 추가적인 추적 ID와 연결할 수 있습니다. 각 ActivityLink는 연결된 활동에 대한 ID 정보를 저장하는 ActivityContext로 채워집니다. ActivityContext는 현재 진행 중인 활동 개체에서 Activity.Context을 사용하여 검색할 수 있으며, 직렬화된 ID 정보 ActivityContext.Parse(String, String)를 통해 구문 분석할 수도 있습니다.

void DoBatchWork(ActivityContext[] requestContexts)
{
    // Assume each context in requestContexts encodes the trace-id that was sent with a request
    using(Activity activity = s_source.StartActivity(name: "BigBatchOfWork",
                                                     kind: ActivityKind.Internal,
                                                     parentContext: default,
                                                     links: requestContexts.Select(ctx => new ActivityLink(ctx))
    {
        // do the batch of work here
    }
}

요청 시 추가할 수 있는 이벤트 및 태그와 달리 링크는 중에 StartActivity() 추가되어야 하며 나중에 변경할 수 없습니다.

중요합니다

OpenTelemetry 사양에 따라 링크 수에 대해 제안된 제한은 128입니다. 그러나 이 제한은 적용되지 않는다는 점에 유의해야 합니다.