Skip to content

Commit 15cdfbc

Browse files
committed
fix logging
1 parent 543e4a8 commit 15cdfbc

File tree

4 files changed

+149
-160
lines changed

4 files changed

+149
-160
lines changed

Sources/SwiftAPIClient/APIClientCaller.swift

Lines changed: 53 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import Logging
33
#if canImport(FoundationNetworking)
44
import FoundationNetworking
55
#endif
6+
import HTTPTypes
67

78
/// A generic structure for handling network requests and their responses.
89
public struct APIClientCaller<Response, Value, Result> {
910

10-
var logRequestByItSelf = false
11+
public var logRequestByItSelf = false
1112
private let _call: (
1213
UUID,
1314
HTTPRequestComponents,
@@ -116,6 +117,17 @@ public extension APIClientCaller where Result == Value {
116117
}
117118
}
118119

120+
extension APIClientCaller {
121+
122+
func mock(_ response: Response) -> APIClientCaller {
123+
APIClientCaller { [_mockResult] _, _, _, serialize in
124+
try _mockResult(serialize(response) {})
125+
} mockResult: { [_mockResult] value in
126+
try _mockResult(value)
127+
}
128+
}
129+
}
130+
119131
public extension APIClient {
120132

121133
/// Asynchronously performs a network call using the provided caller and serializer.
@@ -212,93 +224,71 @@ public extension APIClient {
212224
line: UInt = #line
213225
) throws -> Result {
214226
let uuid = UUID()
227+
let start = Date()
215228
do {
216229
return try withRequest { request, configs in
217230
let fileIDLine = configs.fileIDLine ?? FileIDLine(fileID: fileID, line: line)
218-
let configs = configs.with(\.fileIDLine, fileIDLine)
219-
231+
var configs = configs.with(\.fileIDLine, fileIDLine)
232+
220233
if let mock = try configs.getMockIfNeeded(for: Value.self, serializer: serializer) {
221-
#if canImport(Metrics)
222-
configs.with(\.reportMetrics, false).logRequest(request, uuid: uuid)
223-
#else
224-
configs.logRequest(request, uuid: uuid)
225-
#endif
234+
#if canImport(Metrics)
235+
configs = configs.with(\.reportMetrics, false)
236+
#endif
237+
configs.logRequestStarted(request, uuid: uuid)
226238
let result = try caller.mockResult(for: mock)
227-
configs.listener.onResponseSerialized(id: uuid, response: result, configs: configs)
239+
configs.logRequestCompleted(request, response: nil, data: nil, uuid: uuid, start: start, result: result)
228240
return result
229241
}
230242

231243
try configs.requestValidator.validate(request, configs.with(\.requestValidator, .alwaysSuccess))
232244
if !caller.logRequestByItSelf {
233-
configs.logRequest(request, uuid: uuid)
245+
configs.logRequestStarted(request, uuid: uuid)
234246
}
235247

236248
return try caller.call(uuid: uuid, request: request, configs: configs) { response, validate in
249+
let data = (response as? (Data, HTTPResponse))?.0 ?? (response as? Data)
237250
do {
238251
try validate()
239252
let result = try serializer.serialize(response, configs)
240-
#if canImport(Metrics)
241-
if configs.reportMetrics {
242-
updateTotalResponseMetrics(for: request, successful: true)
243-
}
244-
#endif
245-
configs.listener.onResponseSerialized(id: uuid, response: result, configs: configs)
253+
configs.logRequestCompleted(
254+
request,
255+
response: (response as? (Data, HTTPResponse))?.1,
256+
data: data,
257+
uuid: uuid,
258+
start: start,
259+
result: result
260+
)
246261
return result
247262
} catch {
248-
#if canImport(Metrics)
249-
if configs.reportMetrics {
250-
updateTotalResponseMetrics(for: request, successful: false)
263+
var error = error
264+
if let data, let failure = configs.errorDecoder.decodeError(data, configs) {
265+
error = failure
251266
}
252-
#endif
253-
254-
let context = APIErrorContext(
255-
request: request,
256-
response: response as? Data,
257-
fileIDLine: fileIDLine
267+
throw configs.logRequestFailed(
268+
request,
269+
response: (response as? (Data, HTTPResponse))?.1,
270+
data: data,
271+
start: start,
272+
uuid: uuid,
273+
error: error
258274
)
259-
do {
260-
if let data = response as? Data, let failure = configs.errorDecoder.decodeError(data, configs) {
261-
try configs.errorHandler(failure, configs, context)
262-
throw failure
263-
}
264-
try configs.errorHandler(error, configs, context)
265-
throw error
266-
} catch {
267-
configs.listener.onError(id: uuid, error: error, configs: configs)
268-
throw error
269-
}
270275
}
271276
}
272277
}
273278
} catch {
274-
do {
275-
try withConfigs { configs in
276-
let fileIDLine = configs.fileIDLine ?? FileIDLine(fileID: fileID, line: line)
277-
let configs = configs.with(\.fileIDLine, fileIDLine)
278-
if !configs._errorLoggingComponents.isEmpty {
279-
let message = configs._errorLoggingComponents.errorMessage(
280-
uuid: uuid,
281-
error: error,
282-
maskedHeaders: configs.logMaskedHeaders,
283-
fileIDLine: fileIDLine
284-
)
285-
configs.logger.log(level: configs._errorLogLevel, "\(message)")
286-
}
287-
#if canImport(Metrics)
288-
if configs.reportMetrics {
289-
updateTotalErrorsMetrics(for: nil)
290-
}
291-
#endif
292-
let context = APIErrorContext(fileIDLine: fileIDLine)
293-
try configs.errorHandler(error, configs, context)
294-
}
295-
throw error
296-
} catch {
297-
withConfigs { configs in
298-
configs.listener.onError(id: uuid, error: error, configs: configs)
299-
}
300-
throw error
279+
try withConfigs { configs in
280+
let fileIDLine = configs.fileIDLine ?? FileIDLine(fileID: fileID, line: line)
281+
let configs = configs.with(\.fileIDLine, fileIDLine)
282+
throw configs.logRequestFailed(
283+
nil,
284+
response: nil,
285+
data: nil,
286+
start: start,
287+
uuid: uuid,
288+
error: error
289+
)
301290
}
291+
throw error
302292
}
303293
}
304294
}

Sources/SwiftAPIClient/Clients/HTTPClient.swift

Lines changed: 12 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ extension APIClientCaller where Result == AsyncThrowingValue<(Value, HTTPRespons
121121
let requestWrapper = SendableValue(request)
122122
do {
123123
(value, response) = try await configs.httpClientMiddleware.execute(request: request, configs: configs) { request, configs in
124-
configs.logRequest(request, uuid: uuid)
124+
configs.logRequestStarted(request, uuid: uuid)
125125
await requestWrapper.set(request)
126126
let result = try await task(request, configs)
127127
configs.listener.onResponseReceived(id: uuid, response: result, configs: configs)
@@ -130,92 +130,19 @@ extension APIClientCaller where Result == AsyncThrowingValue<(Value, HTTPRespons
130130
}
131131
} catch {
132132
let request = await requestWrapper.value
133-
let response = await responseWrapper.value
134-
let duration = Date().timeIntervalSince(start)
135-
if !configs._errorLoggingComponents.isEmpty {
136-
let message = configs._errorLoggingComponents.errorMessage(
137-
uuid: uuid,
138-
error: error,
139-
request: request,
140-
duration: duration,
141-
maskedHeaders: configs.logMaskedHeaders,
142-
fileIDLine: configs.fileIDLine
143-
)
144-
configs.logger.log(level: configs._errorLogLevel, "\(message)")
145-
}
146-
#if canImport(Metrics)
147-
if configs.reportMetrics {
148-
updateHTTPMetrics(for: request, status: response?.1.status, duration: duration, successful: false)
149-
}
150-
#endif
151-
do {
152-
try configs.errorHandler(
153-
error,
154-
configs,
155-
APIErrorContext(
156-
request: request,
157-
response: response.flatMap { data($0.0) },
158-
status: response?.1.status,
159-
fileIDLine: configs.fileIDLine ?? FileIDLine()
160-
)
161-
)
162-
throw error
163-
} catch {
164-
configs.listener.onError(id: uuid, error: error, configs: configs)
165-
throw error
166-
}
133+
throw configs.logRequestFailed(
134+
request,
135+
response: nil,
136+
data: nil,
137+
start: start,
138+
uuid: uuid,
139+
error: error
140+
)
167141
}
168-
let request = await requestWrapper.value
169-
let duration = Date().timeIntervalSince(start)
170-
let data = data(value)
171-
do {
172-
let result = try serialize((value, response)) {
173-
try validate(value, response, configs)
174-
}
175-
let isError = response.status.kind.isError
176-
let logComponents = isError ? configs._errorLoggingComponents : configs.loggingComponents
177-
if !logComponents.isEmpty {
178-
let message = logComponents.responseMessage(
179-
for: response,
180-
uuid: uuid,
181-
request: request,
182-
data: data,
183-
duration: duration,
184-
maskedHeaders: configs.logMaskedHeaders,
185-
fileIDLine: configs.fileIDLine
186-
)
187-
configs.logger.log(
188-
level: isError ? configs._errorLogLevel : configs.logLevel,
189-
"\(message)"
190-
)
191-
}
192-
#if canImport(Metrics)
193-
if configs.reportMetrics {
194-
updateHTTPMetrics(for: request, status: response.status, duration: duration, successful: true)
195-
}
196-
#endif
197-
return (result, response)
198-
} catch {
199-
if !configs._errorLoggingComponents.isEmpty {
200-
let message = configs._errorLoggingComponents.responseMessage(
201-
for: response,
202-
uuid: uuid,
203-
request: request,
204-
data: data,
205-
duration: duration,
206-
error: error,
207-
maskedHeaders: configs.logMaskedHeaders,
208-
fileIDLine: configs.fileIDLine
209-
)
210-
configs.logger.log(level: configs._errorLogLevel, "\(message)")
211-
}
212-
#if canImport(Metrics)
213-
if configs.reportMetrics {
214-
updateHTTPMetrics(for: request, status: response.status, duration: duration, successful: false)
215-
}
216-
#endif
217-
throw error
142+
let result = try serialize((value, response)) {
143+
try validate(value, response, configs)
218144
}
145+
return (result, response)
219146
}
220147
} mockResult: { value in
221148
asyncWithResponse(value)

Sources/SwiftAPIClient/Modifiers/LoggingModifier.swift

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22
import Logging
3-
import HTTPTypesFoundation
3+
import HTTPTypes
44

55
public extension APIClient {
66

@@ -92,27 +92,99 @@ public extension APIClient.Configs {
9292
}
9393

9494
extension APIClient.Configs {
95-
95+
9696
var _errorLogLevel: Logger.Level {
9797
errorLogLevel ?? logLevel
9898
}
99-
99+
100100
var _errorLoggingComponents: LoggingComponents {
101101
errorLogginComponents ?? loggingComponents
102102
}
103-
104-
func logRequest(_ request: HTTPRequestComponents, uuid: UUID) {
103+
104+
public func logRequestStarted(_ request: HTTPRequestComponents, uuid: UUID) {
105105
if loggingComponents.contains(.onRequest), loggingComponents != .onRequest {
106106
let message = loggingComponents.requestMessage(for: request, uuid: uuid, maskedHeaders: logMaskedHeaders, fileIDLine: fileIDLine)
107107
logger.log(level: logLevel, "\(message)")
108108
}
109-
#if canImport(Metrics)
109+
#if canImport(Metrics)
110110
if reportMetrics {
111111
updateTotalRequestsMetrics(for: request)
112112
}
113-
#endif
113+
#endif
114114
listener.onRequestStarted(id: uuid, request: request, configs: self)
115115
}
116+
117+
public func logRequestFailed(
118+
_ request: HTTPRequestComponents?,
119+
response: HTTPResponse?,
120+
data: Data?,
121+
start: Date,
122+
uuid: UUID,
123+
error: Error
124+
) -> Error {
125+
let duration = Date().timeIntervalSince(start)
126+
if !_errorLoggingComponents.isEmpty {
127+
let message = _errorLoggingComponents.errorMessage(
128+
uuid: uuid,
129+
error: error,
130+
request: request,
131+
duration: duration,
132+
maskedHeaders: logMaskedHeaders,
133+
fileIDLine: fileIDLine
134+
)
135+
logger.log(level: _errorLogLevel, "\(message)")
136+
}
137+
#if canImport(Metrics)
138+
if reportMetrics {
139+
updateHTTPMetrics(for: request, status: response?.status, duration: duration, successful: false)
140+
}
141+
#endif
142+
do {
143+
try errorHandler(
144+
error,
145+
self,
146+
APIErrorContext(
147+
request: request,
148+
response: data,
149+
status: response?.status,
150+
fileIDLine: fileIDLine ?? FileIDLine()
151+
)
152+
)
153+
return error
154+
} catch {
155+
listener.onError(id: uuid, error: error, configs: self)
156+
return error
157+
}
158+
}
159+
160+
public func logRequestCompleted<T>(
161+
_ request: HTTPRequestComponents,
162+
response: HTTPResponse?,
163+
data: Data?,
164+
uuid: UUID,
165+
start: Date,
166+
result: T
167+
) {
168+
let duration = Date().timeIntervalSince(start)
169+
if !loggingComponents.isEmpty {
170+
let message = loggingComponents.responseMessage(
171+
for: response,
172+
uuid: uuid,
173+
request: request,
174+
data: data,
175+
duration: duration,
176+
maskedHeaders: logMaskedHeaders,
177+
fileIDLine: fileIDLine
178+
)
179+
logger.log(level: logLevel, "\(message)")
180+
}
181+
#if canImport(Metrics)
182+
if reportMetrics {
183+
updateHTTPMetrics(for: request, status: response?.status, duration: duration, successful: true)
184+
}
185+
#endif
186+
listener.onResponseSerialized(id: uuid, response: result, configs: self)
187+
}
116188
}
117189

118190
extension Set<HTTPField.Name> {

0 commit comments

Comments
 (0)