Traces and Spans

Traces and spans are fundamental concepts in Specto. This guide will discuss their definitions in detail, and provide examples on how to use them to instrument your code.


Traces

Tracing has a few existing definitions:

  • Logging data about the execution of an application or kernel (typically data about resource usage, function/method execution, etc.) with tools like dtrace
  • Tracing requests in distributed systems, where a trace is a collection of spans, each of which represent an operation that occurs within a trace. This definition originated in Google's Dapper project, which popularized the high level concepts that are common to most distributed tracing systems today.

Specto's definition of a trace is a combination of these two definitions. A trace contains information about a program's execution for a particular interaction, where we define an interaction as being an action that a user can take in a mobile application — for example: starting the app, loading a screen, or initiating an action as a result of a button press. Like low-level tracing tools, a Specto trace contains information about resource usage in the app (CPU, memory, etc.) and information about function execution. Like distributed tracing tools, Specto traces also contain spans, which break a trace down into a more granular set of operations.

To start a trace, call Specto.startTrace with the name of your interaction:

Specto.startTrace(interactionName: "load-feed")
Specto.startTrace("load-feed")
[Specto startTrace:@"load-feed"];
Specto.startTrace("load-feed")

We recommend starting traces for high level operations in your app. For example, on a button press that starts loading a new screen. To end the trace, call Specto.endTrace with the same interaction name that you passed to startTrace:

Specto.endTrace(interactionName: "load-feed")
Specto.endTrace("load-feed")
[Specto endTrace:@"load-feed"];
Specto.endTrace("load-feed")

If you call endTrace for a trace that is not active, it will be treated as a no-op.

Additional metadata can be associated with a trace by adding annotations. In the dashboard, annotation keys can be used to filter traces & metrics.

Specto.annotateTrace(interactionName: "load-feed", key: "sort", value: "trending")
Specto.annotateTrace("load-feed", "sort", "trending")
[Specto annotateTrace:@"load-feed" key:@"sort" value:@"trending"];
Specto.annotateTrace("load-feed", "sort", "trending")

If you call annotateTrace for a trace that is not active, it will be treated as a no-op.

Limitations of Traces

  • Traces cannot be concurrent. There can only be 1 active trace at a time. If you start a new trace, the previous one will be cancelled. The only exception to this rule is the startup trace: the startup trace takes precedence over all other traces, so if you attempt to start a new trace before calling Specto.markAppStartupComplete(), it will be a no-op. Traces that are cancelled for this reason will have an error code of TRACE_LIMIT_EXCEEDED in the dashboard.
  • Traces are limited to 30 seconds. Traces will automatically time out after 30 seconds if they are not explicitly ended. Traces that are cancelled for this reason will have an error code of TRACE_TIMEOUT in the dashboard.
  • Trace annotation keys must be unique. If you annotate a trace twice with the same key, only the last annotation will be considered.
  • Trace annotation keys and metrics must be strings. We plan to expand support to other data types (numeric types, booleans, etc.) in the future.

We are open to re-evaluating these limitations, so if you have a use case that requires changes to these rules, please contact us!

Spans

A span is an operation within a trace that has its execution time traced independently of its parent trace. Spans exist within the context of a trace, but can be nested and concurrent, unlike traces. For example, during the course of a traced interaction like loading a feed, you may create spans for the distinct operations involved: loading data from a cache or the network, building view models, and rendering the data on screen.

To start a trace, call Specto.startSpan with the name of your span. If there is no active trace, this call will be a no-op (as will subsequent calls to end or annotate the trace).

let span = Specto.startSpan("load-cover-photo")
var span = Specto.startSpan("load-cover-photo")
id<SpectoSpan> span = [Specto startSpan:@"load-cover-photo"];
SpanHandler span = Specto.startSpan("load-cover-photo")

The handle object returned from the startSpan can be used to end or annotate the span:

span.annotate(key: "size", value: "large")
span.end()
span.annotate("size", "large")
span.end()
[span annotateWithKey:@"size" value:@"large"];
[span end];
span.annotate("size", "large")
span.end()

👍

If you end a trace without explicitly ending the spans, all active spans in that trace will be automatically ended.

For cases where passing around a handle object is not practical (e.g. if the span is ended from a different class from where it is started, and passing the handle through is not an option), spans can also be annotated or ended by using their name:

Specto.annotateSpan("load-cover-photo", key: "size", value: "large")
Specto.endSpan("load-cover-photo")
Specto.annotateSpan("load-cover-photo", "size", "large")
Specto.endSpan("load-cover-photo")
[Specto annotateSpan:@"load-cover-photo" key:@"size" value:@"large"];
[Specto endSpan:@"load-cover-photo"];
Specto.annotateSpan("load-cover-photo", "size", "large")
Specto.endSpan("load-cover-photo")

🚧

When ending or annotating a span using the span name instead of the handle, there is an edge case where you may have multiple spans with the same name in the current stack of spans. In this scenario, annotate/end will only impact the top-most (i.e. the most recent) span in the stack.

Finally, if you are using Specto with Kotlin, there is a simplified syntax for creating a span around a block of code, by using a receiver function:

// Span starts here.
Specto.span("load-cover-photo") {
    // The span will last as long as this code block.
    doThings()
    
    // The span can be annotated as well:
    annotate("size", "large")
}
// Span ends here.

This is only suitable for synchronous blocks of code. This API is not available in Swift, Objective-C, or Java.