Logs
In the previous lessons, all the metrics and traces you sent were with the aim to have a visual representation. There is still a need for ordinary text information. You’re probably used to reading logs from your users, you may have generated some logs and sent them through a feedback form in your app.
OpenTelemetry can make this easier to collect, and you can still benefit from the visual graphs of traces and metrics for your logs.
To get started, open the starter project for the lesson. The project has the implementation for OTelLogs and the setup needed to connect the log exporter to Grafana, just as you did before with metrics and spans.
Add your token and endpoint to the Common.swift file so the app can connect to your server.
Sending a log works almost the same as sending a metric. Open OTelLogs.swift and add this at the end of the class:
public func sendLog(
scope: String,
message: String
) {
let openTelemetry = OpenTelemetry.instance
let otelLogger = openTelemetry.loggerProvider.loggerBuilder(instrumentationScopeName: scope).setEventDomain("Device").build()
let log = otelLogger.logRecordBuilder()
.setBody(.string(message))
log.emit()
}
There is nothing here that you haven’t seen before with metrics. You create a logger from the loggerBuilder method. The setBody() creates an instance of the log itself, then finally calling emit() to send the log.
For convenience, create a class method for sendLog(::):
public class func sendLog(
scope: String,
message: String
) {
shared.sendLog(
scope: scope,
message: message)
}
Go to TheMetStore.swift and add log at the end of fetchObjects(for:):
OTelLogs.sendLog(
scope: "TheMet-Logs",
message: "Searched for \(queryTerm), found \(objects.count) objects")
Build and run the app, you can search a couple of times for different keywords.
Open your Grafana portal and open “Logs”. You’ll find “TheMet” as one of the services.
Click on the “Show logs” button to see the logs better. Then click on the “Table” tab on the right.
The graph on the top represents the amount of logs over time. The checkboxes on the left allow you to choose which fields to show in the table. Feel free to add a few.
This log you sent is meant to be an example, it’s not meant to be used like print(). That will make your logs list messy and harder to understand. Feel free to remove it.
Another way to make logs easy to understand is to give them context beside the content in the log itself. You can connect logs to spans almost the same way you connect spans together.
Change sendLog to the following:
public func sendLog(
scope: String,
message: String,
span: (any Span)? = nil
) {
let openTelemetry = OpenTelemetry.instance
let otelLogger = openTelemetry.loggerProvider.loggerBuilder(instrumentationScopeName: scope).setEventDomain("Device").build()
let log = otelLogger.logRecordBuilder()
.setBody(.string(message))
if let span {
_ = log.setSpanContext(span.context)
}
log.emit()
}
Remember to update the class method too:
public class func sendLog(
scope: String,
message: String,
span: (any Span)? = nil
) {
shared.sendLog(
scope: scope,
message: message,
span: span)
}
Go back to TheMetStore.swift and move the call to send the log before ending the span, and add the span as a parameter in the function call:
OTelLogs.sendLog(
scope: "TheMet-Logs",
message: "Searched for \(queryTerm), found \(objects.count) objects",
span: span)
Build and run the app. Open Logs on Grafana, and you’ll see the logs reachable from there. A new field called “span_id” will appear in the scrollable “Fields” list on the left.
Go to “Traces” and open the main span for “fetchObjects”. Clicking the button “Logs for this span” will show only the logs connected to this span.
You can have multiple logs attached to a span.