Episode #347

Activity Tracing in Swift

Series: Unified Logging and Activity Tracing

20 minutes
Published on July 20, 2018

This video is only available to subscribers. Get access to this video and 573 others.

Activity Tracing can help give you the big picture when looking at logs. By marking logical activities with os_activity, you can create a hierarchy of tasks that roll up the log statements that occurred for that activity. Unfortunately, using os_activity from Swift is not really supported yet, so we will see how to use a wrapper to make it a little easier, then dive in deep into C interop to see how all the pieces work.

Special thanks to Zach Waldowski, who originally created the Activity wrapper we use in this episode. Also thanks to Greg Heo, who helped me understand how dlsym works with RTLD_DEFAULT.

Episode Links

  • Activity.swift - the os_activity wrapper that makes the framework accessible in Swift

Using the Activity wrapper

let activity = Activity("Complex Work")
activity.apply {
    os_log("Starting some work...", log: log, type: .info)
    DispatchQueue.global(qos: .background).async {
        os_log("Work started...", log: self.log, type: .info)
        self.doStuff()
    }
}

Entering and leaving activities

let scope = waterActivity.enter()
defer {
    scope.leave()
}

Implementing os_activity directly using C-interop

First, we load the symbol dynamically, since it is not exposed to Swift...

   // see activity.h and dlfcn.h
   let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
   let sym = dlsym(RTLD_DEFAULT, "_os_activity_current")
   let OS_ACTIVITY_CURRENT = unsafeBitCast(sym, to: os_activity_t.self)

Then we get a handle to our dynamic shared object, which we need to make mutable:

    let dso = UnsafeMutableRawPointer(mutating: #dsohandle)

For our description, we need to translate this into the pointer type that the method expects. This involves some acrobatics.

     let desc: StaticString = "My Custom Activity"
        desc.withUTF8Buffer { buffer in
            if let address = buffer.baseAddress {
                let descriptionBuffer = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)

                // ...
            }
        }

Finally we can create our activity and apply a block to it.

        let activity = _os_activity_create(dso, descriptionBuffer, OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)
        os_activity_apply(activity, {
            os_log("Logging from my custom activity", log: self.log, type: .info)
        })