We use cookies or similar technologies to personalize your online experience and tailor marketing to you. Many of our product features require cookies to function properly. Your use of this site and online product constitutes your consent to these personalization technologies. Read our Privacy Policy to find out more.

X

Beeline for Java

The Java Beeline for Honeycomb is a quick and easy way to instrument your Java Spring Boot v2 application. It automatically instruments HTTP requests and also provides automatic tracing out of the box. While this is a great way to get general insights about your app as quickly as possible, as you forge ahead on your observability journey you may find you’d like to add new events or tracing spans to add more details specific to your app. The Java Beeline provides simple interfaces for adding both.

If you do not have a Spring Boot v2 application, you can still take advantage of the Java Beeline to add tracing to your app. Only a small amount of additional setup code is required.

If you’d like to see more options in the Java Beeline, please file an issue or vote up one already filed! You can also contact us at support@honeycomb.io.

If you prefer more control over your application’s instrumentation, check out our Java SDK.

Requirements

To take advantage of automatic HTTP instrumentation and tracing support, you’ll need a Java Spring Boot v2 app. Otherwise, any Java application will suffice. You’ll just have a bit more configuration code to get traces. You wil also need a Honeycomb API key.

You can find your API key on your Team Settings page. If you don’t have a API key yet, sign up for a Honeycomb trial.

Quick installation with Java Spring Boot v2

To install the Java Beeline for your Spring Boot application:

  1. The Java Beeline is available from Maven Central Repository. To get started, add this to your Maven build’s pom.xml:

    <dependency>
      <groupId>io.honeycomb.beeline</groupId>
      <artifactId>beeline-spring-boot-starter</artifactId>
      <version>1.1.0</version>
    </dependency>
    

    or in Gradle:

    implementation 'io.honeycomb.beeline:beeline-spring-boot-starter:1.1.0'
    
  2. In your Spring app, add the following configuration to your application.properties file in src/main/resources:

    # (Required) Give your application a name to identify the origin of your Honeycomb Events/Spans
    honeycomb.beeline.service-name           :<service_name>
    
    # (Required) Dataset to send the Events/Spans to
    honeycomb.beeline.dataset                :<dataset_name>
    
    # (Required) Your honeycomb account API key
    honeycomb.beeline.write-key              :<Honeycomb API key>
    
    # (Optional) Sets the global sample rate of traces.
    honeycomb.beeline.sample-rate            :1
    
    # (Optional) Allows the entire Beeline AutoConfiguration to be disabled completely.
    honeycomb.beeline.enabled                :true
    
    # (Optional) Allows to switch off automatic instrumentation of the RestTemplate.
    honeycomb.beeline.rest-template.enabled  :true
    
    # (Optional) Allows overriding of the beeline's servlet filter precedence
    # - in case you want your own filters to come before it.
    #honeycomb.beeline.filter-order:
    
    # (Optional) Enables a form of debug logging of responses from Honeycomb's server
    honeycomb.beeline.log-honeycomb-responses:true
    
    # (Optional) List of paths that should be subject to tracing (ant path pattern)
    honeycomb.beeline.include-path-patterns  :/**
    
    # (Optional) List of paths that should not be subject to tracing (ant path pattern)
    honeycomb.beeline.exclude-path-patterns  :/exclude-this-path
    
    # (Optional) For testing you can override Honeycomb's hostname and redirect Events/Spans.
    #honeycomb.beeline.api-host=http://localhost:8089
    

    You are currently logged in to the team, so we have populated the write key here to the first write key for that team.

    After you build and run your app with this configuration, the beeline will automatically start sending events to Honeycomb.

  3. Augment the data with interesting information from your app with beeline.getActiveSpan().addField so that you can see rich information about your app in Honeycomb.

    import io.honeycomb.beeline.tracing.Beeline;
    
    @Controller
    class OwnerController {
    
        private final Beeline beeline;
        private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
        private final OwnerRepository owners;
    
        public OwnerController(OwnerRepository clinicService, Beeline beeline) {
            this.owners = clinicService;
            this.beeline = beeline;
        }
    
        @GetMapping("/owners/new")
        public String initCreationForm(Map<String, Object> model) {
            Owner owner = new Owner();
            this.beeline.getActiveSpan().addField("new_owner", owner);
            model.put("owner", owner);
            return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
        }
    }
  4. Add child spans with the @ChildSpan annotation and turn a series of events into a trace. Important: @ChildSpan will only work on methods called in classes other than the root controller instrumented by the Beeline. (This is due to Spring AOP using a proxy mechanism).

    import io.honeycomb.beeline.spring.beans.aspects.ChildSpan;
    
    @ChildSpan("find an owner")
    @GetMapping("/owners/find")
    public String initFindForm(Map<String, Object> model) {
        model.put("owner", new Owner());
        return "owners/findOwners";
    }

Tracing Across Services with Spring Boot v2 Beeline

The Beeline includes a RestTemplateInterceptor which can be used to propagate tracing information across HTTP requests to ensure the requests can be tied together in queries in the Honeycomb UI. If a Spring Boot v2 app which has been instrumented with the Java Beeline uses RestTemplate to call another Beeline instrumented service, the X-Honeycomb-Trace header will be set accordingly and the app on the other side will understand this context to set the tracing information correctly.

The Interceptor is on by default when Beeline auto-instrumentation has been added to a Spring Boot v2 app and can be turned off using the honeycomb.beeline.rest-template.enabled setting in application.properties.

Testing with Spring Boot v2 Beeline

When testing a Spring Boot controller class that uses the beeline, mock the beeline using Mockito’s @MockBean annotation with Mockito’s Answers.RETURNS_DEEP_STUBS option:

@RunWith(SpringRunner.class)
@WebMvcTest(OwnerController.class)
public class OwnerControllerTests {

    private static final int TEST_OWNER_ID = 1;

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private OwnerRepository owners;

    @MockBean(answer = Answers.RETURNS_DEEP_STUBS)
    private Beeline beeline;

    private Owner george;

    @Before
    public void setup() {
        ...
    }

Installation with any Java Application (Simple)

  1. The Java Beeline is available from Maven Central Repository. To get started, add this to your Maven build’s pom.xml:

    <dependency>
      <groupId>io.honeycomb.beeline</groupId>
      <artifactId>beeline-core</artifactId>
      <version>1.1.0</version>
    </dependency>
    

    or in Gradle:

    implementation 'io.honeycomb.beeline:beeline-core:1.1.0'
    
  2. In your Java app, import the DefaultBeeline by supplying getInstance with your dataset name, service name, and API key:

package com.example.MyApp;

import io.honeycomb.beeline.DefaultBeeline;

/// ...

public static void main( String[] args ) {
    DefaultBeeline beeline = DefaultBeeline.getInstance("test-dataset", "my-app", "WRITE_KEY");

    // ...

    // Call close at program termination to ensure all pending
    // spans are sent.
    beeline.close();
}

Note: If your servers are behind an http proxy, you will need to initialize the beeline with additional transport options:

DefaultBeeline beeline = DefaultBeeline.getInstance("test-dataset", "my-app", "WRITE_KEY",
    "https://api.honeycomb.io", LibHoney.transportOptions().setProxy(HttpHost.create("https://myproxy.example.com")).build());
  1. Start adding trace spans at interesting points in your code. For example, at the start of an incoming request, or the beginning of an asynchronous job. Start more spans as needed, such as when doing a third party API call or an expensive computation. You can add useful context to spans with addField.
// startSpan will begin a trace if there is no active trace.
// Otherwise, it will create child span of the currently active span.
Span rootSpan = beeline.startSpan("request start");

// Add context by calling addField on spans.
// This is useful for describing what's happening
// in your system.
rootSpan.addField("user_id", userId);
rootSpan.addField("endpoint", requestEndpoint);

// You can also call addField directly on the DefaultBeeline
// to add fields to the current 'active' span.
beeline.addField("success", true);

// When needed, add more spans:
Span callDataAPISpan = beeline.startSpan("call data API");

// These child spans can be annotated with
// yet more fields
callDataAPISpan.addField("user_id", userId);
callDataAPISpan.addField("action", "store result");

// Once the work you're wrapping with a span is finished,
// send it to the Honeycomb API by using sendActiveSpan()
// or close() on the span itself.
callDataAPI(); // do the actual work
callDataAPISpan.close(); // finish timing the work

// Don't forget to send the root too.
rootSpan.close();

// Once all spans have been closed end the trace.
beeline.getBeeline().getTracer().endTrace();

Installation with any Java Application (Advanced)

  1. The Java Beeline is available from Maven Central Repository. To get started, add this to your Maven build’s pom.xml:

    <dependency>
      <groupId>io.honeycomb.beeline</groupId>
      <artifactId>beeline-core</artifactId>
      <version>1.1.0</version>
    </dependency>
    

    or in Gradle:

    implementation 'io.honeycomb.beeline:beeline-core:1.1.0'
    
  2. In your Java app, first set up the Beeline and its collaborator classes:

    package com.mycompany.services.Honeycomb;
    
    import io.honeycomb.beeline.tracing.Beeline;
    import io.honeycomb.beeline.tracing.Span;
    import io.honeycomb.beeline.tracing.SpanBuilderFactory;
    import io.honeycomb.beeline.tracing.SpanPostProcessor;
    import io.honeycomb.beeline.tracing.Tracer;
    import io.honeycomb.beeline.tracing.Tracing;
    import io.honeycomb.beeline.tracing.sampling.Sampling;
    import io.honeycomb.libhoney.HoneyClient;
    import io.honeycomb.libhoney.LibHoney;
    
    public class TracerSpans {
        private static final String WRITE_KEY = "test-write-key";
        private static final String DATASET = "test-dataset";
    
        private static final HoneyClient client;
        private static final Beeline beeline;
    
        static {
            client                          = LibHoney.create(LibHoney.options().setDataset(DATASET).setWriteKey(WRITE_KEY).build());
            SpanPostProcessor postProcessor = Tracing.createSpanProcessor(client, Sampling.alwaysSampler());
            SpanBuilderFactory factory      = Tracing.createSpanBuilderFactory(postProcessor, Sampling.alwaysSampler());
            Tracer tracer                   = Tracing.createTracer(factory);
            beeline                         = Tracing.createBeeline(tracer, factory);
        }
    }

    You are currently logged in to the team, so we have populated the write key here to the first write key for that team.

  3. Augment the data with interesting information from your app so that you can see rich information about your app in Honeycomb. Use the spanBuilderFactory to create a root span. Add custom fields to that span with beeline.getActiveSpan().addField().

    import io.honeycomb.beeline.tracing.Beeline;
    import io.honeycomb.beeline.tracing.Span;
    import io.honeycomb.beeline.tracing.SpanBuilderFactory;
    import io.honeycomb.beeline.tracing.SpanPostProcessor;
    import io.honeycomb.beeline.tracing.Tracer;
    import io.honeycomb.beeline.tracing.Tracing;
    import io.honeycomb.beeline.tracing.propagation.HttpHeaderV1PropagationCodec;
    import io.honeycomb.beeline.tracing.propagation.Propagation;
    import io.honeycomb.beeline.tracing.propagation.PropagationContext;
    import io.honeycomb.beeline.tracing.sampling.Sampling;
    import io.honeycomb.libhoney.HoneyClient;
    import io.honeycomb.libhoney.LibHoney;
    
    public static void main(String... args) {
            TracerSpans example = new TracerSpans();
        try {
            HttpRequest request = new HttpRequest();
            startTrace(request);
            example.acceptRequest(request);
        } finally {
            beeline.getTracer().endTrace();
            client.close(); // close to flush events and release its thread pool
        }
    }
    
    private DatabaseService db = new DatabaseService();
    
    private static void startTrace(HttpRequest request) {
        String headerValue = request.getHeader(HttpHeaderV1PropagationCodec.HONEYCOMB_TRACE_HEADER);
        PropagationContext context = Propagation.honeycombHeaderV1().decode(headerValue);
    
        Span rootSpan = beeline.getSpanBuilderFactory().createBuilder()
            .setSpanName("get-customer-data")
            .setServiceName("customer-db-traced")
            .setParentContext(context)
            .build();
        beeline.getTracer().startTrace(rootSpan);
    }
    
    public void acceptRequest(HttpRequest request) {
        Span span = beeline.getActiveSpan();
        try {
            db.queryDb(request.getParameter("customer-id"));
            span.addField("result", "OK");
        } catch (Exception e) {
            span.addField("result", "Bad Request")
                .addField("exception-message", e.getMessage());
        }
    }

    In this example, when the parent context is set in SpanBuilderFactory, the traceId in the context becomes the id for the trace initialized with startTrace. The spanId in the context becomes the parentSpanId of the next span in the trace. Effectively startTrace is more like “continue trace” when there is parent context involved. This is how you can achieve cross-service traces using the Java Beeline.

  4. Add additional spans with beeline.startChildSpan() and turn a series of spans into a trace:

    public static class DatabaseService {
        public void queryDb(String id) {
            try (Span childSpan = beeline.startChildSpan("customer-db-query")) {
                String data = getCustomerDataById(id);
                childSpan.addField("customer-data", data);
            }
    
        }
    
        public String getCustomerDataById(String id) {
            return "customer-0123";
        }
    }

Instrumenting HTTP clients and servers

The Beeline Spring Boot Starter auto-instruments the HTTP server and client inside your Spring Boot application. If you are not using Spring Boot you can still instrument your HTTP server and client code using Beeline Core’s HTTP instrumentation utilities.

Instrumenting HTTP clients

Beeline Core contains high-level utility classes to help you instrument the HTTP client of your choice. Using this pattern will open and close a child span for each HTTP client call and add a standard set of fields to this span based on the data in the HTTP request and response. The class that does the work is HttpClientPropagator. You pass implementations to it of HttpClientRequestAdapter and HttpClientResponseAdapter which adapt the HTTP request and response abstractions used in your HTTP client library. This allows the HttpClientPropagator to read and write to the request and response so the client call can be traced. The general pattern is as follows:

public class HttpClientCallInterceptor {
    // Initialised in constructor
    private final HttpClientPropagator clientPropagator;

    ///...

    public HttpResponse instrumentedClientCall(final HttpRequest actualHttpRequest) throws Exception {
            // Adapt your actual HTTP request so it can be read by the HttpClientPropagator
            HttpClientRequestAdapter adaptedHttpRequest = new MyHttpClientRequestAdapter(actualHttpRequest);

            // These two variables are so we can reference the adapted response and error (if they exist) when closing the child span using #endPropagation
            HttpClientResponseAdapter adaptedHttpResponse = null;
            Throwable error = null;

            // Call #startPropagation just before you execute the actual HTTP request; this will start the child span and add standard HTTP span fields.
            Span span = clientPropagator.startPropagation(adaptedHttpRequest);

            try {
                // Execute the actual HTTP request
                HttpResponse actualHttpResponse = makeClientCall(actualHttpRequest);

                // Adapt your actual HTTP response so it can be read by the HttpClientPropagator
                adaptedHttpResponse = new MyHttpClientResponseAdapter(actualHttpResponse);

                //Return the actual HTTP response for processing by your code
                return actualHttpResponse;
            } catch (Exception ex) {
                // Assign the error if present
                error = ex;
                throw ex;
            } finally {
                // Call #endPropagation when request has finished. This closes the child span and adds the standard HTTP span fields based on whether the error/response are non-null.
                clientPropagator.endPropagation(adaptedHttpResponse, error, span);
            }
    }

    private HttpResponse makeClientCall(HttpRequest httpRequest) {
       	// Make the actual HTTP client call
        /// ...
    }

    class MyHttpClientRequestAdapter implements HttpClientRequestAdapter {

            private final HttpRequest httpRequest;

            public MyHttpClientRequestAdapter(HttpRequest httpRequest) {
                this.httpRequest = httpRequest;
            }

        //implement methods
        ///...
    }

    class MyHttpClientResponseAdapter implements HttpClientResponseAdapter {

            private final HttpResponse httpResponse;

            public MyHttpClientResponseAdapter(HttpResponse httpResponse) {
                this.httpResponse = httpResponse;
            }

        //implement methods
        ///...
    }
}

It is likely that you would implement this in an interceptor that is added to your HTTP client. An example of this is found in the instrumentation Beeline adds to Spring’s RestTemplate interceptor. This also contains an example of how to adapt an HTTP client request and response.

Instrumenting HTTP servers

Beeline Core contains high-level utility classes to help you instrument the HTTP server of your choice. Using this pattern will open and close a span for each HTTP request received by the server and add a standard set of fields to this span based on the data in the HTTP request and response. If the HTTP request is being made by a service that is also using Beeline tracing, then the new span will be the child of the HTTP client’s request span. The class that does the work is HttpServerPropagator. You pass implementations to it of HttpServerRequestAdapter and HttpServerResponseAdapter which adapt your framework’s HTTP request and response abstractions. This allows the HttpServerPropagator to read and write to the request/response so the request handling can be traced. The general pattern is as follows:

// Inside your HTTP server handler class, e.g. a Servlet Filter
public class ServerHandler {

    // Initialised in constructor
    private final HttpServerPropagator serverPropagator;

    ///...

    public HttpResponse instrumentedRequest(final HttpRequest actualHttpRequest) throws Exception {
            // Adapt the actual received HTTP request so it can be read by the HttpServerPropagator
            HttpServerRequestAdapter adaptedHttpRequest = new MyHttpServerRequestAdapter(actualHttpRequest);

            // These two variables are so we can reference the adapted response and error (if they exist) when closing the child span using #endPropagation
            HttpServerResponseAdapter adaptedHttpResponse = null;
            Throwable error = null;

            // Call #startPropagation before you start handling the actual HTTP request; this will start the child span and add standard HTTP span fields.
            Span span = serverPropagator.startPropagation(adaptedHttpRequest);

            try {
                // Handle the HTTP request
                HttpResponse actualHttpResponse = handleRequest(actualHttpRequest);

                // Adapt the actual HTTP response that you will return to the client so it can be read by the HttpServerPropagator
                adaptedHttpResponse = new MyHttpServerResponseAdapter(actualHttpResponse);

                //Return the actual HTTP response to the client
                return actualHttpResponse;
            } catch (Exception ex) {
                // Assign the error if an exception occurred when your code was processing the HTTP request
                error = ex;
                throw ex;
            } finally {
                // Call #endPropagation when the server has finished processing the request. This closes the child span and adds the standard HTTP span fields based on whether the error/response are non-null.
                serverPropagator.endPropagation(adaptedHttpResponse, error, span);
            }
        }

    private HttpResponse handleRequest(HttpRequest actualHttpRequest) {
        ///... handle the actual HTTP request
    }

    class MyHttpServerResponseAdapter implements HttpServerResponseAdapter {

         private final HttpResponse httpResponse;

         public MyHttpServerResponseAdapter(HttpResponse httpResponse) {
             this.httpResponse = httpResponse;
         }
        //implement methods
        ///...
    }

    class MyHttpServerRequestAdapter implements HttpServerRequestAdapter {

        private final HttpRequest httpRequest;

        public MyHttpServerRequestAdapter(HttpRequest httpRequest) {
            this.httpRequest = httpRequest;
        }
        //implement methods
        ///...
    }
}

Beeline Core already uses this approach to add server instrumentation via a Java Servlet Filter. This also contains an example of how to adapt an HTTP server request and response.

Tracing Servlets outside of Spring Boot

If you are not using Spring Boot 2, but you are using Java Servlets, you can still use the Beeline Core’s io.honeycomb.beeline.tracing.propagation.BeelineServletFilter to instrument your application for Honeycomb. You just need to manually configure it, depending on your framework choice.

The filter is compatible with Servlet 3.1+, so for instance you could use a ServletContextListener to programatically configure the filter. In the example below, we use the io.honeycomb.beeline.DefaultBeeline to make the setup concise. However, you could just as well use a io.honeycomb.beeline.tracing.Beeline with a more customized configuration.

import io.honeycomb.beeline.DefaultBeeline;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.EnumSet;
import io.honeycomb.beeline.tracing.propagation.BeelineServletFilter;

public class ExampleBeelineServletContextListener implements ServletContextListener {
    private static final String SERVICE_NAME = "myServiceName";

    private final DefaultBeeline defaultBeeline = DefaultBeeline.getInstance("myDataset", SERVICE_NAME, "myWriteKey");

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // initialize the filter
        // you may wish to customise it using the builder; for instance to change the default span naming functions
        final BeelineServletFilter beelineServletFilter = BeelineServletFilter.builder()
                                                                            .setBeeline(defaultBeeline.getBeeline())
                                                                            .setServiceName(SERVICE_NAME)
                                                                            .build();

        //add the filter to the context
        sce.getServletContext().addFilter("beelineFilter", beelineServletFilter)
            .addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //close the beeline and its underlying resources
        defaultBeeline.close();
    }
}

More on adding context to events

In the above examples, we’ve gone over adding a custom field to a specific span. If you’re interested in adding custom fields to all spans, use Span.addTraceField or Span.AddTraceFields instead. These will adds the field(s) to the currently active span and also propgagate them down the trace.

Additionally, these fields will be packaged up and passed along to downstream processes if those processes are also using a beeline.

These functions are good for adding context that is better scoped to the request than this specific unit of work, e.g. user IDs, globally relevant feature flags, errors, etc.

More on spans and traces

We encourage people to think about instrumentation in terms of “units of work”. As your program grows, what constitutes a unit of work is likely to be portions of your overall service rather than an entire run. Spans are a way of breaking up a single external action (say, an HTTP request) into several smaller units in order to gain more insight into your service. Together, many spans make a trace, which you can visualize as traces within the Honeycomb query builder.

Spans always get a few fields:

Sampling Events

For very high throughput services, you can send only a portion of the events flowing through your service by enabling sampling. The sample rate N will send 1/N events, so a sample rate of 5 would send 20% of all events. A sample rate of 0 will never sample, while a sample rate of 1 will always sample. For high throughput services, a sample rate of 100 is a good start.

Sampling with Spring Boot

To start sampling traces with a Spring Boot app, simply set honeycomb.beeline.sample-rate in your application.properties.

# (Optional) Sets the global sample rate of traces.
honeycomb.beeline.sample-rate            :1

The Beeline will deterministically sample 1/N events based on the Span’s trace id. (This means that adding sampling will not break your traces. Either all spans in a trace will be sent, or no spans in the trace will be sent.)

Sampling without Spring Boot

Sampling of traces and Spans can be done in two ways:

When the two sampling mechanisms are used together, the effective sample rate is a product of both. However, note that you do not have to use either of the sampling mechanisms and can always configure either one or both to “always sample” with a sampleRate of 1 (Sampling.alwaysSampler() does this).

Note: Defining a Sampler overrides the deterministic sampling behavior for trace IDs. Unless you take trace.trace_id into account, you will get incomplete traces.

Troubleshooting with Spring Boot

A DebugResponseObserver is registered by default and enables a form of debug logging of responses from Honeycomb’s server, such as if the event was sucessfully sent, rejected by the server, or rejected client side. You can also provide your own implementation of io.honeycomb.libhoney.ResponseObserver either as a Spring Bean or by adding it with io.honeycomb.libhoney.HoneyClient#addResponseObserver.

To explicitly disable debug logging, set honeycomb.beeline.log-honeycomb-responses to false in your application.properties file.

Configuring the Beeline to Disable Sending Events to Honeycomb

For situations in which you would like to disable sending events to Honeycomb, such as a test environment or in unit tests, use HoneyClient’s constructor in which the Transport can be overridden. Create a mock Transport to substitute in that implements the Transport interface. One similar example can be seen here.

Example event with Spring Boot

Here is a sample event created by the Java Beeline for Spring Boot:

{
  "Timestamp": "2018-03-20T00:47:25.339Z",
  "duration_ms": 107.446625,
  "meta.beeline_version": "1.0.3",
  "meta.instrumentation_count": 3,
  "meta.instrumentations": ["spring_mvc", "spring_rest_template", "spring_aop"],
  "meta.local_hostname": "cobbler.local",
  "meta.package": "Spring Boot",
  "meta.package_version": "2.1.2.RELEASE",
  "name": "ProcessCreationForm",
  "request.content_length": 99,
  "request.error": "",
  "request.error_detail": "",
  "request.header.accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
  "request.header.content_type": "application/x-www-form-urlencoded",
  "request.header.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
  "request.host": "localhost",
  "request.http_version": "HTTP/1.1",
  "request.method": "POST",
  "request.path": "/owners/new",
  "request.query": "",
  "request.remote_addr": "0:0:0:0:0:0:0:1",
  "request.scheme": "http",
  "request.secure": false,
  "request.xhr": false,
  "response.header_content_type": "text/html;charset=UTF-8",
  "response.status_code": 200,
  "service_name": "sample app",
  "spring.method.name": "",
  "spring.request.dispatcher_type": "",
  "spring.request.handler_method": "OwnerController#processCreationForm",
  "spring.request.handler_type": "HandlerMethod",
  "spring.request.matched_pattern": "/owners/new",
  "trace.parent_id": "0676a4de-09f9-4287-baf5-a772b548362c",
  "trace.span_id": "9e4fe697-3ea9-48c9-b673-72d7ddf118a6",
  "trace.trace_id": "b64c89a9-7671-4732-bef1-9ef75ab831f6",
  "type": "http_server"
}

Queries to try

Here are some examples to get you started querying your app’s behavior:

Which endpoints are the slowest?

Which endpoints are the most frequently hit?

Contributions

Features, bug fixes and other changes to Beelines are gladly accepted. Please open issues or a pull request with your change via GitHub. Remember to add your name to the CONTRIBUTORS file!

All contributions will be released under the Apache License 2.0.