Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tracing] Swift Distributed Tracing support (or better async/await support) #1511

Open
0xpablo opened this issue Oct 7, 2023 · 4 comments
Open

Comments

@0xpablo
Copy link

0xpablo commented Oct 7, 2023

I was looking to integrate DataDog's tracing today and I noticed there's a concept of the current execution context but I believe it's using the os_activity APIs based on threads, which doesn't work with code using async/await.

It would be great to either support Swift Distributed Tracing or to at least have better async/await support using Task Locals.

@0xpablo
Copy link
Author

0xpablo commented Oct 7, 2023

I've been experimenting a bit and defined a couple of helpers that seem to do the trick. I'll share them in case it's useful to someone:

Helpers:

enum SpanContext {
    @TaskLocal
    static var current: OTSpanContext?
}

public func withSpan<T>(
     _ operationName: String,
     context: OTSpanContext? = nil,
     _ operation: (OTSpan) throws -> T
 ) rethrows -> T {
     let span = Tracer.shared().startSpan(operationName: operationName,
                               childOf: context ?? SpanContext.current)

     defer { span.finish() }

     do {
         return try SpanContext.$current.withValue(span.context) {
             try operation(span)
         }
     } catch {
         span.setError(error)
         throw error
     }
 }

public func withSpan<T>(
     _ operationName: String,
     context: OTSpanContext? = nil,
    @_inheritActorContext @_implicitSelfCapture _ operation: (OTSpan) async throws -> T
) async rethrows -> T {
     let span = Tracer.shared().startSpan(operationName: operationName,
                               childOf: context ?? SpanContext.current)

     defer { span.finish() }

     do {
         return try await SpanContext.$current.withValue(span.context) {
             try await operation(span)
         }
     } catch {
         span.setError(error)
         throw error
     }
 }

Example:

            try await withSpan("Parent") { spain in
                try await Task.sleep(nanoseconds: NSEC_PER_SEC * 1)

                try await withSpan("Child 1 (Group)") { _ in
                    try await withThrowingTaskGroup(of: Void.self) { taskGroup in
                        taskGroup.addTask {
                            try await withSpan("Group Child 1") { _ in
                                try await Task.sleep(nanoseconds: NSEC_PER_SEC * 1)
                            }
                        }

                        taskGroup.addTask {
                            try await withSpan("Group Child 2") { _ in
                                try await Task.sleep(nanoseconds: NSEC_PER_SEC * 1)
                            }
                        }

                        try await taskGroup.waitForAll()
                    }
                }

                try await withSpan("Child 2") { span in
                    try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2)
                }
            }

Produces:

image

@ganeshnj
Copy link
Contributor

ganeshnj commented Oct 9, 2023

Thanks for suggestion @0xpablo and the example, very helpful.

We have been talking to support structured concurrency within the team already.

To your point on adopting/providing https://github.com/apple/swift-distributed-tracing support, we usually stay close to Otel (and Open Tracing) in terms of experience (I know it is not 1:1 mapping), but that's the aspiration.

I wonder, if OTSpan is Sendable, would make it any better?

@ganeshnj ganeshnj added feature awaiting response Waiting for response / confirmation from the reporter labels Oct 9, 2023
@0xpablo
Copy link
Author

0xpablo commented Oct 10, 2023

That's great to hear! I think it would make sense to make OTSpan Sendable to be able to pass it around different concurrency domains. I think this should be easy as DDSpan can be marked as @unchecked Sendable since it's already thread safe right?

Thanks!

@ganeshnj
Copy link
Contributor

Mostly yes, DDSpan can be marked as @unchecked Sendable but it has some properties like tracer instance which needs to be ignored.

@maxep maxep added feature-request and removed feature awaiting response Waiting for response / confirmation from the reporter labels May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants