Sending Spans with Start & Finish

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the last lesson, you created a new Grafana account and sent your first metric data.

In this section, you’ll setup OpenTelemetry Tracing to prepare for creating spans and measure the time your app takes for different operations.

Open the sample project in the starter folder of this lesson.

It’s almost the same as how you finished the project from the previous lesson. It has two additional files Common.swift & OTelSpans.swift.

Set your Grafana token in the Common.swift file in grafanaToken property, and set the URL of the Grafana stack in grafanaEndpoint property.

Next, open OTelSpans.swift and start by implementing the initializer. Start by adding those lines in init:

let grafanaEndpoint = URL(string: "\(grafanaEndpoint)/v1/traces")! // 1

let grafanaHeaders = OtlpConfiguration(headers: [("Authorization", "Basic \(grafanaToken)")], exportAsJson: true)  // 2

grafanaHttpExporter = OtlpHttpTraceExporter(endpoint: grafanaEndpoint,
                                            config: grafanaHeaders)  // 3
  1. Similar to OTelMetrics from the previous lesson, you specify the URL for sending traces, notice that it ends with /traces.
  2. You create the header configurations just the same as OTelMetrics.
  3. Setup the Trace exporter that will connect to Grafana’s HTTP endpoint with the authentication token.

Next step is to register the trace provider in OpenTelemetry. Add the following code at the end of init:

let spanProcessor = SimpleSpanProcessor(spanExporter: grafanaHttpExporter) // 1
    
OpenTelemetry.registerTracerProvider(tracerProvider:
                                      TracerProviderBuilder()
  .with(resource: DefaultResources().get())
  .add(spanProcessor: spanProcessor)
  .build()
) // 2
  1. You start by creating a processor that will be responsible for sending the spans through your exporter. You’ll be using the simple processor for now.
  2. Finally, You register the trace provider through its builder. The builder can take some resources to include with every span, which are a set of key-value pairs to include in all the spans. Then pass the builder the processor you just created.

Creating spans is similar to creating gauges from the previous lesson. From the OpenTelemetry instance, you access the provider, then create a builder.

Add this method to OTelSpans right after the initializer:

internal func tracer(  // 1
  scopeName: String
) -> any Tracer {
  let instrumentationScopeVersion = "semver:0.1.0"  // 2
  
  let tracer = OpenTelemetry.instance.tracerProvider.get(  // 3
    instrumentationName: scopeName,
    instrumentationVersion: instrumentationScopeVersion)
  return tracer
}
  1. The new method takes a string as a parameter and returns an object conforming to Tracer protocol.
  2. To get the tracer instance, you need to specify an instrumentation name, which is the parameter passed to the method, and a version which you’re setting as a constant. The “semver” is short for Semantic Version.
  3. Finally, you get the tracer object from OpenTelemetry and return it.

Now to add a method to create a span, add the following after the last method at the end of the class:

public func createSpan(  // 1
  scopeName: String,
  name: String
) -> (any Span) {
  var spanBuilder = tracer(scopeName: scopeName)  // 2
    .spanBuilder(spanName: name)
  
  let span = spanBuilder.startSpan()  // 3
  
  return span
}
  1. The method takes two string parameters, once for the scope name that you’ll use to get a tracer, the second is the name of the span.
  2. Using tracer(scopeName: String) method you created earlier, you get the tracer and through it a span builder while defining a name for any span created from this builder.
  3. From the builder, get a span that is already started. Starting the span is equivalent to setting the start date to Date.now.

Since OTelSpans is keeping the shared property and the initializer private, it would make the usage of your feature easy by making a class method that forwards the call to its own internal instance. Add this method:

public class func createSpan(
  scopeName: String,
  name: String
) -> (any Span) {
  shared.createSpan(
    scopeName: scopeName,
    name: name)
}

Now to put all this to good use. In the previous lesson, you created a gauge that set how many objects are stored. Now, you’ll create a span that covers how long it took to fetch the objects matching the query.

Go to TheMetStore.swift add this line in the very beginning of fetchObjects(for:):

let span = OTelSpans.createSpan(scopeName: "TheMet-Tracing", name: "fetchObjects")

Add this right before sending the gauge:

span.end()

Run the app and search for a few different keywords, then open your Grafana portal. From the menu, choose “Traces” under the “Drilldown” section. Choose “Traces” from the tabs in the middle of the page.

Selecting the tab will show you all the individual spans you sent: When it started, its name, and its duration.

You can select any of the individual spans from its trace name for additional information.

Expanding the span doesn’t show much more info right now. Some can be long and some can be short, and you can’t understand why would it be either.

For that, you need to attach some information from your app on the span to provide some “debugging” information to gain more insight on those spans. Info such as what was the keyword searched for, and the number of objects returned from the search would definitely be helpful.

In the next section, you’ll learn how to do this.

See forum comments
Download course materials from Github
Previous: Introduction Next: Attaching Attributes to Spans