Skip to content

Commit

Permalink
Merge pull request #254 from kean/add-task-description
Browse files Browse the repository at this point in the history
Add URLSession.taskDescription support
  • Loading branch information
kean authored May 19, 2024
2 parents 32707c6 + 068d95a commit 95cc77f
Show file tree
Hide file tree
Showing 27 changed files with 164 additions and 51 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Pulse 4.x

## Pulse 4.2.0

*May 19, 2024*

- Add `URLSession.taskDescription` support (closes https://github.com/kean/Pulse/issues/251). The console will now use `.taskDescription` instead of the original request URL. This behavior can be customized using the new `ConsoleViewDelegate`.


## Pulse 4.1.1

*May 6, 2024*
Expand Down
4 changes: 4 additions & 0 deletions Pulse.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
0C9F05002884F34A0035239F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0C9F04FF2884F34A0035239F /* Preview Assets.xcassets */; };
0CA20FED29E5976700A59180 /* LoggerStoreExportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA20FEC29E5976700A59180 /* LoggerStoreExportTests.swift */; };
0CA20FEF29E5999700A59180 /* LoggerStoreBaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA20FEE29E5999700A59180 /* LoggerStoreBaseTests.swift */; };
0CA2632E2BFA3855003C8E97 /* ConsoleDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA2632D2BFA3855003C8E97 /* ConsoleDelegate.swift */; };
0CA32DE729D5E028001712E8 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA32DE629D5E028001712E8 /* NSAttributedString+Extensions.swift */; };
0CAF1D7B297C2040002E2722 /* ConsoleListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAF1D7A297C2040002E2722 /* ConsoleListViewModel.swift */; };
0CB17F1A2978ABBA004E33F4 /* ManagedObjectsCountObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB17F192978ABBA004E33F4 /* ManagedObjectsCountObserver.swift */; };
Expand Down Expand Up @@ -562,6 +563,7 @@
0C9F05012884F34A0035239F /* Pulse_Demo_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Pulse_Demo_macOS.entitlements; sourceTree = "<group>"; };
0CA20FEC29E5976700A59180 /* LoggerStoreExportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerStoreExportTests.swift; sourceTree = "<group>"; };
0CA20FEE29E5999700A59180 /* LoggerStoreBaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerStoreBaseTests.swift; sourceTree = "<group>"; };
0CA2632D2BFA3855003C8E97 /* ConsoleDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleDelegate.swift; sourceTree = "<group>"; };
0CA32DE629D5E028001712E8 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
0CAF1D7A297C2040002E2722 /* ConsoleListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleListViewModel.swift; sourceTree = "<group>"; };
0CB17F192978ABBA004E33F4 /* ManagedObjectsCountObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectsCountObserver.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1393,6 +1395,7 @@
0CF0D5FD296F189600EED9D4 /* ConsoleView-macos.swift */,
0CF0D5FB296F189600EED9D4 /* ConsoleView-watchos.swift */,
0CBEF6A429DF69E800132FB3 /* ConsoleView.swift */,
0CA2632D2BFA3855003C8E97 /* ConsoleDelegate.swift */,
0CF0D60A296F189600EED9D4 /* ConsoleEnvironment.swift */,
0CECF63C2996BE12000F9CCD /* ConsoleDataSource.swift */,
);
Expand Down Expand Up @@ -2065,6 +2068,7 @@
0CF0D614296F189600EED9D4 /* TextRendererJSON.swift in Sources */,
0C38F57B2A34BFA0001E19C0 /* RemoteLoggerSettingsViewModel.swift in Sources */,
0CFB0CA42A3F662300221FEA /* SettingsView-tvos.swift in Sources */,
0CA2632E2BFA3855003C8E97 /* ConsoleDelegate.swift in Sources */,
0CB63A232974A64500525165 /* ConsoleSearchOperation.swift in Sources */,
0CF0D658296F189600EED9D4 /* SettingsView-ios.swift in Sources */,
0CF0D665296F189600EED9D4 /* NetworkMenuCell.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Sources/Pulse/LoggerStore/LoggerStore+Entities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public final class NetworkTaskEntity: NSManagedObject {
@NSManaged public var requestBodySize: Int64
/// The size of the response body.
@NSManaged public var responseBodySize: Int64
/// The `taskDescription` value of `URLSessionTask`.
@NSManaged public var taskDescription: String?
/// Associated (technical) message.
@NSManaged public var message: LoggerMessageEntity?

Expand Down
22 changes: 18 additions & 4 deletions Sources/Pulse/LoggerStore/LoggerStore+Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,26 @@ extension LoggerStore {
}
}

public struct NetworkTaskCreated: Codable, Sendable {
public struct NetworkTaskCreated: Codable, Sendable, NetworkTaskEvent {
public var taskId: UUID
public var taskType: NetworkLogger.TaskType
public var createdAt: Date
public var originalRequest: NetworkLogger.Request
public var currentRequest: NetworkLogger.Request?
public var label: String?
public var taskDescription: String?

@available(*, deprecated, message: "Deprecated (added for backward compatibility)")
public var session: UUID? = Session.current.id

public init(taskId: UUID, taskType: NetworkLogger.TaskType, createdAt: Date, originalRequest: NetworkLogger.Request, currentRequest: NetworkLogger.Request?, label: String?) {
public init(taskId: UUID, taskType: NetworkLogger.TaskType, createdAt: Date, originalRequest: NetworkLogger.Request, currentRequest: NetworkLogger.Request?, label: String?, taskDescription: String?) {
self.taskId = taskId
self.taskType = taskType
self.createdAt = createdAt
self.originalRequest = originalRequest
self.currentRequest = currentRequest
self.label = label
self.taskDescription = taskDescription
}
}

Expand All @@ -83,7 +85,7 @@ extension LoggerStore {
}
}

public struct NetworkTaskCompleted: Codable, Sendable {
public struct NetworkTaskCompleted: Codable, Sendable, NetworkTaskEvent {
public var taskId: UUID
public var taskType: NetworkLogger.TaskType
public var createdAt: Date
Expand All @@ -95,11 +97,12 @@ extension LoggerStore {
public var responseBody: Data?
public var metrics: NetworkLogger.Metrics?
public var label: String?
public var taskDescription: String?

@available(*, deprecated, message: "Deprecated (added for backward compatibility)")
public var session: UUID? = Session.current.id

public init(taskId: UUID, taskType: NetworkLogger.TaskType, createdAt: Date, originalRequest: NetworkLogger.Request, currentRequest: NetworkLogger.Request?, response: NetworkLogger.Response?, error: NetworkLogger.ResponseError?, requestBody: Data?, responseBody: Data?, metrics: NetworkLogger.Metrics?, label: String?) {
public init(taskId: UUID, taskType: NetworkLogger.TaskType, createdAt: Date, originalRequest: NetworkLogger.Request, currentRequest: NetworkLogger.Request?, response: NetworkLogger.Response?, error: NetworkLogger.ResponseError?, requestBody: Data?, responseBody: Data?, metrics: NetworkLogger.Metrics?, label: String?, taskDescription: String?) {
self.taskId = taskId
self.taskType = taskType
self.createdAt = createdAt
Expand All @@ -111,6 +114,7 @@ extension LoggerStore {
self.responseBody = responseBody
self.metrics = metrics
self.label = label
self.taskDescription = taskDescription
}

init(_ entity: NetworkTaskEntity) {
Expand All @@ -130,6 +134,7 @@ extension LoggerStore {
self.metrics = NetworkLogger.Metrics(taskInterval: interval, redirectCount: Int(entity.redirectCount), transactions: transactions)
}
self.label = entity.message?.label
self.taskDescription = entity.taskDescription
}
}

Expand All @@ -147,3 +152,12 @@ extension LoggerStore {
}
}
}

protocol NetworkTaskEvent {
var taskId: UUID { get }
var taskType: NetworkLogger.TaskType { get }
var createdAt: Date { get }
var label: String? { get }
var originalRequest: NetworkLogger.Request { get }
var taskDescription: String? { get }
}
1 change: 1 addition & 0 deletions Sources/Pulse/LoggerStore/LoggerStore+Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ extension LoggerStore {
Attribute(name: "isFromCache", type: .booleanAttributeType),
Attribute(name: "isMocked", type: .booleanAttributeType),
Attribute(name: "rawMetadata", type: .stringAttributeType),
Attribute(name: "taskDescription", type: .stringAttributeType),
Relationship(name: "originalRequest", type: .oneToOne(), entity: request),
Relationship(name: "currentRequest", type: .oneToOne(isOptional: true), entity: request),
Relationship(name: "response", type: .oneToOne(isOptional: true), entity: response),
Expand Down
53 changes: 36 additions & 17 deletions Sources/Pulse/LoggerStore/LoggerStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,16 @@ public final class LoggerStore: @unchecked Sendable, Identifiable {

extension LoggerStore {
/// Stores the given message.
public func storeMessage(createdAt: Date? = nil, label: String, level: Level, message: String, metadata: [String: MetadataValue]? = nil, file: String = #file, function: String = #function, line: UInt = #line) {
public func storeMessage(
createdAt: Date? = nil,
label: String,
level: Level,
message: String,
metadata: [String: MetadataValue]? = nil,
file: String = #file,
function: String = #function,
line: UInt = #line
) {
handle(.messageStored(.init(
createdAt: createdAt ?? configuration.makeCurrentDate(),
label: label,
Expand All @@ -312,7 +321,15 @@ extension LoggerStore {
///
/// - note: If you want to store incremental updates to the task, use
/// `NetworkLogger` instead.
public func storeRequest(_ request: URLRequest, response: URLResponse?, error: Swift.Error?, data: Data?, metrics: URLSessionTaskMetrics? = nil, label: String? = nil) {
public func storeRequest(
_ request: URLRequest,
response: URLResponse?,
error: Swift.Error?,
data: Data?,
metrics: URLSessionTaskMetrics? = nil,
label: String? = nil,
taskDescription: String? = nil
) {
handle(.networkTaskCompleted(.init(
taskId: UUID(),
taskType: .dataTask,
Expand All @@ -324,7 +341,8 @@ extension LoggerStore {
requestBody: request.httpBody ?? request.httpBodyStreamData(),
responseBody: data,
metrics: metrics.map(NetworkLogger.Metrics.init),
label: label
label: label,
taskDescription: taskDescription
)))
}

Expand Down Expand Up @@ -369,7 +387,7 @@ extension LoggerStore {
}

private func process(_ event: Event.NetworkTaskCreated) {
let entity = createTask(forTaskId: event.taskId, taskType: event.taskType, createdAt: event.createdAt, label: event.label, url: event.originalRequest.url)
let entity = createTask(for: event)

entity.url = event.originalRequest.url?.absoluteString
entity.host = event.originalRequest.url.flatMap { $0.getHost() }
Expand All @@ -395,7 +413,7 @@ extension LoggerStore {
}

private func process(_ event: Event.NetworkTaskCompleted) {
let entity = findOrCreateTask(forTaskId: event.taskId, taskType: event.taskType, createdAt: event.createdAt, label: event.label, url: event.originalRequest.url)
let entity = findOrCreateTask(for: event)

entity.url = event.originalRequest.url?.absoluteString
entity.host = event.originalRequest.url.flatMap { $0.getHost() }
Expand Down Expand Up @@ -514,40 +532,41 @@ extension LoggerStore {
}
}

private func findOrCreateTask(forTaskId taskId: UUID, taskType: NetworkLogger.TaskType, createdAt: Date, label: String?, url: URL?) -> NetworkTaskEntity {
if let entity = findTask(forTaskId: taskId) {
private func findOrCreateTask(for event: NetworkTaskEvent) -> NetworkTaskEntity {
if let entity = findTask(forTaskId: event.taskId) {
return entity
}
return createTask(forTaskId: taskId, taskType: taskType, createdAt: createdAt, label: label, url: url)
return createTask(for: event)
}

private func createTask(forTaskId taskId: UUID, taskType: NetworkLogger.TaskType, createdAt: Date, label: String?, url: URL?) -> NetworkTaskEntity {
if let entity = tasksCache[taskId] {
private func createTask(for event: NetworkTaskEvent) -> NetworkTaskEntity {
if let entity = tasksCache[event.taskId] {
return entity // Defensive code in case createTask gets called more than once
}
let task = NetworkTaskEntity(context: backgroundContext)
task.taskId = taskId
task.taskType = taskType.rawValue
task.createdAt = createdAt
task.taskId = event.taskId
task.taskType = event.taskType.rawValue
task.createdAt = event.createdAt
task.responseBodySize = -1
task.requestBodySize = -1
task.isFromCache = false
task.session = session.id
task.taskDescription = event.taskDescription

let message = LoggerMessageEntity(context: backgroundContext)
message.createdAt = createdAt
message.createdAt = event.createdAt
message.level = Level.debug.rawValue
message.label = label ?? "network"
message.label = event.label ?? "network"
message.session = session.id
message.file = ""
message.function = ""
message.line = Int32(NetworkTaskEntity.State.pending.rawValue)
message.text = url?.absoluteString ?? ""
message.text = event.originalRequest.url?.absoluteString ?? ""

message.task = task
task.message = message

tasksCache[taskId] = task
tasksCache[event.taskId] = task

return task
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/Pulse/NetworkLogger/NetworkLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ public final class NetworkLogger: @unchecked Sendable {
createdAt: Date(),
originalRequest: .init(originalRequest),
currentRequest: task.currentRequest.map(Request.init),
label: configuration.label
label: configuration.label,
taskDescription: task.taskDescription
)))
}

Expand Down Expand Up @@ -225,7 +226,8 @@ public final class NetworkLogger: @unchecked Sendable {
requestBody: originalRequest.httpBody ?? originalRequest.httpBodyStreamData(),
responseBody: data,
metrics: metrics,
label: configuration.label
label: configuration.label,
taskDescription: task.taskDescription
)))
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Pulse/RemoteLogger/RemoteLogger-Protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ extension RemoteLogger {
responseBody = data.from(Manifest.size + Int(manifest.messageSize) + Int(manifest.requestBodySize), size: Int(manifest.responseBodySize))
}

return LoggerStore.Event.NetworkTaskCompleted(taskId: event.taskId, taskType: event.taskType, createdAt: event.createdAt, originalRequest: event.originalRequest, currentRequest: event.currentRequest, response: event.response, error: event.error, requestBody: requestBody, responseBody: responseBody, metrics: event.metrics, label: event.label)
return LoggerStore.Event.NetworkTaskCompleted(taskId: event.taskId, taskType: event.taskType, createdAt: event.createdAt, originalRequest: event.originalRequest, currentRequest: event.currentRequest, response: event.response, error: event.error, requestBody: requestBody, responseBody: responseBody, metrics: event.metrics, label: event.label, taskDescription: event.taskDescription)
}
}

Expand Down
4 changes: 0 additions & 4 deletions Sources/PulseUI/Extensions/Pulse+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ extension NetworkTaskEntity: Identifiable {
}

extension NetworkTaskEntity {
var title: String {
url.flatMap(URL.init(string:))?.lastPathComponent ?? "Request"
}

var requestFileViewerContext: FileViewerViewModel.Context {
FileViewerViewModel.Context(
contentType: originalRequest?.contentType,
Expand Down
30 changes: 30 additions & 0 deletions Sources/PulseUI/Features/Console/ConsoleDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// The MIT License (MIT)
//
// Copyright (c) 2020-2024 Alexander Grebenyuk (github.com/kean).

import Foundation
import Pulse

/// Allows you to customize the console behavior.
public protocol ConsoleViewDelegate {
/// Returns a title for the given task.
func getTitle(for task: NetworkTaskEntity) -> String?
}

extension ConsoleViewDelegate {
func getTitle(for task: NetworkTaskEntity) -> String? {
if let taskDescription = task.taskDescription, !taskDescription.isEmpty {
return taskDescription
}
return task.url
}

func getShortTitle(for task: NetworkTaskEntity) -> String {
guard let title = getTitle(for: task) else {
return ""
}
return URL(string: title)?.lastPathComponent ?? title
}
}

struct DefaultConsoleViewDelegate: ConsoleViewDelegate {}
5 changes: 4 additions & 1 deletion Sources/PulseUI/Features/Console/ConsoleEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ final class ConsoleEnvironment: ObservableObject {
let router = ConsoleRouter()

let initialMode: ConsoleMode
let delegate: ConsoleViewDelegate

@Published var mode: ConsoleMode
@Published var listOptions: ConsoleListOptions = .init()
Expand All @@ -37,7 +38,7 @@ final class ConsoleEnvironment: ObservableObject {

private var cancellables: [AnyCancellable] = []

init(store: LoggerStore, mode: ConsoleMode = .all) {
init(store: LoggerStore, mode: ConsoleMode = .all, delegate: ConsoleViewDelegate = DefaultConsoleViewDelegate()) {
self.store = store
switch mode {
case .all: self.title = "Console"
Expand All @@ -52,6 +53,8 @@ final class ConsoleEnvironment: ObservableObject {
case .network: self.mode = .network
}

self.delegate = delegate

func makeDefaultOptions() -> ConsoleDataSource.PredicateOptions {
var options = ConsoleDataSource.PredicateOptions()
options.filters.shared.sessions.selection = [store.session.id]
Expand Down
2 changes: 1 addition & 1 deletion Sources/PulseUI/Features/Console/ConsoleView-ios.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct ConsoleView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
ConsoleView(environment: .init(store: .mock))
ConsoleView(environment: .init(store: .mock, delegate: DefaultConsoleViewDelegate()))
}.previewDisplayName("Console")
NavigationView {
ConsoleView(store: .mock, mode: .network)
Expand Down
13 changes: 10 additions & 3 deletions Sources/PulseUI/Features/Console/ConsoleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ import Pulse
import Combine

extension ConsoleView {
/// Initializes the console view
/// Initializes the console view.
///
/// - parameters:
/// - store: The store to display. By default, `LoggerStore/shared`.
/// - mode: The console mode. By default, ``ConsoleMode/all``. If you change
/// the mode to ``ConsoleMode/network``, the console will only display the
public init(store: LoggerStore = .shared, mode: ConsoleMode = .all) {
self.init(environment: .init(store: store, mode: mode))
/// network messages.
/// - delegate: The delegate that allows you to customize multiple aspects
/// of the console view.
public init(
store: LoggerStore = .shared,
mode: ConsoleMode = .all,
delegate: ConsoleViewDelegate? = nil
) {
self.init(environment: .init(store: store, mode: mode, delegate: delegate ?? DefaultConsoleViewDelegate()))
}
}
Loading

0 comments on commit 95cc77f

Please sign in to comment.