Ручное внедрение OpenTelemetry в Java-приложение

Материал из Документация Ключ-АСТРОМ

В этом пошаговом руководстве показано, как добавить возможность наблюдения в ваше Java-приложение с помощью библиотек ручного инструментария и инструментов, предоставляемых OpenTelemetry Java.

Получите данные для доступа к Ключ-АСТРОМ

Определение базового URL API

Подробную информацию о том, как собрать базовый URL-адрес конечной точки OTLP, см. в разделе Экспорт с помощью OTLP .

URL-адрес должен заканчиваться на /api/v2/otlp.

Получение токена доступа API

Токен доступа для сбора трассировок, логов и метрик можно создать в разделе Токены доступа .

Экспорт с помощью OTLP содержит более подробную информацию о формате и необходимых областях доступа.

Инструментируйте свое приложение

С автоматической настройкой SDK

1. Добавьте текущие версии следующих пакетов в конфигурацию вашего пакета (например, Maven, Gradle).

2. Настройте следующие переменные среды, чтобы задать предпочтение временности deltaи определить параметры экспорта, заменив [URL] и [TOKEN] значениями для базового URL-адреса и токена доступа.

OTEL_EXPORTER_OTLP_ENDPOINT=[URL]

OTEL_EXPORTER_OTLP_HEADERS="Authorization=Api-Token [TOKEN]"

OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

OTEL_RESOURCE_ATTRIBUTES="service.name=java-quickstart,service.version=1.0.1"

OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta

3. Добавьте следующие операторы импорта в класс запуска, который инициирует загрузку вашего приложения.

import io.opentelemetry.api.common.Attributes;

import io.opentelemetry.sdk.OpenTelemetrySdk;

import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;

import io.opentelemetry.sdk.resources.Resource;

import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender;

4. Добавьте метод initOpenTelemetry в класс запуска и вызовите его как можно раньше при запуске приложения. Это инициализирует OpenTelemetry для бэкенда Ключ-АСТРОМ и создаст поставщиков трассировщиков и счётчиков по умолчанию.

private static void initOpenTelemetry() {

    OpenTelemetrySdk sdk = AutoConfiguredOpenTelemetrySdk.builder().addResourceCustomizer((resource, properties) -> {

        Resource dtMetadata = Resource.empty();

        for (String name : new String[]{"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", "/var/lib/astromkey/enrichment/dt_metadata.properties"}) {

            try {

                Properties props = new Properties();

                props.load(name.startsWith("/var") ? new FileInputStream(name) : new FileInputStream(Files.readAllLines(Paths.get(name)).get(0)));

                dtMetadata = dtMetadata.merge(Resource.create(props.entrySet().stream()

                        .collect(Attributes::builder, (b, e) -> b.put(e.getKey().toString(), e.getValue().toString()), (b1, b2) -> b1.putAll(b2.build()))

                        .build())

                );

            } catch (IOException e) {

            }

        }

        return resource.merge(dtMetadata);

    }).build().getOpenTelemetrySdk();

    OpenTelemetryAppender.install(sdk);

}

Обогащение данных Ключ-АСТРОМ

Операции чтения файлов, анализирующие файлы dt_metadata в примере кода, пытаются прочитать файлы данных ЕдиногоАгента, чтобы обогатить запрос OTLP и гарантировать, что вся соответствующая информация о топологии доступна в Ключ-АСТРОМ.

Без автонастройки SDK

1. Добавьте текущие версии следующих пакетов в конфигурацию вашего пакета (например, Maven, Gradle).

2. Добавьте текущую версию opentelemetry-log4j-appender-2.17 в качестве библиотеки времени выполнения в конфигурацию вашего пакета (область для Maven runtime, для Gradle runtimeOnly).

3. Добавьте следующие операторы импорта в класс запуска, который инициирует загрузку вашего приложения.

import io.opentelemetry.api.common.AttributeKey;

import io.opentelemetry.api.common.Attributes;

import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;

import io.opentelemetry.context.propagation.ContextPropagators;

import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;

import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;

import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;

import io.opentelemetry.sdk.OpenTelemetrySdk;

import io.opentelemetry.sdk.resources.Resource;

import io.opentelemetry.sdk.trace.SdkTracerProvider;

import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;

import io.opentelemetry.sdk.trace.export.SpanExporter;

import io.opentelemetry.sdk.trace.samplers.Sampler;

import io.opentelemetry.sdk.metrics.SdkMeterProvider;

import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;

import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;

import io.opentelemetry.sdk.logs.SdkLoggerProvider;

import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;

import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender;

4. При приеме данных с использованием OTLP добавьте в класс запуска два поля для URL-адреса Ключ-АСТРОМ и токена доступа.

private static final String DT_API_URL = ""; // TODO: Provide your SaaS/Managed URL here

private static final String DT_API_TOKEN = ""; // TODO: Provide the OpenTelemetry-scoped access token here

5. Настройте имя службы с помощью переменной среды OTEL_SERVICE_NAME.

OTEL_SERVICE_NAME=java-quickstart

6. Добавьте метод initOpenTelemetry в класс инициализатора и вызовите его как можно раньше при запуске приложения. Это инициализирует OpenTelemetry для бэкенда Ключ-АСТРОМ и создаст поставщиков трассировщиков и счётчиков по умолчанию.

private static void initOpenTelemetry()

{

    // ===== GENERAL SETUP =====

    // Read service name from the environment variable OTEL_SERVICE_NAME, if present

    Resource serviceName = Optional.ofNullable(System.getenv("OTEL_SERVICE_NAME"))

        .map(n -> Attributes.of(AttributeKey.stringKey("service.name"), n))

        .map(Resource::create)

        .orElseGet(Resource::empty);

    // Parse the environment variable OTEL_RESOURCE_ATTRIBUTES into key-value pairs

    Resource envResourceAttributes = Resource.create(Stream.of(Optional.ofNullable(System.getenv("OTEL_RESOURCE_ATTRIBUTES")).orElse("").split(","))

        .filter(pair -> pair != null && pair.length() > 0 && pair.contains("="))

        .map(pair -> pair.split("="))

        .filter(pair -> pair.length == 2)

        .collect(Attributes::builder, (b, p) -> b.put(p[0], p[1]), (b1, b2) -> b1.putAll(b2.build()))

        .build()

    );

// Define enrichment files

String[] files = new String[] {

"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties",

"/var/lib/astromkey/enrichment/dt_metadata.properties",

"/var/lib/astromkey/enrichment/dt_host_metadata.properties"

};

    // Read host information from OneAgent files to enrich telemetry

    Resource dtMetadata = Resource.empty();

    for (String name : files) {

        try {

            Properties props = new Properties();

            props.load(name.startsWith("/var") ? new FileInputStream(name) : new FileInputStream(Files.readAllLines(Paths.get(name)).get(0)));

            dtMetadata = dtMetadata.merge(Resource.create(

                props.entrySet().stream()

                    .collect(Attributes::builder, (b, e) -> b.put(e.getKey().toString(), e.getValue().toString()), (b1, b2) -> b1.putAll(b2.build()))

                    .build()

            ));

        } catch (IOException e) {}

    }

    // ===== TRACING SETUP =====

    // Configure span exporter with the astromkey URL and the API token

    SpanExporter exporter = OtlpHttpSpanExporter.builder()

        .setEndpoint(DT_API_URL + "/v1/traces")

        .addHeader("Authorization", "Api-Token " + DT_API_TOKEN)

        .build();

    // Set up tracer provider with a batch processor and the span exporter

    SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()

        .setResource(Resource.getDefault().merge(envResourceAttributes).merge(serviceName).merge(dtMetadata))

        .setSampler(Sampler.alwaysOn())

        .addSpanProcessor(BatchSpanProcessor.builder(exporter).build())

        .build();

    // ===== METRIC SETUP =====

    // Configure metric exporter with the astromkey URL and the API token

    OtlpHttpMetricExporter metricExporter = OtlpHttpMetricExporter.builder()

        .setEndpoint(DT_API_URL + "/v1/metrics")

        .addHeader("Authorization", "Api-Token " + DT_API_TOKEN)

        .setAggregationTemporalitySelector(AggregationTemporalitySelector.deltaPreferred())

        .build();

    // Set up meter provider with a periodic reader and the metric exporter

    SdkMeterProvider meterProvider = SdkMeterProvider.builder()

        .setResource(Resource.getDefault().merge(envResourceAttributes).merge(serviceName).merge(dtMetadata))

        .registerMetricReader(PeriodicMetricReader.builder(metricExporter).build())

        .build();

    // ===== LOG SETUP =====

    // Configure log exporter with the astromkey URL and the API token

    OtlpHttpLogRecordExporter logExporter = OtlpHttpLogRecordExporter.builder()

        .setEndpoint(DT_API_URL + "/v1/logs")

        .addHeader("Authorization", "Api-Token " + DT_API_TOKEN)

        .build();

    // Set up log provider with the log exporter

    SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder()

        .setResource(Resource.getDefault().merge(envResourceAttributes).merge(serviceName).merge(dtMetadata))

        .addLogRecordProcessor(BatchLogRecordProcessor.builder(logExporter).build())

        .build();

    // ===== INITIALIZATION =====

    // Initialize OpenTelemetry with the tracer and meter providers

    OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()

        .setTracerProvider(sdkTracerProvider)

        .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))

        .setMeterProvider(meterProvider)

        .setLoggerProvider(sdkLoggerProvider)

        .buildAndRegisterGlobal();

    //

    Runtime.getRuntime().addShutdownHook(new Thread(sdkTracerProvider::close));

    OpenTelemetryAppender.install(sdk);

}

Обогащение данных Ключ-АСТРОМ

Операции чтения файлов, анализирующие файлы dt_metadata в примере кода, пытаются прочитать файлы данных ЕдиногоАгента, чтобы обогатить запрос OTLP и гарантировать, что вся соответствующая информация о топологии доступна в Ключ-АСТРОМ.

Добаление телеметрических сигналов вручную (необязательно)

Создание интервалов

1. Для создания новых интервалов нам сначала нужен объект трассировки.

Tracer tracer = GlobalOpenTelemetry

    .getTracerProvider()

    .tracerBuilder("my-tracer") //TODO Replace with the name of your tracer

    .build();

2. Теперь с помощью tracer, мы можем использовать конструктор интервалов для создания и запуска новых интервалов.

// Obtain and name new span from tracer

Span span = tracer.spanBuilder("Call to /myendpoint")

    .setSpanKind(SpanKind.CLIENT)

    .startSpan();

// Set demo span attributes using semantic naming

span.setAttribute("http.method", "GET");

span.setAttribute("net.protocol.version", "1.1");

// Set the span as current span and parent for future child spans

try (Scope scope = span.makeCurrent())

{

    // TODO your code goes here

}

finally

{

    // Completing the span

    span.end();

}

В приведенном выше коде мы:

  • Создали новый диапазон и назвали его «Call to /myendpoint».
  • Добавили два атрибута, следуя семантическому соглашению об именовании, специфичные для действия этого диапазона: информацию о методе HTTP и версии.
  • Использовали метод span makeCurrent(), чтобы отметить его как активный span и родительский для будущих span (пока span не завершится)
  • Вызвали метод span end() для завершения span (в блоке finally, чтобы гарантировать вызов метода)

Сборка метрик

1. Для создания новых инструментов измерения нам сначала нужен объект счетчика.

Meter meter = GlobalOpenTelemetry

    .getMeterProvider()

    .meterBuilder("my-meter") //TODO Replace with the name of your meter

    .build();

2. С помощью meter мы теперь можем создавать отдельные инструменты, например, счетчик.

LongCounter counter = meter.counterBuilder("request_counter")

    .setDescription("The number of requests we received")

    .setUnit()

    .build();

3. Теперь мы можем вызвать метод add() для записи новых значений counter с помощью нашего счетчика и сохранения дополнительных атрибутов (например, action.type).

Attributes attrs = Attributes.of(stringKey("action.type"), "create");

counter.add(1, attrs);

Вы также можете создать асинхронный датчик, для которого потребуется функция обратного вызова, которая будет вызываться OpenTelemetry при сборе данных.

В следующем примере при каждом вызове записывается доступная память, а также атрибут количества активных пользователей, полученный из вымышленного метода getUserCount().

meter.gaugeBuilder("free_memory")

    .setDescription("Available memory in bytes")

    .setUnit("bytes")

    .buildWithCallback(measurement -> {

        measurement.record(

            Runtime.getRuntime().freeMemory(),

            Attributes.of(stringKey("user_count"), getUserCount())

        );

    });

Подключение логов

Сначала вам необходимо настроить файл конфигурации Log4j 2 log4j.xml, чтобы включить в него приложение OpenTelemetry.

<?xml version="1.0" encoding="UTF-8"?>

<Configuration status="WARN" packages="io.opentelemetry.instrumentation.log4j.appender.v2_17">

  <Appenders>

    <Console name="Console" target="SYSTEM_OUT">

      <PatternLayout

          pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} trace_id: %X{trace_id} span_id: %X{span_id} trace_flags: %X{trace_flags} - %msg%n"/>

    </Console>

    <OpenTelemetry name="OpenTelemetryAppender"/>

  </Appenders>

  <Loggers>

    <Root>

      <AppenderRef ref="OpenTelemetryAppender" level="All"/>

      <AppenderRef ref="Console" level="All"/>

    </Root>

  </Loggers>

</Configuration>

В этой конфигурации мы добавили новую запись <OpenTelemetry> в раздел <Appenders>, а также запись <AppenderRef> в раздел <Loggers>.

С помощью вызова GlobalLoggerProvider, который мы ранее выполнили в разделе Настройка, этот аппендер настраивается для бэкэнда Ключ-АСТРОМ.

Обеспечение распространения контекста

Распространение контекста особенно важно, когда задействованы сетевые вызовы (например, REST).

Если вы используете автоматическое инструментирование и ваши сетевые библиотеки также им охватываются, то это будет автоматически реализовано библиотеками инструментирования. В противном случае ваш код должен это учитывать.

Извлечение контекста при получении запроса

В следующем примере мы предполагаем, что получили сетевой вызов через com.sun.net.httpserver.HttpExchange и определяем экземпляр TextMapGetter для извлечения контекстной информации из HTTP-заголовков. Затем мы передаем этот экземпляр в extract(), возвращая объект контекста, что позволяет нам продолжить предыдущую трассировку с нашими интервалами.

//The getter will be used for incoming requests

TextMapGetter<HttpExchange> getter =

    new TextMapGetter<>() {

        @Override

        public String get(HttpExchange carrier, String key) {

            if (carrier.getRequestHeaders().containsKey(key)) {

                return carrier.getRequestHeaders().get(key).get(0);

            }

            return null;

        }

        @Override

        public Iterable<String> keys(HttpExchange carrier) {

            return carrier.getRequestHeaders().keySet();

        }

    };

@Override

public void handle(HttpExchange httpExchange) {

    //Extract the SpanContext and other elements from the request

    Context extractedContext = GlobalOpenTelemetry.getPropagators().getTextMapPropagator()

        .extract(Context.current(), httpExchange, getter);

    try (Scope scope = extractedContext.makeCurrent()) {

        //This will automatically propagate context by creating child spans within the extracted context

        Span serverSpan = tracer.spanBuilder("my-server-span") //TODO Replace with the name of your span

            .setSpanKind(SpanKind.SERVER) //TODO Set the kind of your span

            .startSpan();

        serverSpan.setAttribute(SemanticAttributes.HTTP_METHOD, "GET"); //TODO Add attributes

        serverSpan.end();

    }

}

Внедрение контекста при отправке запросов

В следующем примере мы отправляем REST-запрос к другой службе и предоставляем наш существующий контекст как часть HTTP-заголовков нашего запроса.

Для этого мы определяем экземпляр TextMapSetter, который добавляет соответствующую информацию с помощью setRequestProperty(). После создания экземпляра REST-объекта мы передаем его вместе с контекстом и экземпляром сеттера в inject(), который добавит необходимые заголовки к запросу.

//The setter will be used for outgoing requests

TextMapSetter<HttpURLConnection> setter =

    (carrier, key, value) -> {

        assert carrier != null;

        // Insert the context as Header

        carrier.setRequestProperty(key, value);

    };

URL url = new URL("<URL>"); //TODO Replace with the URL of the service to be called

Span outGoing = tracer.spanBuilder("my-client-span") //TODO Replace with the name of your span

    .setSpanKind(SpanKind.CLIENT) //TODO Set the kind of your span

    .startSpan();

try (Scope scope = outGoing.makeCurrent()) {

    outGoing.setAttribute(SemanticAttributes.HTTP_METHOD, "GET"); //TODO Add attributes

    HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();

    // Inject the request with the *current*  Context, which contains our current span

    GlobalOpenTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), transportLayer, setter);

    // Make outgoing call

} finally {

    outGoing.end();

}

Настройка сбора данных в соответствии с требованиями конфиденциальности (необязательно)

Хотя Ключ-АСТРОМ автоматически собирает все атрибуты OpenTelemetry, в веб-интерфейсе Ключ-АСТРОМ сохраняются и отображаются только значения атрибутов, указанные в списке разрешенных. Это предотвращает случайное сохранение персональных данных, позволяя вам соблюдать требования к конфиденциальности и контролировать объем хранимых данных мониторинга.

Чтобы просматривать пользовательские атрибуты, необходимо сначала разрешить их использование в веб-интерфейсе Ключ-АСТРОМ.

Проверка загрузки данных в Ключ-АСТРОМ

После завершения инструментирования вашего приложения выполните несколько тестовых действий для создания и отправки демонстрационных трассировок, метрик и логов, а также проверьте, что они были правильно загружены в Ключ-АСТРОМ.

Чтобы сделать это для трассировок, перейдите в раздел Трассировки и выберите вкладку Распределенные трассировки. Если вы используете ЕдиныйАгент, выберите PurePaths .

Для просмотра метрик и логов перейдите в раздел Метрики или Журналы или Журналы и события.