TracedResource.java

/*
 * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.jersey.examples.opentracing;

import java.util.concurrent.Executors;

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.opentracing.OpenTracingFeature;
import org.glassfish.jersey.opentracing.OpenTracingUtils;
import org.glassfish.jersey.server.ManagedAsync;
import org.glassfish.jersey.server.Uri;

import io.opentracing.Span;


/**
 * OpenTracing example resource.
 * <p>
 * Jersey (with registered {@link OpenTracingFeature} will automatically
 * create and start span for each request ("root" span or "request" span) and a child span to be used in the resource method
 * ("resource" span). The root span is used for Jersey-level event logging (resource matching started, request filters applied,
 * etc). The resource span serves for application-level event logging purposes (used-defined). Both are automatically created
 * and also automatically finished.
 * <p>
 * Resource span is created right before the resource method invocation and finished right after resource method finishes. It
 * can be resolved by calling {@link OpenTracingUtils#getRequestSpan(ContainerRequestContext)}.
 * <p>
 * Application code can also create ad-hoc spans as child spans of the resource span. This can be achieved by calling one of the
 * convenience methods {@link OpenTracingUtils#getRequestChildSpan(ContainerRequestContext)}.
 * <p>
 * {@link ContainerRequestContext} can be obtained via injection.
 * <p>
 * All the ad-hoc created spans MUST be {@link Span#finish() finished} explicitly.
 *
 * @author Adam Lindenthal
 */
@Path(value = "/resource")
public class TracedResource {

    /**
     * Resource method with no explicit tracing.
     * <p>
     * One span (jersey-server) will be created and finished automatically.
     *
     * @return dummy response
     */
    @GET
    @Path("defaultTrace")
    public Response defaultTrace() {
        return Response.ok("foo").build();
    }

    /**
     * Resource method with explicit logging into resource span.
     *
     * @param context injected request context with resource-level span reference
     * @return dummy response
     * @throws InterruptedException if interrupted
     */
    @GET
    @Path("appLevelLogging")
    public Response appLevelLogging(@Context ContainerRequestContext context) throws InterruptedException {
        final Span resourceSpan = OpenTracingUtils
                .getRequestSpan(context)
                .orElseThrow(() -> new RuntimeException("Tracing has failed"));

        resourceSpan.log("Starting expensive operation.");
        // Do the business
        Thread.sleep(200);
        resourceSpan.log("Expensive operation finished.");
        resourceSpan.setTag("expensiveOperationSuccess", true);

        return Response.ok("SUCCESS").build();
    }

    /**
     * Similar as {@link #appLevelLogging(ContainerRequestContext)}, just with {@code POST} method.
     *
     * @param entity  posted entity
     * @param context injected context
     * @return dummy response
     */
    @POST
    @Path("appLevelPost")
    public Response tracePost(String entity, @Context ContainerRequestContext context) {
        final Span resourceSpan = OpenTracingUtils
                .getRequestSpan(context)
                .orElseThrow(() -> new RuntimeException("Tracing has failed"));

        resourceSpan.setTag("result", "42");
        resourceSpan.setBaggageItem("entity", entity);
        return Response.ok("Done!").build();
    }

    /**
     * Resource method with explicit child span creation.
     *
     * @param context injected request context with resource-level (parent) span reference
     * @return dummy response
     * @throws InterruptedException if interrupted
     */
    @GET
    @Path("childSpan")
    public Response childSpan(@Context ContainerRequestContext context) throws InterruptedException {
        final Span childSpan = OpenTracingUtils.getRequestChildSpan(context, "AppCreatedSpan");
        childSpan.log("Starting expensive operation.");
        // Do the business
        Thread.sleep(200);
        childSpan.log("Expensive operation finished.");
        childSpan.setTag("expensiveOperationSuccess", true);

        childSpan.finish();
        return Response.ok("SUCCESS").build();
    }


    /**
     * Resource method with explicit span creation and propagation into injected managed client.
     * <p>
     * Shows how to propagate the server-side span into managed client (or any common Jersey client).
     * This way, the client span will be child of the resource span.
     *
     * @param context injected context
     * @param wt      injected web target
     * @return dummy response
     */
    @GET
    @Path("managedClient")
    public Response traceWithManagedClient(@Context ContainerRequestContext context,
                                           @Uri("resource/appLevelPost") WebTarget wt) {
        final Span providedSpan = OpenTracingUtils
                .getRequestSpan(context)
                .orElseThrow(() -> new RuntimeException("Tracing failed"));

        providedSpan.log("Resource method started.");

        final Response response = wt.request()
                .property(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, providedSpan.context())  // <--- span propagation
                .post(Entity.text("Hello"));

        providedSpan.log("1st Response received from managed client");
        providedSpan.log("Firing 1st request from managed client");

        providedSpan.log("Creating child span");
        final Span childSpan = OpenTracingUtils.getRequestChildSpan(context, "jersey-resource-child-span");


        childSpan.log("Firing 2nd request from managed client");
        final Response response2 = wt.request()
                .property(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, childSpan.context())  // <--- span propagation
                .post(Entity.text("World"));
        childSpan.log("2st Response received from managed client");

        childSpan.finish();
        return Response.ok("Result: " + response.getStatus() + ", " + response2.getStatus()).build();
    }

    @GET
    @Path("async")
    @ManagedAsync
    public void traceWithAsync(@Suspended final AsyncResponse asyncResponse, @Context ContainerRequestContext context) {
        final Span span = OpenTracingUtils.getRequestSpan(context).orElseThrow(() -> new RuntimeException("tracing failed"));
        span.log("In the resource method.");
        Executors.newSingleThreadExecutor().submit(() -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                span.log("Interrupted");
                e.printStackTrace();
            }
            span.log("Resuming");
            asyncResponse.resume("OK");
        });
        span.log("Before exiting the resource method");
    }

    @GET
    @Path("error")
    public String failTrace(@Context ContainerRequestContext context) {
        throw new RuntimeException("Failing just for fun.");
    }
}