This topic describes how to use open source OpenTelemetry SDK for Java to manually instrument your application and monitor your application.

Prerequisites

The Application Real-Time Monitoring Service (ARMS) agent V2.7.1.3 or later for public preview is used. To update the ARMS agent, contact the official DingTalk account for ARMS (account ID: arms160804).

Background information

In cloud-native environments, OpenTelemetry that is developed based on OpenTracing provides SDKs for various programming languages. OpenTelemetry provides standardized APIs and unifies the data format and serves as a standard observability framework.

The ARMS agent for Java V2.7.1.3 or later supports OpenTelemetry SDK for Java. If you have manually instrumented your application by using OpenTelemetry SDK for Java, no changes are required. ARMS uses a relevant span as an independent method or an internal method stack to monitor your application. If your application has not been instrumented, manually instrument your application by adding dependencies on OpenTelemetry SDK for Java to your application.

Manual instrumentation

If your application has not been instrumented, add the following Maven dependencies to manually instrument your application. For more information, see Manual Instrumentation.

<dependencies>   
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-api</artifactId>
    </dependency>
    <dependency>
          <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk-trace</artifactId>
    </dependency>
    <dependency>
          <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk</artifactId>
    </dependency>
    <dependency>
          <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-logging</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-bom</artifactId>
      <version>1.2.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Compatibility between ARMS and OpenTelemetry-based instrumentation

ARMS is compatible with the following concepts involved in OpenTelemetry-based instrumentation. For more information about other concepts introduced by OpenTelemetry, see opentelemetry-specification.

  • Span: a specific operation in a request, such as a remote call or an internal method call.
  • SpanContext: the context of a span. A SpanContext is associated with a specific operation in a request.
  • Attribute: an additional attribute field of a span, which is used to record key information.

Spans in OpenTelemetry can be divided into the following types:

  • Entry span: creates a SpanContext. Examples: Server and Consumer spans.
    Note For an entry span, the methods used for instrumentation in ARMS are often built in the framework, and the SpanContext already exists before your application is manually instrumented. ARMS records the entry span in OpenTelemetry as an internal span. For a method where no instrumentation is implemented in ARMS, such as a method for an asynchronous call or a custom RPC framework, the entry span in OpenTelemetry remains unchanged. ARMS performs aggregation on the client to generate relevant statistics.
  • Internal span: reuses an existing SpanContext. An internal span is recorded as an internal method stack.
  • Exit span: passes a SpanContext. Examples: Client and Producer spans.

ARMS is compatible with the entry spans and internal spans of OpenTelemetry. However, ARMS does not allow exit spans to pass through SpanContexts based on the OpenTelemetry standard. In ARMS, exit spans must pass through SpanContexts based on the formats provided by ARMS.

The following code provides an example on how to create spans by using OpenTelemetry SDK for Java:

package com.alibaba.arms.brightroar.console.controller;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/ot")
public class OpenTelemetryController {

    private Tracer tracer;

    private ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();

    @PostConstruct
    public void init() {
        SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(new LoggingSpanExporter()).build())
                .build();

        OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
                .setTracerProvider(sdkTracerProvider)
                .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
                .buildAndRegisterGlobal();

        tracer = openTelemetry.getTracer("manual-sdk", "1.0.0");

        ses.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Span span = tracer.spanBuilder("schedule")
                        .setAttribute("schedule.time", System.currentTimeMillis())
                        .startSpan();
                try (Scope scope = span.makeCurrent()) {
                    System.out.println("scheduled!");
                    Thread.sleep(500L);
                    span.setAttribute("schedule.success", true);
                } catch (Throwable t) {
                    span.setStatus(StatusCode.ERROR, t.getMessage());
                } finally {
                    span.end();
                }
            }
        }, 10, 30, TimeUnit.SECONDS);
    }

    @ResponseBody
    @RequestMapping("/parent")
    public String parent() {
        Span span = tracer.spanBuilder("parent").setSpanKind(SpanKind.SERVER).startSpan();
        try (Scope scope = span.makeCurrent()) {
            child();
            span.setAttribute("http.method", "GET");
            span.setAttribute("http.uri", "/parent");
        } finally {
            span.end();
        }
        return "parent";
    }

    private void child() {
        Span span = tracer.spanBuilder("child").startSpan();
        try (Scope scope = span.makeCurrent()) {
            span.addEvent("Sleep Start");
            Thread.sleep(1000);
            Attributes attr = Attributes.of(AttributeKey.longKey("cost"), 1000L);
            span.addEvent("Sleep End", attr);
        } catch (Throwable e) {
            span.setStatus(StatusCode.ERROR, e.getMessage());
        } finally {
            span.end();
        }
    }

}

The sample code can be used to create the following three spans:

  • parent: a parent span, which serves as the entry span of an HTTP request based on the OpenTelemetry standard. However, ARMS already creates a SpanContext in the embedded code of Apache Tomcat. Therefore, ARMS records the parent span as an internal method on the ARMS method stack.
  • child: the internal span of the parent span. The internal span is recorded as an internal method on the ARMS method stack.
  • schedule: an entry span for an independent thread. By default, ARMS does not create SpanContexts for schedule spans. Therefore, ARMS records a schedule span as a custom method and generates relevant statistics.

View the information about parent and child spans in the ARMS console

On the Method Stack tab in the Details panel of the /ot/parent HTTP method, you can view the information about parent and child spans. For more information, see Trace query.

View the information about a schedule span in the ARMS console

You can view the information about a schedule span on the following pages in the ARMS console:

  • You can view the statistics on the schedule span on the Application Overview page in the ARMS console. For more information, see Application overview. Overview page
  • You can view the details of the schedule span on the Interface Invocation page. For more information, see API monitoring. Interface Invocation page
  • You can view the schedule span on the Traces tab. For more information, see Trace query.

    On the Traces tab, click the Zoom In icon in the Details column to view the details of the schedule span.

    ARMS displays the attributes of an OpenTelemetry span as tags. To view the attributes of a span, move the pointer over Tags next to the span name.