Documentation forAppOptics

Instrumentation SDK (legacy agent)

The following content pertains to custom instrumentation with the SDK for the legacy AppOptics Java Agent.

AppOptics agents are no long receiving updates. The new SolarWinds Observability libraries can send APM data in AppOptics and are regularly updated with new features and improvements. If you are still relying on the AppOptics agents and your components are supported by the new libraries, consider transitioning to the SolarWinds Observability libraries for your APM needs. For more information about the benefits of migrating to the SolarWinds Observability libraries. Alternatively, you can use SolarWinds Observability as your primary APM solution.

If you have already transitioned to the new SolarWinds Observability Java Library, see the SolarWinds Java Library documentation for custom instrumentation with the SDK information.

SolarWinds Observability libraries are not compatible with AppOptics agents. Do not use a mix of SolarWinds Observability libraries and AppOptics agents to instrument applications that are part of a distributed trace.

Get started

Looking to extend our out-of-the box instrumentation? The Java SDK enables custom-tracing and metrics reporting. It is provided as a separate package from the agent itself for better separation of dependencies. It is safe to run code against the Java SDK jar in an environment that doesn't have the agent running, but it will be a no-op.

The SDK jar is available as a maven dependency:

<dependency>
  <groupId>com.appoptics.agent.java</groupId>
  <artifactId>appoptics-sdk</artifactId>
  <version>x.x.x</version>
</dependency>

The same SDK jar is also available in the Java Agent installation directory, the jar should be made available in the classpath during development, compilation and runtime. On the other hand, the AppOptics Java Agent jar should only be used via the javaagent JVM argument at runtime and not be included elsewhere in your runtime classpath/environment, else you might encounter classloader errors.

Custom Spans

Instrumentation creates some spans by default, e.g., ‘tomcat', ‘jdbc_mysql', ‘spring', which more or less map to the components you'll find in the support matrix. If these spans don’t provide enough visibility, you can further divide your code into sub-spans. How you segment your application into spans is entirely up to you. For example the out-of-box instrumentation might not recognize calls to external processes; or, maybe there’s a subsection of your code that’s worth calling out on it’s own. In any case, instrumentation offers two facilities for creating spans: one is an annotation, for use when you want to represent a particular method as a span; the other is a set of SDK methods that’s better used when the block of code isn’t neatly contained.

Custom spans using annotations

To create a custom span using annotations:

  1. Import the com.appoptics.api.ext.LogMethod annotation at the top of your .java class file.

    import com.appoptics.api.ext.LogMethod
  2. Adding the following line above the method of interest.

    @LogMethod(layer="myLayerName", backTrace=true, storeReturn=true,
    reportExceptions=false)
  3. LogMethod also takes additional, optional key/value pairs.

    • backTrace: Reports a stack trace at the start of the method. Default: false.
    • storeReturn : Reports a string representation of the return value. Default: false.
    • reportExceptions : Reports exceptions that may be thrown by this method. Default: true.

Custom Spans using manual SDK calls

To manually create a span, you'll need to fire an entry event before your section of code, and fire an exit event after it.

// example 1
public static void myLogicallySeparateTask() {
  TraceEvent entryEvent = Trace.createEntryEvent("serviceX");
  
  //entryEvent.addInfo() to add extra KVs
  entryEvent.report();

  // do stuff
  
  TraceEvent exitEvent = Trace.createExitEvent("serviceX");

  //exitEvent.addInfo() to add extra KVs
  exitEvent.report();
}

// example 2
public static void alotOfLogicallySeparateTaks(int choice){
  boolean reportExtraInfo = false;
  
  switch(choice){
    case 0:
      Trace.createEntryEvent("LayerA").report();

      //doWorkA(...)
      
      break;
    case 1:
      Trace.createEntryEvent("LayerB").report();

      //doWorkB(...)
      
      break;  
    // etc ...
    }
    
  if (reportExtraInfo) {
    TraceEvent infoEvent = Trace.createInfoEvent(null);
    
    //infoEvent.addInfo() to add extra info
    infoEvent.report();
  }
  Trace.createExitEvent(null).report(); //Both these logs inherit their Layer
}

Custom Trace

Sometimes the out-of-box instrumentation might not capture traces on certain systems (custom web framework, batch jobs etc), but traces can still be created by using our SDK.

For example to instrument a standalone job:

//briefly block until agent is ready, since by default our agent call is non blocking, it might not trace the batch job if
//Trace.startTrace is called before a connection with collector is established
AgentChecker.waitUntilAgentReady(5, TimeUnit.SECONDS);

TraceEvent startEvent = Trace.startTrace("MyJob");

//set the "URL" so it can be filtered more easily on Web UI
startEvent.addInfo("URL", "job-1");
startEvent.report();

//job execution here...

Trace.endTrace("MyJob");

Take note that Custom Traces also report metrics and are subjected to sampling similar to all other instrumented web requests

Custom Transaction Name

Our out-of-the-box instrumentation assigns transaction name based on URL and Controller/Action values detected. However, you may want to override the transaction name to better describe your instrumented operation. Take note that transaction name is converted to lowercase, and might be truncated with invalid characters replaced.

If multiple transaction names are set on the same trace, then the last one would be used.

Empty string and null are considered invalid transaction name values and will be ignored.

protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
  ...
  Trace.setTransactionName("my-transaction-name");
  ...
}

Custom Metrics

Custom metrics can be added by using Metrics from our Java Agent SDK:

long startTime = System.currentTimeMillis();

//some work here...

long duration = System.currentTimeMillis() - startTime;
Metrics.summaryMetric("my-work-duration", duration, tags);
Metrics.incrementMetric("my-work-count", tags);

Values reported will be aggregated based on the metrics name and tags, and flushed every 30 seconds.

Tracing multi-threaded applications

While instrumenting your code, you may want to report events from background/child threads and associate them with the parent thread that spawned them - assuming that a trace was already started in the parent thread. To do this, mark the entry event associated with the background thread as ‘async' by calling .setAsync() . Then call Trace.endTrace() when that thread is done processing.

TraceEvent event = Trace.createEntryEvent(layerName);
event.setAsync();
event.report();

// Your processing ...

Trace.endTrace();

Tracing across external spans

While instrumenting your code you might want to trace a request across processes or hosts. This can be done by passing the X-Trace ID between them.

  1. The client should call Trace.getCurrentXTraceID(), which returns either a string representing the current trace or an empty string if no trace is active.
  2. The client should then send the value to the remote process by any desired means, but probably via the X-Trace HTTP header.
  3. The remote process should read the identifier and input it to Trace.continueTrace().
  4. When the remote process is done, it should end the trace using Trace.endTrace().
  5. The remote process should send the trace id back to the client by any means, but probably via the X-Trace HTTP header.
  6. The client should then associate the trace id with the next exit event that it reports by using TraceEvent.addEdge(), which links the client-side and remote-side events.

Collecting additional trace details

Any additional key/value inserted into entry/info/exit events will be presented on the trace details page.

TraceEvent event = Trace.createInfoEvent("some_other_layer");
event.addInfo("something", "interesting");
event.report();

Report errors and exceptions

To report an error or exception, which actually may be any java throwable, you should call the Trace.logException() method with the throwable as the first parameter. Typically this would be done in the catch block of a try/catch. This will generate an error event and report it to AppOptics.

try {
  // your code that might throw an exception goes here ...
} catch (YourException exception) {
  Trace.logException(exception);
  // the rest of your exception handler ...
}

Report backtraces

To report a backtrace of the current thread, create an info event, call the TraceEvent.addBackTrace() method, and then report the event.

TraceEvent event = Trace.createInfoEvent("some_other_layer");
event.addBackTrace();
event.report();

Report single execution jobs

The non-blocking design in our SDK imposes minimal impact to your application. However, in scenario of tracing a single execution job via our SDK, it is advised to put a short wait with our AgentChecker.waitUntilAgentReady handle to ensure all trace details are captured. The call would block until either the agent is ready or the wait timeout has elapsed.

AgentChecker.waitUntilAgentReady(10, TimeUnit.SECONDS);
Trace.startTrace("my-job").report();
//... job execution here
Trace.endTrace("my-job");

Java agent version compatibility

Our Java agent is fully backward compatible with all SDK versions, which means upgrading the agent should not require updating the SDK dependency on the project. However, updating the SDK sometimes does require upgrading to a newer version of the agent.

If the running Java agent does not fulfill the minimum version requirement for the SDK, a warning message like below will be printed:

SDK calls would be ignored. Require -javaagent of at least version [x.x.x] but found version [x.x.x] running

In this case the Java agent will need to be upgraded in order for the SDK to function properly.

There is a known bug for SDK version 6.7.0 or earlier that if the agent is newer than version 6.10.0, it will incorrectly print the warning message above. Upgrade to SDK version 6.7.1 or newer to solve this problem.

Java SDK reference

The full Javadoc of the SDK can be found here.

The scripts are not supported under any SolarWinds support program or service. The scripts are provided AS IS without warranty of any kind. SolarWinds further disclaims all warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The risk arising out of the use or performance of the scripts and documentation stays with you. In no event shall SolarWinds or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the scripts or documentation.