Log slow API requests with server spans and client timing
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
final class RequestTiming: NSObject, URLSessionTaskDelegate, Sendable {
|
||||
private static let slow: TimeInterval = 0.2
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
|
||||
guard let t = metrics.transactionMetrics.last, let response = t.response as? HTTPURLResponse, response.statusCode != 101, response.value(forHTTPHeaderField: "Content-Type") != "text/event-stream", let fetched = t.fetchStartDate, let sent = t.requestStartDate, let first = t.responseStartDate, let done = t.responseEndDate else {
|
||||
return
|
||||
}
|
||||
let finished = Date()
|
||||
let serverTiming = response.value(forHTTPHeaderField: "Server-Timing") ?? ""
|
||||
let server = Self.serverTotal(serverTiming)
|
||||
let queue = (t.connectStartDate ?? sent).timeIntervalSince(fetched)
|
||||
let connect = t.connectStartDate.flatMap { start in t.connectEndDate.map { $0.timeIntervalSince(start) } } ?? 0
|
||||
let network = first.timeIntervalSince(sent) - server
|
||||
let download = done.timeIntervalSince(first)
|
||||
let total = done.timeIntervalSince(fetched)
|
||||
let line = "\(task.originalRequest?.httpMethod ?? "") \(task.originalRequest?.url?.path ?? "")"
|
||||
Task { @MainActor in
|
||||
let app = Date().timeIntervalSince(finished)
|
||||
if total + app < Self.slow {
|
||||
return
|
||||
}
|
||||
log.notice("request slow \(line, privacy: .public) total=\(Self.ms(total + app), privacy: .public) queue=\(Self.ms(queue), privacy: .public) connect=\(Self.ms(connect), privacy: .public) network=\(Self.ms(network), privacy: .public) server=\(Self.ms(server), privacy: .public) download=\(Self.ms(download), privacy: .public) app=\(Self.ms(app), privacy: .public) spans=\(serverTiming, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
private static func serverTotal(_ header: String) -> TimeInterval {
|
||||
for metric in header.split(separator: ",") {
|
||||
let parts = metric.split(separator: ";").map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
if parts.first == "total", let dur = parts.dropFirst().first(where: { $0.hasPrefix("dur=") }), let value = Double(dur.dropFirst(4)) {
|
||||
return value / 1000
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private static func ms(_ seconds: TimeInterval) -> String {
|
||||
"\(Int((seconds * 1000).rounded()))ms"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user