From 508137c755593f4560946ba7fe35212171b45c8a Mon Sep 17 00:00:00 2001 From: Andrey Frolov Date: Tue, 24 Dec 2024 17:51:33 +0100 Subject: [PATCH 1/4] improve logging system --- .../IfServerFailsFromCacheNode.swift | 16 ++-- .../CacheNode/URLNotModifiedTriggerNode.swift | 18 ++-- NodeKit/NodeKit/Chains/ChainBuilder.swift | 76 +++++++++++---- NodeKit/NodeKit/Core/Node/Node.swift | 6 -- .../URLJsonRequestEncodingNode.swift | 7 +- .../DTOProcessingLayer/DTOMapperNode.swift | 5 +- NodeKit/NodeKit/Layers/DefaultLogOrder.swift | 3 +- .../InputProcessingLayer/VoidIONode.swift | 2 +- .../InputProcessingLayer/VoidOutputNode.swift | 2 +- .../MultipartRequestCreatorNode.swift | 7 +- .../RequestCreatorNode.swift | 7 +- .../RequestSenderNode.swift | 15 +-- .../ResponseDataParserNode.swift | 9 +- .../ResponseDataPreprocessorNode.swift | 2 +- .../ResponseHttpErrorProcessorNode.swift | 42 ++++++++- .../ResponseProcessorNode.swift | 6 +- .../Layers/Utils/HeaderInjectorNode.swift | 4 +- NodeKit/NodeKit/Utils/Logging/Chain/Log.swift | 22 +++++ .../Utils/Logging/Chain/LogSession.swift | 16 ++++ .../NodeKit/Utils/Logging/Chain/LogType.swift | 11 +++ .../Utils/Logging/Chain/LoggingProxy.swift | 10 ++ .../Logging/{Log.swift => LogChain.swift} | 37 ++++++-- .../{Logable.swift => LogableChain.swift} | 24 ++--- .../NodeKit/Utils/Logging/LoggerNode.swift | 64 +++++++++++-- .../Utils/Logging/LoggerStreamNode.swift | 2 +- .../Utils/Logging/LoggingContext.swift | 53 +++++++++-- .../Builder/ChainBuilderMock.swift | 15 ++- NodeKit/NodeKitMock/LogSessionMock.swift | 25 +++++ NodeKit/NodeKitMock/LoggingContextMock.swift | 48 ++++++++-- NodeKit/NodeKitMock/LoggingProxyMock.swift | 24 +++++ .../Utils/Equatable/Log+Equatalbe.swift | 5 +- .../MultipartRequestTests.swift | 2 +- .../UnitTests/Logging/LogableTests.swift | 8 +- .../Logging/LoggingContextTests.swift | 92 +++++++++++++++---- .../UnitTests/Nodes/DTOMapperNodeTests.swift | 6 +- .../UnitTests/Nodes/LoggerNodeTests.swift | 72 ++++++++++++++- .../UnitTests/Nodes/VoidIONodeTests.swift | 9 +- .../UnitTests/Nodes/VoidOutputNodeTests.swift | 7 +- 38 files changed, 628 insertions(+), 151 deletions(-) create mode 100644 NodeKit/NodeKit/Utils/Logging/Chain/Log.swift create mode 100644 NodeKit/NodeKit/Utils/Logging/Chain/LogSession.swift create mode 100644 NodeKit/NodeKit/Utils/Logging/Chain/LogType.swift create mode 100644 NodeKit/NodeKit/Utils/Logging/Chain/LoggingProxy.swift rename NodeKit/NodeKit/Utils/Logging/{Log.swift => LogChain.swift} (63%) rename NodeKit/NodeKit/Utils/Logging/{Logable.swift => LogableChain.swift} (69%) create mode 100644 NodeKit/NodeKitMock/LogSessionMock.swift create mode 100644 NodeKit/NodeKitMock/LoggingProxyMock.swift diff --git a/NodeKit/NodeKit/CacheNode/IfServerFailsFromCacheNode.swift b/NodeKit/NodeKit/CacheNode/IfServerFailsFromCacheNode.swift index 0a3e0acc8..a729929b4 100644 --- a/NodeKit/NodeKit/CacheNode/IfServerFailsFromCacheNode.swift +++ b/NodeKit/NodeKit/CacheNode/IfServerFailsFromCacheNode.swift @@ -50,23 +50,23 @@ open class IfConnectionFailedFromCacheNode: AsyncNode { // MARK: - Private Method - private func makeBaseTechinalLog(with error: Error) -> Log { - return Log( - logViewObjectName + + private func makeBaseTechinalLog(with error: Error) -> LogChain { + return LogChain( "Catching \(error)" + .lineTabDeilimeter + "Start read cache" + .lineTabDeilimeter, - id: objectName + id: objectName, + logType: .failure ) } - private func makeLog(with error: Error, from request: URLNetworkRequest) -> Log { - return Log( - logViewObjectName + + private func makeLog(with error: Error, from request: URLNetworkRequest) -> LogChain { + return LogChain( "Catching \(error)" + .lineTabDeilimeter + "Error is \(type(of: error))" + "and request = \(String(describing: request))" + .lineTabDeilimeter + "-> throw error", - id: objectName + id: objectName, + logType: .failure ) } diff --git a/NodeKit/NodeKit/CacheNode/URLNotModifiedTriggerNode.swift b/NodeKit/NodeKit/CacheNode/URLNotModifiedTriggerNode.swift index d1dd3f514..c76406996 100644 --- a/NodeKit/NodeKit/CacheNode/URLNotModifiedTriggerNode.swift +++ b/NodeKit/NodeKit/CacheNode/URLNotModifiedTriggerNode.swift @@ -58,19 +58,21 @@ open class URLNotModifiedTriggerNode: AsyncNode { // MARK: - Private Methods - private func makeErrorLog(code: Int) -> Log { + private func makeErrorLog(code: Int) -> LogChain { let msg = "Response status code = \(code) != 304 -> skip cache reading" - return Log( - logViewObjectName + msg, - id: objectName + return LogChain( + msg, + id: objectName, + logType: .failure ) } - private func makeSuccessLog() -> Log { + private func makeSuccessLog() -> LogChain { let msg = "Response status code == 304 -> read cache" - return Log( - logViewObjectName + msg, - id: objectName + return LogChain( + msg, + id: objectName, + logType: .failure ) } } diff --git a/NodeKit/NodeKit/Chains/ChainBuilder.swift b/NodeKit/NodeKit/Chains/ChainBuilder.swift index 212da6d0a..856fdc82a 100644 --- a/NodeKit/NodeKit/Chains/ChainBuilder.swift +++ b/NodeKit/NodeKit/Chains/ChainBuilder.swift @@ -22,7 +22,8 @@ public protocol ChainBuilder { func encode(as encoding: ParametersEncoding) -> Self func add(provider: MetadataProvider) -> Self func set(metadata: [String: String]) -> Self - + func set(loggingProxy: LoggingProxy) -> Self + func build() -> AnyAsyncNode where I.DTO.Raw == Json, O.DTO.Raw == Json @@ -73,6 +74,9 @@ open class URLChainBuilder: ChainConfigBuilder, ChainBu /// Route to the remote method (specifically, the URL endpoint). public var route: Route? + /// Logging proxy. + open var loggingProxy: LoggingProxy? + // MARK: - Initialization @@ -179,15 +183,25 @@ open class URLChainBuilder: ChainConfigBuilder, ChainBu self.metadata = metadata return self } - + + open func set(loggingProxy: LoggingProxy) -> Self { + self.loggingProxy = loggingProxy + return self + } + open func build() -> AnyAsyncNode where I.DTO.Raw == Json, O.DTO.Raw == Json { let requestChain = serviceChainProvider.provideRequestJsonChain(with: headersProviders) let metadataConnectorChain = metadataConnectorChain(root: requestChain) let dtoConverterNode = DTOMapperNode(next: metadataConnectorChain) let modelInputNode = ModelInputNode(next: dtoConverterNode) - return LoggerNode(next: modelInputNode, filters: logFilter) - .eraseToAnyNode() + return LoggerNode( + next: modelInputNode, + method: method, + route: route, + loggingProxy: loggingProxy, + filters: logFilter + ).eraseToAnyNode() } open func build() -> AnyAsyncNode @@ -197,24 +211,39 @@ open class URLChainBuilder: ChainConfigBuilder, ChainBu let dtoConverterNode = DTOMapperNode(next: metadataConnectorChain) let modelInputNode = ModelInputNode(next: dtoConverterNode) let voidNode = VoidInputNode(next: modelInputNode) - return LoggerNode(next: voidNode, filters: logFilter) - .eraseToAnyNode() + return LoggerNode( + next: voidNode, + method: method, + route: route, + loggingProxy: loggingProxy, + filters: logFilter + ).eraseToAnyNode() } open func build() -> AnyAsyncNode where I.DTO.Raw == Json { let requestChain = serviceChainProvider.provideRequestJsonChain(with: headersProviders) let metadataConnectorChain = metadataConnectorChain(root: requestChain) let voidOutputNode = VoidOutputNode(next: metadataConnectorChain) - return LoggerNode(next: voidOutputNode, filters: logFilter) - .eraseToAnyNode() + return LoggerNode( + next: voidOutputNode, + method: method, + route: route, + loggingProxy: loggingProxy, + filters: logFilter + ).eraseToAnyNode() } open func build() -> AnyAsyncNode { let requestChain = serviceChainProvider.provideRequestJsonChain(with: headersProviders) let metadataConnectorChain = metadataConnectorChain(root: requestChain) let voidOutputNode = VoidIONode(next: metadataConnectorChain) - return LoggerNode(next: voidOutputNode, filters: logFilter) - .eraseToAnyNode() + return LoggerNode( + next: voidOutputNode, + method: method, + route: route, + loggingProxy: loggingProxy, + filters: logFilter + ).eraseToAnyNode() } open func build() -> AnyAsyncNode @@ -223,16 +252,26 @@ open class URLChainBuilder: ChainConfigBuilder, ChainBu let metadataConnectorChain = metadataConnectorChain(root: requestChain) let rawEncoderNode = DTOMapperNode(next: metadataConnectorChain) let dtoEncoderNode = ModelInputNode(next: rawEncoderNode) - return LoggerNode(next: dtoEncoderNode, filters: logFilter) - .eraseToAnyNode() + return LoggerNode( + next: dtoEncoderNode, + method: method, + route: route, + loggingProxy: loggingProxy, + filters: logFilter + ).eraseToAnyNode() } open func buildDataLoading() -> AnyAsyncNode { let requestChain = serviceChainProvider.provideRequestDataChain(with: headersProviders) let metadataConnectorChain = metadataConnectorChain(root: requestChain) let voidInputNode = VoidInputNode(next: metadataConnectorChain) - return LoggerNode(next: voidInputNode, filters: logFilter) - .eraseToAnyNode() + return LoggerNode( + next: voidInputNode, + method: method, + route: route, + loggingProxy: loggingProxy, + filters: logFilter + ).eraseToAnyNode() } open func buildDataLoading() -> AnyAsyncNode where I.DTO.Raw == Json { @@ -240,7 +279,12 @@ open class URLChainBuilder: ChainConfigBuilder, ChainBu let metadataConnectorChain = metadataConnectorChain(root: requestChain) let rawEncoderNode = RawEncoderNode(next: metadataConnectorChain) let dtoEncoderNode = DTOEncoderNode(rawEncodable: rawEncoderNode) - return LoggerNode(next: dtoEncoderNode, filters: logFilter) - .eraseToAnyNode() + return LoggerNode( + next: dtoEncoderNode, + method: method, + route: route, + loggingProxy: loggingProxy, + filters: logFilter + ).eraseToAnyNode() } } diff --git a/NodeKit/NodeKit/Core/Node/Node.swift b/NodeKit/NodeKit/Core/Node/Node.swift index c45c129b1..ce2390294 100644 --- a/NodeKit/NodeKit/Core/Node/Node.swift +++ b/NodeKit/NodeKit/Core/Node/Node.swift @@ -18,10 +18,4 @@ public extension Node { var objectName: String { return "\(type(of: self))" } - - /// Name of the object in following format: - /// <<<===\(self.objectName)===>>>" + `String.lineTabDeilimeter` - var logViewObjectName: String { - return "<<<===\(self.objectName)===>>>" + .lineTabDeilimeter - } } diff --git a/NodeKit/NodeKit/Encodings/URLJsonRequestEncodingNode.swift b/NodeKit/NodeKit/Encodings/URLJsonRequestEncodingNode.swift index ff67587e5..ec20d3633 100644 --- a/NodeKit/NodeKit/Encodings/URLJsonRequestEncodingNode.swift +++ b/NodeKit/NodeKit/Encodings/URLJsonRequestEncodingNode.swift @@ -56,11 +56,10 @@ open class URLJsonRequestEncodingNode: AsyncNode { } - private func getLogMessage(_ data: RequestEncodingModel) -> Log { - let message = "<<<===\(self.objectName)===>>>\n" + - "input: \(type(of: data))" + + private func getLogMessage(_ data: RequestEncodingModel) -> LogChain { + let message = "input: \(type(of: data))" + "encoding: \(String(describing: data.encoding))" + "raw: \(String(describing: data.raw))" - return Log(message, id: self.objectName, order: LogOrder.requestEncodingNode) + return LogChain(message, id: self.objectName, logType: .info, order: LogOrder.requestEncodingNode) } } diff --git a/NodeKit/NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift b/NodeKit/NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift index ffae14745..06ee33632 100644 --- a/NodeKit/NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift +++ b/NodeKit/NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift @@ -52,9 +52,10 @@ open class DTOMapperNode: AsyncNode where Input: RawEncodable, Ou } private func log(error: Error, logContext: LoggingContextProtocol) async { - let log = Log( - logViewObjectName + "\(error)", + let log = LogChain( + "\(error)", id: objectName, + logType: .failure, order: LogOrder.dtoMapperNode ) await logContext.add(log) diff --git a/NodeKit/NodeKit/Layers/DefaultLogOrder.swift b/NodeKit/NodeKit/Layers/DefaultLogOrder.swift index 88c5a129d..005b0c4a6 100644 --- a/NodeKit/NodeKit/Layers/DefaultLogOrder.swift +++ b/NodeKit/NodeKit/Layers/DefaultLogOrder.swift @@ -23,6 +23,7 @@ public enum LogOrder { public static let responseHttpErrorProcessorNode = 57.0 public static let responseDataParserNode = 59.0 - + public static let dtoMapperNode = 100.0 + public static let loggerNode = 200.0 } diff --git a/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidIONode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidIONode.swift index 736e3a674..90be5da45 100644 --- a/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidIONode.swift +++ b/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidIONode.swift @@ -23,7 +23,7 @@ open class VoidIONode: AsyncNode { await .withCheckedCancellation { await next.process(Json(), logContext: logContext).asyncFlatMap { json in if !json.isEmpty { - var log = Log(logViewObjectName, id: objectName, order: LogOrder.voidIONode) + var log = LogChain("", id: objectName, logType: .info, order: LogOrder.voidIONode) log += "VoidIOtNode used but request have not empty response" log += .lineTabDeilimeter log += "\(json)" diff --git a/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift index 459d09c95..42f0e6dc4 100644 --- a/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift +++ b/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift @@ -27,7 +27,7 @@ open class VoidOutputNode: AsyncNode where Input: DTOEncodable, Input.DTO await .withCheckedCancellation { await next.process(raw, logContext: logContext).asyncFlatMap { json in if !json.isEmpty { - var log = Log(logViewObjectName, id: objectName, order: LogOrder.voidOutputNode) + var log = LogChain("", id: objectName, logType: .info, order: LogOrder.voidOutputNode) log += "VoidOutputNode used but request have not empty response" log += .lineTabDeilimeter log += "\(json)" diff --git a/NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift index da4cb67e4..daf04e627 100644 --- a/NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift +++ b/NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift @@ -76,15 +76,14 @@ open class MultipartRequestCreatorNode: AsyncNode { } } - private func getLogMessage(_ data: MultipartURLRequest) -> Log { - var message = "<<<===\(self.objectName)===>>>\n" - message += "input: \(type(of: data))\n\t" + private func getLogMessage(_ data: MultipartURLRequest) -> LogChain { + var message = "input: \(type(of: data))\n\t" message += "method: \(data.method.rawValue)\n\t" message += "url: \(data.url.absoluteString)\n\t" message += "headers: \(data.headers)\n\t" message += "parametersEncoding: multipart)" - return Log(message, id: self.objectName, order: LogOrder.requestCreatorNode) + return LogChain(message, id: self.objectName, logType: .info, order: LogOrder.requestCreatorNode) } open func append(multipartForm: MultipartFormDataProtocol, with request: MultipartURLRequest) { diff --git a/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift index 0e3c3cd00..6f395855a 100644 --- a/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift +++ b/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift @@ -41,14 +41,13 @@ open class RequestCreatorNode: AsyncNode { } } - private func getLogMessage(_ data: TransportURLRequest) -> Log { - var message = "<<<===\(self.objectName)===>>>\n" - message += "input: \(type(of: data))\n\t" + private func getLogMessage(_ data: TransportURLRequest) -> LogChain { + var message = "input: \(type(of: data))\n\t" message += "method: \(data.method.rawValue)\n\t" message += "url: \(data.url.absoluteString)\n\t" message += "headers: \(data.headers)\n\t" message += "raw: \(String(describing: data.raw))\n\t" - return Log(message, id: self.objectName, order: LogOrder.requestCreatorNode) + return LogChain(message, id: self.objectName, logType: .info, order: LogOrder.requestCreatorNode) } } diff --git a/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift index ee43448bb..92b324f0a 100644 --- a/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift +++ b/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift @@ -50,15 +50,15 @@ open class RequestSenderNode: AsyncNode, Aborter { logContext: LoggingContextProtocol ) async -> NodeResult { await .withCheckedCancellation { - var log = Log(logViewObjectName, id: objectName, order: LogOrder.requestSenderNode) - + var log = LogChain("", id: objectName, logType: .info, order: LogOrder.requestSenderNode) + async let nodeResponse = nodeResponse(request, logContext: logContext) - log += "Request sended!" - + log += "Request sended!" + .lineTabDeilimeter + let response = await nodeResponse - log += "Get response!)" + log += "Got response! \(response.result)" let result = await rawResponseProcessor.process(response, logContext: logContext) @@ -68,9 +68,10 @@ open class RequestSenderNode: AsyncNode, Aborter { } open func cancel(logContext: LoggingContextProtocol) { - let log = Log( - logViewObjectName + "Request was cancelled!", + let log = LogChain( + "Request was cancelled!", id: objectName, + logType: .info, order: LogOrder.requestSenderNode ) Task.detached { [weak self] in diff --git a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift index cc3d4b322..2f2359d10 100644 --- a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift +++ b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift @@ -40,19 +40,16 @@ open class ResponseDataParserNode: AsyncNode { await .withCheckedCancellation { await parse(with: data, logContext: logContext) .asyncFlatMap { json, logMessage in - let logMsg = logViewObjectName + logMessage + .lineTabDeilimeter - var log = Log(logMsg, id: objectName, order: LogOrder.responseDataParserNode) + let logMsg = logMessage + .lineTabDeilimeter + var log = LogChain(logMsg, id: objectName, logType: .info, order: LogOrder.responseDataParserNode) guard let next = next else { - log += "Next node is nil -> terminate chain process" await logContext.add(log) return .success(json) } let networkResponse = URLProcessedResponse(dataResponse: data, json: json) - log += "Have next node \(next.objectName) -> call `process`" - await logContext.add(log) await next.process(networkResponse, logContext: logContext) @@ -116,7 +113,7 @@ open class ResponseDataParserNode: AsyncNode { let result = try json(from: data) return .success(result) } catch { - var log = Log(logViewObjectName, id: objectName, order: LogOrder.responseDataParserNode) + var log = LogChain("", id: objectName, logType: .failure, order: LogOrder.responseDataParserNode) switch error { case ResponseDataParserNodeError.cantCastDesirializedDataToJson(let logMsg), ResponseDataParserNodeError.cantDeserializeJson(let logMsg): diff --git a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift index 9ce504823..f72958064 100644 --- a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift +++ b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift @@ -31,7 +31,7 @@ open class ResponseDataPreprocessorNode: AsyncNode { logContext: LoggingContextProtocol ) async -> NodeResult { await .withCheckedCancellation { - var log = Log(logViewObjectName, id: objectName, order: LogOrder.responseDataPreprocessorNode) + var log = LogChain("", id: objectName, logType: .info, order: LogOrder.responseDataPreprocessorNode) guard data.response.statusCode != 204 else { log += "Status code is 204 -> response data is empty -> terminate process with empty json" diff --git a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift index 56a2d9c3b..79b248e93 100644 --- a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift +++ b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift @@ -51,19 +51,59 @@ open class ResponseHttpErrorProcessorNode: AsyncNode { await .withCheckedCancellation { switch data.response.statusCode { case 400: + let log = LogChain( + "Match with 400 status code (badRequest)", + id: objectName, + logType: .failure, + order: LogOrder.responseHttpErrorProcessorNode + ) + await logContext.add(log) return .failure(HttpError.badRequest(data.data)) case 401: + let log = LogChain( + "Match with 401 status code (unauthorized)", + id: objectName, + logType: .failure, + order: LogOrder.responseHttpErrorProcessorNode + ) + await logContext.add(log) return .failure(HttpError.unauthorized(data.data)) case 403: + let log = LogChain( + "Match with 403 status code (forbidden)", + id: objectName, + logType: .failure, + order: LogOrder.responseHttpErrorProcessorNode + ) + await logContext.add(log) return .failure(HttpError.forbidden(data.data)) case 404: + let log = LogChain( + "Match with 404 status code (notFound)", + id: objectName, + logType: .failure, + order: LogOrder.responseHttpErrorProcessorNode + ) + await logContext.add(log) return .failure(HttpError.notFound) case 500: + let log = LogChain( + "Match with 500 status code (internalServerError)", + id: objectName, + logType: .failure, + order: LogOrder.responseHttpErrorProcessorNode + ) + await logContext.add(log) return .failure(HttpError.internalServerError(data.data)) default: break } - let log = Log(logViewObjectName + "Cant match status code -> call next", id: objectName) + let log = LogChain( + "Cant match status code -> call next", + id: objectName, + logType: .info, + order: LogOrder.responseHttpErrorProcessorNode + ) await logContext.add(log) return await next.process(data, logContext: logContext) } diff --git a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift index be3c700bd..68536b62c 100644 --- a/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift +++ b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift @@ -36,13 +36,14 @@ open class ResponseProcessorNode: AsyncNode { logContext: LoggingContextProtocol ) async -> NodeResult { await .withCheckedCancellation { - var log = Log(logViewObjectName, id: objectName, order: LogOrder.responseProcessorNode) + var log = LogChain("", id: objectName, logType: .info, order: LogOrder.responseProcessorNode) switch data.result { case .failure(let error): - log += "Catch URLSeesions error: \(error)" + .lineTabDeilimeter + log += "Catch URLSession error: \(error)" + .lineTabDeilimeter guard let urlResponse = data.urlResponse, let urlRequest = data.urlRequest else { + log.update(logType: .failure) await logContext.add(log) return .failure(error) } @@ -69,6 +70,7 @@ open class ResponseProcessorNode: AsyncNode { let urlResponse = data.urlResponse, let urlRequest = data.urlRequest else { + log.update(logType: .failure) log += "But cant extract parameters -> terminate with error" await logContext.add(log) return .failure(ResponseProcessorNodeError.rawResponseNotHaveMetaData) diff --git a/NodeKit/NodeKit/Layers/Utils/HeaderInjectorNode.swift b/NodeKit/NodeKit/Layers/Utils/HeaderInjectorNode.swift index 4ca3e2319..08d654408 100644 --- a/NodeKit/NodeKit/Layers/Utils/HeaderInjectorNode.swift +++ b/NodeKit/NodeKit/Layers/Utils/HeaderInjectorNode.swift @@ -35,7 +35,7 @@ open class HeaderInjectorNode: AsyncNode { ) async -> NodeResult { await .withCheckedCancellation { var resultHeaders = headers - var log = logViewObjectName + var log = "" log += "Add headers \(headers)" + .lineTabDeilimeter log += "To headers \(data.headers)" + .lineTabDeilimeter data.headers.forEach { resultHeaders[$0.key] = $0.value } @@ -46,7 +46,7 @@ open class HeaderInjectorNode: AsyncNode { raw: data.raw ) log += "Result headers: \(resultHeaders)" - await logContext.add(Log(log, id: objectName)) + await logContext.add(LogChain(log, id: objectName, logType: .info)) return await next.process(newData, logContext: logContext) } } diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/Log.swift b/NodeKit/NodeKit/Utils/Logging/Chain/Log.swift new file mode 100644 index 000000000..beffd9f1d --- /dev/null +++ b/NodeKit/NodeKit/Utils/Logging/Chain/Log.swift @@ -0,0 +1,22 @@ +// +// Log.swift +// +// +// Created by frolov on 24.12.2024. +// + +public protocol Log { + + /// The order of the log in the chain. Necessary for sorting. + var order: Double { get } + + /// Log identifier. + var id: String { get } + + /// The content of this log. + var message: String { get } + + /// Type of the log + var logType: LogType { get } + +} diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/LogSession.swift b/NodeKit/NodeKit/Utils/Logging/Chain/LogSession.swift new file mode 100644 index 000000000..d49aeea4b --- /dev/null +++ b/NodeKit/NodeKit/Utils/Logging/Chain/LogSession.swift @@ -0,0 +1,16 @@ +// +// LogSession.swift +// +// +// Created by frolov on 24.12.2024. +// + +public protocol LogSession: Actor { + + /// Request Method + var method: Method? { get } + /// Request Route + var route: URLRouteProvider? { get } + + func subscribe(_ subscription: @escaping ([Log]) -> Void) +} diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/LogType.swift b/NodeKit/NodeKit/Utils/Logging/Chain/LogType.swift new file mode 100644 index 000000000..e982d18c6 --- /dev/null +++ b/NodeKit/NodeKit/Utils/Logging/Chain/LogType.swift @@ -0,0 +1,11 @@ +// +// LogType.swift +// +// +// Created by frolov on 24.12.2024. +// + +public enum LogType { + case failure + case info +} diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/LoggingProxy.swift b/NodeKit/NodeKit/Utils/Logging/Chain/LoggingProxy.swift new file mode 100644 index 000000000..f112c2e53 --- /dev/null +++ b/NodeKit/NodeKit/Utils/Logging/Chain/LoggingProxy.swift @@ -0,0 +1,10 @@ +// +// LoggingProxy.swift +// +// +// Created by frolov on 24.12.2024. +// + +public protocol LoggingProxy { + func handle(session: LogSession) +} diff --git a/NodeKit/NodeKit/Utils/Logging/Log.swift b/NodeKit/NodeKit/Utils/Logging/LogChain.swift similarity index 63% rename from NodeKit/NodeKit/Utils/Logging/Log.swift rename to NodeKit/NodeKit/Utils/Logging/LogChain.swift index 26b661588..6fada94c3 100644 --- a/NodeKit/NodeKit/Utils/Logging/Log.swift +++ b/NodeKit/NodeKit/Utils/Logging/LogChain.swift @@ -1,6 +1,5 @@ // -// Log.swift -// CoreNetKit +// LogChain.swift // // Created by Александр Кравченков on 07/04/2019. // Copyright © 2019 Кравченков Александр. All rights reserved. @@ -9,7 +8,7 @@ import Foundation /// Structure describing the work log. -public struct Log: Logable { +public struct LogChain: LogableChain { /// The order of the log in the chain. Necessary for sorting. public var order: Double = 0 @@ -19,7 +18,7 @@ public struct Log: Logable { public var delimeter: String /// Next log. - public var next: Logable? + public var next: LogableChain? /// The content of this log. public var message: String @@ -27,25 +26,42 @@ public struct Log: Logable { /// Log identifier. public var id: String + public var logType: LogType + /// Initializes the object. /// /// - Parameters: /// - message: The content of this log. + /// - id: Log identificator. + /// - logType: Type of the log. /// - delimeter: Separator to be inserted between logs. By default, it is equal to `\n`. - public init(_ message: String, id: String, delimeter: String = "\n", order: Double = 0) { + /// - order: Log order. + public init( + _ message: String, + id: String, + logType: LogType, + delimeter: String = "\n", + order: Double = 0 + ) { self.message = message self.delimeter = delimeter self.id = id + self.logType = logType self.order = order } /// Appends `delimeter` to its own `message`, then appends `next.description` to the resulting string. public var description: String { let result = self.delimeter + message + self.delimeter - return result + (self.next?.description ?? "") } + /// Formatted description with id. + public var printString: String { + let id = "<<<===\(self.id)===>>>" + .lineTabDeilimeter + return id + description + } + /// Adds a message to the log. /// /// - Parameter message: Log message. @@ -53,8 +69,15 @@ public struct Log: Logable { self.message += message } + /// Updates log type. + /// + /// - Parameter logType: Type of the log. + mutating public func update(logType: LogType) { + self.logType = logType + } + /// Syntactic sugar for add(message:) - public static func += (lhs: inout Log, rhs: String) { + public static func += (lhs: inout LogChain, rhs: String) { lhs.add(message: rhs) } } diff --git a/NodeKit/NodeKit/Utils/Logging/Logable.swift b/NodeKit/NodeKit/Utils/Logging/LogableChain.swift similarity index 69% rename from NodeKit/NodeKit/Utils/Logging/Logable.swift rename to NodeKit/NodeKit/Utils/Logging/LogableChain.swift index 4b9d8ab0b..c8c9d3753 100644 --- a/NodeKit/NodeKit/Utils/Logging/Logable.swift +++ b/NodeKit/NodeKit/Utils/Logging/LogableChain.swift @@ -1,6 +1,5 @@ // -// LogWrapper.swift -// CoreNetKit +// LogableChain.swift // // Created by Александр Кравченков on 07/04/2019. // Copyright © 2019 Кравченков Александр. All rights reserved. @@ -9,32 +8,29 @@ import Foundation /// Describes an entity that contains a description for the work log. -public protocol Logable { - - /// The order of the log in the chain. Necessary for sorting. - var order: Double { get } +public protocol LogableChain: Log { /// Next log. - var next: Logable? { get set } - - /// Log identifier. - var id: String { get } + var next: LogableChain? { get set } /// Entire chain of logs with the specified formatting. var description: String { get } + /// Formatted description with id. + var printString: String { get } + /// Adds a message to the log. /// /// - Parameter message: Log message. mutating func add(message: String) } -extension Logable { +extension LogableChain { /// Converts a tree-like structure of log entries into an array /// using non-recursive depth-first traversal. - func flatMap() -> [Logable] { - var currentLogable: Logable? = self - var result = [Logable]() + func flatMap() -> [LogableChain] { + var currentLogable: LogableChain? = self + var result = [LogableChain]() while currentLogable != nil { guard var log = currentLogable else { break } currentLogable = log.next diff --git a/NodeKit/NodeKit/Utils/Logging/LoggerNode.swift b/NodeKit/NodeKit/Utils/Logging/LoggerNode.swift index 2b83a7e2c..43e71ee6d 100644 --- a/NodeKit/NodeKit/Utils/Logging/LoggerNode.swift +++ b/NodeKit/NodeKit/Utils/Logging/LoggerNode.swift @@ -9,13 +9,31 @@ open class LoggerNode: AsyncNode { ///List of keys by which the log will be filtered. open var filters: [String] + /// Request method. + private let method: Method? + /// URL route. + private let route: URLRouteProvider? + /// Logging proxy. + private let loggingProxy: LoggingProxy? + /// Initializer. /// /// - Parameters: /// - next: The next node for processing. + /// - method: Request method. + /// - route: URL route. /// - filters: List of keys by which the log will be filtered. - public init(next: any AsyncNode, filters: [String] = []) { + public init( + next: any AsyncNode, + method: Method?, + route: URLRouteProvider?, + loggingProxy: LoggingProxy?, + filters: [String] = [] + ) { self.next = next + self.method = method + self.route = route + self.loggingProxy = loggingProxy self.filters = filters } @@ -26,15 +44,45 @@ open class LoggerNode: AsyncNode { _ data: Input, logContext: LoggingContextProtocol ) async -> NodeResult { + await prepare(logContext: logContext) let result = await next.process(data, logContext: logContext) - - await logContext.log?.flatMap() - .filter { !filters.contains($0.id) } - .sorted(by: { $0.order < $1.order }) - .forEach { - print($0.description) + .asyncFlatMapError { + await set(error: $0, in: logContext) + return .failure($0) } - + await finish(logContext: logContext) return result } + +} + +private extension LoggerNode { + + func prepare(logContext: LoggingContextProtocol) async { + await logContext.set(method: method, route: route) + loggingProxy?.handle(session: logContext) + } + + func set(error: Error, in logContext: LoggingContextProtocol) async { + let log = LogChain( + "Request failed: \(error)", + id: objectName, + logType: .failure, + order: LogOrder.loggerNode + ) + await logContext.add(log) + } + + func finish(logContext: LoggingContextProtocol) async { + await logContext.complete() + if loggingProxy == nil { + await logContext.log?.flatMap() + .filter { !filters.contains($0.id) } + .sorted(by: { $0.order < $1.order }) + .forEach { + print($0.printString) + } + } + } + } diff --git a/NodeKit/NodeKit/Utils/Logging/LoggerStreamNode.swift b/NodeKit/NodeKit/Utils/Logging/LoggerStreamNode.swift index 0480e4293..5e17c60f3 100644 --- a/NodeKit/NodeKit/Utils/Logging/LoggerStreamNode.swift +++ b/NodeKit/NodeKit/Utils/Logging/LoggerStreamNode.swift @@ -37,7 +37,7 @@ class LoggerStreamNode: AsyncStreamNode { await logContext.log?.flatMap() .filter { !filters.contains($0.id) } .sorted(by: { $0.order < $1.order }) - .forEach { print($0.description) } + .forEach { print($0.printString) } continuation.finish() } continuation.onTermination = { _ in diff --git a/NodeKit/NodeKit/Utils/Logging/LoggingContext.swift b/NodeKit/NodeKit/Utils/Logging/LoggingContext.swift index f506d90a8..55c840023 100644 --- a/NodeKit/NodeKit/Utils/Logging/LoggingContext.swift +++ b/NodeKit/NodeKit/Utils/Logging/LoggingContext.swift @@ -8,19 +8,37 @@ import Foundation -public protocol LoggingContextProtocol: Actor { +public protocol LoggingContextProtocol: LogSession { /// Root log. - var log: Logable? { get } + var log: LogableChain? { get } /// Adds a log message to the context. /// - Parameter log: Log message. - func add(_ log: Logable?) + func add(_ log: LogableChain?) + + /// Sets method and route to the log context. + /// - Parameters: + /// - method: Request method. + /// - route: URL route. + func set(method: Method?, route: URLRouteProvider?) + + /// Notify subscriptions about completion. + func complete() } public actor LoggingContext: LoggingContextProtocol { + /// Request Method + public private(set) var method: Method? + + /// Request Route + public private(set) var route: URLRouteProvider? + /// Root log. - public private(set) var log: Logable? + public private(set) var log: LogableChain? + + /// Log subscriptions. + private var logSubscriptions: [([Log]) -> Void] = [] /// Adds a log message to the context. /// If the context did not have a root log, the passed log will become the root. @@ -28,7 +46,7 @@ public actor LoggingContext: LoggingContextProtocol { /// If there was a root log and it had a next log, then the passed log will be inserted between them. /// /// - Parameter log: Log message. - public func add(_ log: Logable?) { + public func add(_ log: LogableChain?) { guard var currentLog = self.log else { self.log = log return @@ -43,7 +61,30 @@ public actor LoggingContext: LoggingContextProtocol { } self.log = currentLog - return + } + + /// Sets method and route to the log context. + /// - Parameters: + /// - method: Request method. + /// - route: URL route. + public func set(method: Method?, route: URLRouteProvider?) { + self.method = method + self.route = route + } + + /// Add subscriptions for logs. + public func subscribe(_ subscription: @escaping ([Log]) -> Void) { + logSubscriptions.append(subscription) + } + + /// Notify subscriptions about completion. + public func complete() { + guard let logs = log?.flatMap().sorted(by: { $0.order < $1.order }) else { + return + } + logSubscriptions.forEach { subscription in + subscription(logs) + } } } diff --git a/NodeKit/NodeKitMock/Builder/ChainBuilderMock.swift b/NodeKit/NodeKitMock/Builder/ChainBuilderMock.swift index 34a888ef3..f73875578 100644 --- a/NodeKit/NodeKitMock/Builder/ChainBuilderMock.swift +++ b/NodeKit/NodeKitMock/Builder/ChainBuilderMock.swift @@ -63,7 +63,20 @@ open class ChainBuilderMock: ChainConfigBuilderMock, Ch invokedSetMetadataParameterList.append(metadata) return self } - + + public var invokedSetLogProxy = false + public var invokedSetLogProxyCount = 0 + public var invokedSetLogProxyParameter: LoggingProxy? + public var invokedSetLogProxyParameterList: [LoggingProxy] = [] + + public func set(loggingProxy: any LoggingProxy) -> Self { + invokedSetLogProxy = true + invokedSetLogProxyCount += 1 + invokedSetLogProxyParameter = loggingProxy + invokedSetLogProxyParameterList.append(loggingProxy) + return self + } + public var invokedBuildWithInputOutput = false public var invokedBuildWithInputOutputCount = 0 public var stubbedBuildWithInputOutputResult: Any! diff --git a/NodeKit/NodeKitMock/LogSessionMock.swift b/NodeKit/NodeKitMock/LogSessionMock.swift new file mode 100644 index 000000000..342a26875 --- /dev/null +++ b/NodeKit/NodeKitMock/LogSessionMock.swift @@ -0,0 +1,25 @@ +// +// LogSession.swift +// +// +// Created by frolov on 24.12.2024. +// + +import NodeKit + +public actor LogSessionMock: LogSession { + public var method: Method? + public var route: (URLRouteProvider)? + + public var invokedSubscribe = false + public var invokedSubscribeCount = 0 + public var invokedSubscribeParameter: (([Log]) -> Void)? + public var invokedSubscribeParameterList: [([Log]) -> Void] = [] + + public func subscribe(_ subscription: @escaping ([Log]) -> Void) { + invokedSubscribe = true + invokedSubscribeCount += 1 + invokedSubscribeParameter = subscription + invokedSubscribeParameterList.append(subscription) + } +} diff --git a/NodeKit/NodeKitMock/LoggingContextMock.swift b/NodeKit/NodeKitMock/LoggingContextMock.swift index 94634fa2a..f00aa8847 100644 --- a/NodeKit/NodeKitMock/LoggingContextMock.swift +++ b/NodeKit/NodeKitMock/LoggingContextMock.swift @@ -9,20 +9,54 @@ import NodeKit public actor LoggingContextMock: LoggingContextProtocol { - + public var method: Method? + public var route: URLRouteProvider? + public init() { } - public private(set) var log: (Logable)? - + public private(set) var log: (LogableChain)? + public var invokedAdd = false public var invokedAddCount = 0 - public var invokedAddParameter: Logable? - public var invokedAddParameterList: [Logable?] = [] - - public func add(_ log: Logable?) { + public var invokedAddParameter: LogableChain? + public var invokedAddParameterList: [LogableChain?] = [] + + public func add(_ log: LogableChain?) { invokedAdd = true invokedAddCount += 1 invokedAddParameter = log invokedAddParameterList.append(log) } + + public var invokedSet = false + public var invokedSetCount = 0 + public var invokedSetParameter: (Method?, URLRouteProvider?)? + public var invokedSetParameterList: [(Method?, URLRouteProvider?)] = [] + + public func set(method: Method?, route: URLRouteProvider?) { + invokedSet = true + invokedSetCount += 1 + invokedSetParameter = (method, route) + invokedSetParameterList.append((method, route)) + } + + public var invokedSubscribe = false + public var invokedSubscribeCount = 0 + public var invokedSubscribeParameter: (([Log]) -> Void)? + public var invokedSubscribeParameterList: [([Log]) -> Void] = [] + + public func subscribe(_ subscription: @escaping ([Log]) -> Void) { + invokedSubscribe = true + invokedSubscribeCount += 1 + invokedSubscribeParameter = subscription + invokedSubscribeParameterList.append(subscription) + } + + public var invokedComplete = false + public var invokedCompleteCount = 0 + + public func complete() { + invokedComplete = true + invokedCompleteCount += 1 + } } diff --git a/NodeKit/NodeKitMock/LoggingProxyMock.swift b/NodeKit/NodeKitMock/LoggingProxyMock.swift new file mode 100644 index 000000000..e250541b1 --- /dev/null +++ b/NodeKit/NodeKitMock/LoggingProxyMock.swift @@ -0,0 +1,24 @@ +// +// LoggingProxy.swift +// +// +// Created by frolov on 24.12.2024. +// + +import NodeKit + +public class LoggingProxyMock: LoggingProxy { + + public var invokedHandle = false + public var invokedHandleCount = 0 + public var invokedHandleParameter: LogSession? + public var invokedHandleParameterList: [LogSession] = [] + + public func handle(session: LogSession) { + invokedHandle = true + invokedHandleCount += 1 + invokedHandleParameter = session + invokedHandleParameterList.append(session) + } + +} diff --git a/NodeKit/NodeKitMock/Utils/Equatable/Log+Equatalbe.swift b/NodeKit/NodeKitMock/Utils/Equatable/Log+Equatalbe.swift index 85d9d543e..7164e94ec 100644 --- a/NodeKit/NodeKitMock/Utils/Equatable/Log+Equatalbe.swift +++ b/NodeKit/NodeKitMock/Utils/Equatable/Log+Equatalbe.swift @@ -8,12 +8,13 @@ @testable import NodeKit -extension Log: Equatable { +extension LogChain: Equatable { - public static func == (lhs: NodeKit.Log, rhs: NodeKit.Log) -> Bool { + public static func == (lhs: LogChain, rhs: LogChain) -> Bool { return lhs.message == rhs.message && lhs.description == rhs.description && lhs.delimeter == rhs.delimeter && + lhs.logType == rhs.logType && lhs.id == rhs.id && lhs.order == rhs.order } diff --git a/NodeKit/NodeKitTests/IntegrationTests/MultipartRequestTests.swift b/NodeKit/NodeKitTests/IntegrationTests/MultipartRequestTests.swift index 5d506678e..97de06b20 100644 --- a/NodeKit/NodeKitTests/IntegrationTests/MultipartRequestTests.swift +++ b/NodeKit/NodeKitTests/IntegrationTests/MultipartRequestTests.swift @@ -109,7 +109,7 @@ final class MultipartRequestTests: XCTestCase { func testFileSendsCorrectly() async throws { // given - let url = Bundle(for: type(of: self)).url(forResource: "LICENSE", withExtension: "txt")! + let url = Bundle(for: type(of: self)).url(forResource: "Info", withExtension: "plist")! let model = MultipartModel(payloadModel: TestData(data: [:]) ,files: [ "file": .url(url: url) ]) diff --git a/NodeKit/NodeKitTests/UnitTests/Logging/LogableTests.swift b/NodeKit/NodeKitTests/UnitTests/Logging/LogableTests.swift index 30f33dcaf..d21d98469 100644 --- a/NodeKit/NodeKitTests/UnitTests/Logging/LogableTests.swift +++ b/NodeKit/NodeKitTests/UnitTests/Logging/LogableTests.swift @@ -17,10 +17,10 @@ final class LogableTests: XCTestCase { func testFlatMap_thenCorrectResultReceived() throws { // given - var firstLog = Log("First message", id: "1", delimeter: "/", order: 0) - var secondLog = Log("Second message", id: "1", delimeter: "/", order: 0) - let thirdLog = Log("Third message", id: "1", delimeter: "/", order: 0) - + var firstLog = LogChain("First message", id: "1", logType: .info, delimeter: "/", order: 0) + var secondLog = LogChain("Second message", id: "1", logType: .failure, delimeter: "/", order: 0) + let thirdLog = LogChain("Third message", id: "1", logType: .info, delimeter: "/", order: 0) + secondLog.next = thirdLog firstLog.next = secondLog diff --git a/NodeKit/NodeKitTests/UnitTests/Logging/LoggingContextTests.swift b/NodeKit/NodeKitTests/UnitTests/Logging/LoggingContextTests.swift index be7652887..d4fd14f75 100644 --- a/NodeKit/NodeKitTests/UnitTests/Logging/LoggingContextTests.swift +++ b/NodeKit/NodeKitTests/UnitTests/Logging/LoggingContextTests.swift @@ -43,14 +43,14 @@ final class LoggingContextTests: XCTestCase { func testLog_whenOneItemAppending_thenResultIsAppendedItem() async throws { // given - let testLog = Log("Test message", id: "Test id") + let testLog = LogChain("Test message", id: "Test id", logType: .failure) // when await sut.add(testLog) let log = await sut.log - let result = try XCTUnwrap(log as? Log) + let result = try XCTUnwrap(log as? LogChain) // then @@ -60,8 +60,8 @@ final class LoggingContextTests: XCTestCase { func testLog_whenTwoItemsAppending_thenResultIsFirstItemAndNextIsSecondItem() async throws { // given - let firstLog = Log("Test first message", id: "Test first id") - let secondLog = Log("Test second message", id: "Test second id") + let firstLog = LogChain("Test first message", id: "Test first id", logType: .failure) + let secondLog = LogChain("Test second message", id: "Test second id", logType: .info) var expectedLog = firstLog expectedLog.next = secondLog @@ -72,8 +72,8 @@ final class LoggingContextTests: XCTestCase { await sut.add(secondLog) let log = await sut.log - let result = try XCTUnwrap(log as? Log) - let resultNext = try XCTUnwrap(result.next as? Log) + let result = try XCTUnwrap(log as? LogChain) + let resultNext = try XCTUnwrap(result.next as? LogChain) // then @@ -87,9 +87,9 @@ final class LoggingContextTests: XCTestCase { func testLog_whenThreeItemsAppending_thenResultIsFirstItemAndNextIsTree() async throws { // given - let firstLog = Log("Test first message", id: "Test first id") - let secondLog = Log("Test second message", id: "Test second id") - let thirdLog = Log("Test third message", id: "Test third id") + let firstLog = LogChain("Test first message", id: "Test first id", logType: .info) + let secondLog = LogChain("Test second message", id: "Test second id", logType: .failure) + let thirdLog = LogChain("Test third message", id: "Test third id", logType: .info) var expectedLog = firstLog var expectedThirdLog = thirdLog @@ -104,9 +104,9 @@ final class LoggingContextTests: XCTestCase { await sut.add(thirdLog) let log = await sut.log - let result = try XCTUnwrap((log) as? Log) - let firstNextResult = try XCTUnwrap(result.next as? Log) - let secondNextResult = try XCTUnwrap(firstNextResult.next as? Log) + let result = try XCTUnwrap((log) as? LogChain) + let firstNextResult = try XCTUnwrap(result.next as? LogChain) + let secondNextResult = try XCTUnwrap(firstNextResult.next as? LogChain) // then @@ -119,10 +119,10 @@ final class LoggingContextTests: XCTestCase { func testLog_whenFourItemsAppending_thenResultIsFirstItemAndNextIsTree() async throws { // given - let firstLog = Log("Test first message", id: "Test first id") - let secondLog = Log("Test second message", id: "Test second id") - let thirdLog = Log("Test third message", id: "Test third id") - let fourthLog = Log("Test fourth message", id: "Test fourth id") + let firstLog = LogChain("Test first message", id: "Test first id", logType: .failure) + let secondLog = LogChain("Test second message", id: "Test second id", logType: .failure) + let thirdLog = LogChain("Test third message", id: "Test third id", logType: .info) + let fourthLog = LogChain("Test fourth message", id: "Test fourth id", logType: .info) var expectedLog = firstLog var expectedFourthLog = fourthLog @@ -140,10 +140,10 @@ final class LoggingContextTests: XCTestCase { await sut.add(fourthLog) let log = await sut.log - let result = try XCTUnwrap(log as? Log) - let firstNextResult = try XCTUnwrap(result.next as? Log) - let secondNextResult = try XCTUnwrap(firstNextResult.next as? Log) - let thirdNextResult = try XCTUnwrap(secondNextResult.next as? Log) + let result = try XCTUnwrap(log as? LogChain) + let firstNextResult = try XCTUnwrap(result.next as? LogChain) + let secondNextResult = try XCTUnwrap(firstNextResult.next as? LogChain) + let thirdNextResult = try XCTUnwrap(secondNextResult.next as? LogChain) // then @@ -154,4 +154,56 @@ final class LoggingContextTests: XCTestCase { XCTAssertNil(thirdNextResult.next) } + func testLog_withLogSubscription() async throws { + // given + + let firstLog = LogChain("Test first message", id: "Test first id", logType: .failure) + let secondLog = LogChain("Test second message", id: "Test second id", logType: .failure) + let thirdLog = LogChain("Test third message", id: "Test third id", logType: .info) + let fourthLog = LogChain("Test fourth message", id: "Test fourth id", logType: .info) + + var logs: [[Log]] = [] + + await sut.subscribe { + logs.append($0) + } + + // when + + await sut.add(firstLog) + await sut.add(secondLog) + await sut.add(thirdLog) + await sut.add(fourthLog) + await sut.complete() + + // then + + let receivedLogs = try logs.flatMap { + try XCTUnwrap($0 as? [LogChain]) + } + XCTAssertEqual(receivedLogs, [firstLog, fourthLog, thirdLog, secondLog]) + } + + func testLog_withLogSubscription_whenLogIsNil() async { + // given + + var logs: [[Log]] = [] + + await sut.subscribe { + logs.append($0) + } + + // when + + await sut.add(nil) + await sut.add(nil) + await sut.add(nil) + await sut.add(nil) + await sut.complete() + + // then + + XCTAssert(logs.isEmpty) + } + } diff --git a/NodeKit/NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift index 29e80d6d4..038b7e62e 100644 --- a/NodeKit/NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift +++ b/NodeKit/NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift @@ -114,7 +114,11 @@ final class DTOMapperNodeTests: XCTestCase { "test8": "value10" ] - var log = Log(nextNodeMock.logViewObjectName, id: nextNodeMock.objectName, order: LogOrder.dtoMapperNode) + var log = LogChain( + "", + id: nextNodeMock.objectName, + logType: .failure, order: LogOrder.dtoMapperNode + ) log += "\(BaseTechnicalError.noInternetConnection)" await logContextMock.add(log) diff --git a/NodeKit/NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift index d70666bc5..a675667c2 100644 --- a/NodeKit/NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift +++ b/NodeKit/NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift @@ -17,7 +17,9 @@ final class LoggerNodeTests: XCTestCase { private var nextNodeMock: AsyncNodeMock! private var logContextMock: LoggingContextMock! - + private var loggingProxyMock: LoggingProxyMock! + private var urlProviderMock: URLRouteProviderMock! + // MARK: - Sut private var sut: LoggerNode! @@ -28,7 +30,8 @@ final class LoggerNodeTests: XCTestCase { super.setUp() nextNodeMock = AsyncNodeMock() logContextMock = LoggingContextMock() - sut = LoggerNode(next: nextNodeMock) + loggingProxyMock = LoggingProxyMock() + urlProviderMock = URLRouteProviderMock() } override func tearDown() { @@ -44,6 +47,7 @@ final class LoggerNodeTests: XCTestCase { // given let expectedInput = 00942 + makeSutWithoutLoggingProxy(method: .get) nextNodeMock.stubbedAsyncProccessResult = .success(1) // when @@ -62,6 +66,7 @@ final class LoggerNodeTests: XCTestCase { // given let expectedResult = 001238 + makeSutWithoutLoggingProxy(method: .get) nextNodeMock.stubbedAsyncProccessResult = .success(expectedResult) // when @@ -78,6 +83,7 @@ final class LoggerNodeTests: XCTestCase { func testAsyncProccess_whenNextNodeReturnsFailure_thenFailureReceived() async throws { // given + makeSutWithoutLoggingProxy(method: .get) nextNodeMock.stubbedAsyncProccessResult = .failure(MockError.firstError) // when @@ -90,4 +96,66 @@ final class LoggerNodeTests: XCTestCase { XCTAssertEqual(error, .firstError) } + + func testAsyncProcess_thenRequestParamsPassed() async throws { + // given + + makeSutWithoutLoggingProxy(method: .get) + nextNodeMock.stubbedAsyncProccessResult = .failure(MockError.firstError) + + // when + + let result = await sut.process(1, logContext: logContextMock) + + // then + + let invokedSet = await logContextMock.invokedSet + let invokedSetCount = await logContextMock.invokedSetCount + let invokedSetParams = await logContextMock.invokedSetParameter + let invokedSetURLProvider = try XCTUnwrap(invokedSetParams?.1 as? URLRouteProviderMock) + + XCTAssertTrue(invokedSet) + XCTAssertEqual(invokedSetCount, 1) + XCTAssertEqual(invokedSetParams?.0, .get) + XCTAssert(invokedSetURLProvider === urlProviderMock) + } + + func testAsyncProcess_thenCompleteCalled() async { + // given + + makeSutWithoutLoggingProxy(method: .get) + nextNodeMock.stubbedAsyncProccessResult = .failure(MockError.firstError) + + // when + + let result = await sut.process(1, logContext: logContextMock) + + // then + + let invokedComplete = await logContextMock.invokedComplete + let invokedCompleteCount = await logContextMock.invokedCompleteCount + + XCTAssertTrue(invokedComplete) + XCTAssertEqual(invokedCompleteCount, 1) + } + + // MARK: - Private Methods + + private func makeSutWithoutLoggingProxy(method: NodeKit.Method) { + sut = LoggerNode( + next: nextNodeMock, + method: method, + route: urlProviderMock, + loggingProxy: nil + ) + } + + private func makeSutWithLoggingProxy(method: NodeKit.Method) { + sut = LoggerNode( + next: nextNodeMock, + method: method, + route: urlProviderMock, + loggingProxy: loggingProxyMock + ) + } } diff --git a/NodeKit/NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift index 30a0f85d8..a87ea6737 100644 --- a/NodeKit/NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift +++ b/NodeKit/NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift @@ -87,8 +87,13 @@ final class VoidIONodeTest: XCTestCase { // given let json: Json = ["TestKey": "TestValue"] - var expectedLog = Log(sut.logViewObjectName, id: sut.objectName, order: LogOrder.voidIONode) - + var expectedLog = LogChain( + "", + id: sut.objectName, + logType: .info, + order: LogOrder.voidIONode + ) + expectedLog += "VoidIOtNode used but request have not empty response" + .lineTabDeilimeter expectedLog += "\(json)" diff --git a/NodeKit/NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift index 2f1d2f086..61383417a 100644 --- a/NodeKit/NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift +++ b/NodeKit/NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift @@ -149,7 +149,12 @@ final class VoidOutputNodeTests: XCTestCase { dtoEncodableMock.stubbedToDTOResult = .success([:]) nextNodeMock.stubbedAsyncProccessResult = .success(json) - var expectedLog = Log(sut.logViewObjectName, id: sut.objectName, order: LogOrder.voidOutputNode) + var expectedLog = LogChain( + "", + id: sut.objectName, + logType: .info, + order: LogOrder.voidOutputNode + ) expectedLog += "VoidOutputNode used but request have not empty response" + .lineTabDeilimeter expectedLog += "\(json)" From 7bb3324e4c984bd0d85cde886adb898a067f983a Mon Sep 17 00:00:00 2001 From: Andrey Frolov Date: Wed, 25 Dec 2024 13:41:27 +0100 Subject: [PATCH 2/4] update documentation --- TechDocs/Log/Log.md | 46 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/TechDocs/Log/Log.md b/TechDocs/Log/Log.md index 9e87da56e..d9165f540 100644 --- a/TechDocs/Log/Log.md +++ b/TechDocs/Log/Log.md @@ -7,8 +7,8 @@ Out of the box, the library allows logging any operation performed by a node. The NodeKit library provides the ability to write logs using storage [LoggingContextProtocol](https://surfstudio.github.io/NodeKit/documentation/nodekit/loggingcontextprotocol). The built-in implementation of `LoggingContextProtocol` is [LoggingContext](https://surfstudio.github.io/NodeKit/documentation/nodekit/loggingcontext). `LoggingContext` is created when the `process(_ data: Input)` method of the chain is called and is passed to all nodes through the `process(_ data: Input, logContext: LoggingContextProtocol)` method. Thus, each node has the ability to work with the same `LoggingContext`. -The data type that `LoggingContextProtocol` stores is [Logable](https://surfstudio.github.io/NodeKit/documentation/nodekit/logable), implemented in the structure [Log](https://surfstudio.github.io/NodeKit/documentation/nodekit/log). -To add a new log, you need to create a `Log` object and pass it to `LoggingContextProtocol` using the `add` method. +The data type that `LoggingContextProtocol` stores is [LogableChain](https://surfstudio.github.io/NodeKit/documentation/nodekit/logablechain), implemented in the structure [LogChain](https://surfstudio.github.io/NodeKit/documentation/nodekit/logchain). +To add a new log, you need to create a `LogChain` object and pass it to `LoggingContextProtocol` using the `add` method. The log itself represents a linked list: @@ -24,7 +24,7 @@ The [LoggerNode](https://surfstudio.github.io/NodeKit/documentation/nodekit/logg It is placed at the very beginning of the chain and outputs the message only after the entire chain has finished its work. The node has a log filtering mode. -It is based on the `id` field of `Logable`. To exclude a specific log, you can add the node to the filter. +It is based on the `id` field of `LogableChain`. To exclude a specific log, you can add the node to the filter. By default, logs are output in the following format: ``` @@ -37,18 +37,18 @@ log message separated by \r\n and \t Custom formatting for custom logs is possible according to personal preferences. You can configure logging in the `URLChainsBuilder`. More details about this can be found [here](../Chains.md). -## Example +### Example Let's consider an example of logging. ```Swift func process(_ data: Input, logContext: LoggingContextProtocol) async -> NodeResult { - var log = Log(logViewObjectName, id: objectName) + var log = LogChain("process started", id: objectName) // some operation with data - log += "oparetion result" + log += "operation result" logContext.add(log) @@ -57,6 +57,38 @@ func process(_ data: Input, logContext: LoggingContextProtocol) async -> NodeRes ``` -Here, at the beginning, we create a log object, initializing it with the message `<<<===\(objectName)===>>>` and passing the name of this node as its id. +Here, at the beginning, we create a LogChain object, initializing it with the message `process started` and passing the name of this node as its id. Then, we add a message about the data operation, and finally, we write the log. + +## Proxying + +It is also possible to proxy logs to your own object. To do this, you need to create an object and inherit from the [LoggingProxy](https://surfstudio.github.io/NodeKit/documentation/nodekit/loggingproxy) protocol. Then, you need to pass this object to `URLChainBuilder`. After setting up LoggingProxy in the URLChainBuilder, the LoggerNode will stop printing logs to the console and will redirect the logs to the LoggingProxy. + +The object inherited from `LoggingProxy` in the `handle` method receives a [LogSession](https://surfstudio.github.io/NodeKit/documentation/nodekit/logsession) actor from which you can retrieve request data (such as method and route), and also subscribe to receive logs for that request. + +### Example + +```Swift +struct NodeKitLoggingProxy: LoggingProxy { + func handle(session: LogSession) { + Task { + let method = await session.method + let route = await session.route + await session.subscribe { logs in + ... + } + } + } +} +``` + +```Swift +let loggingProxy = NodeKitLoggingProxy() + +await chainBuilder + .set(loggingProxy: loggingProxy) + .route(.get, .users) + .build() + .process() +``` \ No newline at end of file From ab9ed9a45a871db9824bc85df41230921f47ec65 Mon Sep 17 00:00:00 2001 From: Andrey Frolov Date: Wed, 25 Dec 2024 14:35:08 +0100 Subject: [PATCH 3/4] fix files in xcodeproj --- NodeKit/NodeKit.xcodeproj/project.pbxproj | 56 ++++++++++++++++--- .../Utils/Logging/{ => Chain}/LogChain.swift | 0 .../Logging/{ => Chain}/LogableChain.swift | 0 .../Utils/Logging/{Chain => Log}/Log.swift | 0 .../Logging/{Chain => Log}/LogSession.swift | 0 .../Logging/{Chain => Log}/LogType.swift | 0 .../Logging/{Chain => Log}/LoggingProxy.swift | 0 7 files changed, 48 insertions(+), 8 deletions(-) rename NodeKit/NodeKit/Utils/Logging/{ => Chain}/LogChain.swift (100%) rename NodeKit/NodeKit/Utils/Logging/{ => Chain}/LogableChain.swift (100%) rename NodeKit/NodeKit/Utils/Logging/{Chain => Log}/Log.swift (100%) rename NodeKit/NodeKit/Utils/Logging/{Chain => Log}/LogSession.swift (100%) rename NodeKit/NodeKit/Utils/Logging/{Chain => Log}/LogType.swift (100%) rename NodeKit/NodeKit/Utils/Logging/{Chain => Log}/LoggingProxy.swift (100%) diff --git a/NodeKit/NodeKit.xcodeproj/project.pbxproj b/NodeKit/NodeKit.xcodeproj/project.pbxproj index 2f84ee164..47faa77ee 100644 --- a/NodeKit/NodeKit.xcodeproj/project.pbxproj +++ b/NodeKit/NodeKit.xcodeproj/project.pbxproj @@ -56,6 +56,14 @@ 50528E272BADF64F00E86CB6 /* NodeResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50528E262BADF64F00E86CB6 /* NodeResult.swift */; }; 50528E292BAE162600E86CB6 /* LoggerStreamNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50528E282BAE162600E86CB6 /* LoggerStreamNode.swift */; }; 50528E372BAE34A400E86CB6 /* TokenRefresherActorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50528E362BAE34A400E86CB6 /* TokenRefresherActorTests.swift */; }; + 506091A62D1C40F60042B6EE /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091A12D1C40F60042B6EE /* Log.swift */; }; + 506091A72D1C40F60042B6EE /* LoggingProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091A22D1C40F60042B6EE /* LoggingProxy.swift */; }; + 506091A82D1C40F60042B6EE /* LogSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091A32D1C40F60042B6EE /* LogSession.swift */; }; + 506091A92D1C40F60042B6EE /* LogType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091A42D1C40F60042B6EE /* LogType.swift */; }; + 506091AD2D1C41460042B6EE /* LogChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091AB2D1C41460042B6EE /* LogChain.swift */; }; + 506091AE2D1C41460042B6EE /* LogableChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091AC2D1C41460042B6EE /* LogableChain.swift */; }; + 506091B12D1C424A0042B6EE /* LogSessionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091AF2D1C424A0042B6EE /* LogSessionMock.swift */; }; + 506091B22D1C424A0042B6EE /* LoggingProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506091B02D1C424A0042B6EE /* LoggingProxyMock.swift */; }; 5060A45C2BC3D8D2004E84E2 /* EntryinputDtoOutputNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5060A45B2BC3D8D2004E84E2 /* EntryinputDtoOutputNodeTests.swift */; }; 5060A4642BC3E660004E84E2 /* RequestSenderNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5060A4632BC3E660004E84E2 /* RequestSenderNodeTests.swift */; }; 5060A4662BC3E6E8004E84E2 /* URLSessionDataTaskActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5060A4652BC3E6E8004E84E2 /* URLSessionDataTaskActor.swift */; }; @@ -200,9 +208,7 @@ 90B609BB283E1287006F4309 /* StateStorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B6097A283E1287006F4309 /* StateStorable.swift */; }; 90B609BD283E1287006F4309 /* UrlRouting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B6097D283E1287006F4309 /* UrlRouting.swift */; }; 90B609BE283E1287006F4309 /* LoggerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B6097F283E1287006F4309 /* LoggerNode.swift */; }; - 90B609BF283E1287006F4309 /* Logable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60980283E1287006F4309 /* Logable.swift */; }; 90B609C0283E1287006F4309 /* LoggerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60981283E1287006F4309 /* LoggerExtensions.swift */; }; - 90B609C1283E1287006F4309 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60982283E1287006F4309 /* Log.swift */; }; 90B609EF283E16DC006F4309 /* DTOMapperNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B609C5283E16DC006F4309 /* DTOMapperNodeTests.swift */; }; 90B609F3283E16DC006F4309 /* URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B609CE283E16DC006F4309 /* URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift */; }; 90B609F4283E16DC006F4309 /* URLQueryArrayKeyEncodingBracketsStartegyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B609CF283E16DC006F4309 /* URLQueryArrayKeyEncodingBracketsStartegyTests.swift */; }; @@ -296,6 +302,14 @@ 50528E282BAE162600E86CB6 /* LoggerStreamNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerStreamNode.swift; sourceTree = ""; }; 50528E302BAE1F9800E86CB6 /* LoggingContextMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingContextMock.swift; sourceTree = ""; }; 50528E362BAE34A400E86CB6 /* TokenRefresherActorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRefresherActorTests.swift; sourceTree = ""; }; + 506091A12D1C40F60042B6EE /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + 506091A22D1C40F60042B6EE /* LoggingProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingProxy.swift; sourceTree = ""; }; + 506091A32D1C40F60042B6EE /* LogSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogSession.swift; sourceTree = ""; }; + 506091A42D1C40F60042B6EE /* LogType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogType.swift; sourceTree = ""; }; + 506091AB2D1C41460042B6EE /* LogChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogChain.swift; sourceTree = ""; }; + 506091AC2D1C41460042B6EE /* LogableChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogableChain.swift; sourceTree = ""; }; + 506091AF2D1C424A0042B6EE /* LogSessionMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogSessionMock.swift; sourceTree = ""; }; + 506091B02D1C424A0042B6EE /* LoggingProxyMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingProxyMock.swift; sourceTree = ""; }; 5060A45B2BC3D8D2004E84E2 /* EntryinputDtoOutputNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryinputDtoOutputNodeTests.swift; sourceTree = ""; }; 5060A4632BC3E660004E84E2 /* RequestSenderNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSenderNodeTests.swift; sourceTree = ""; }; 5060A4652BC3E6E8004E84E2 /* URLSessionDataTaskActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataTaskActor.swift; sourceTree = ""; }; @@ -433,9 +447,7 @@ 90B6097A283E1287006F4309 /* StateStorable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateStorable.swift; sourceTree = ""; }; 90B6097D283E1287006F4309 /* UrlRouting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UrlRouting.swift; sourceTree = ""; }; 90B6097F283E1287006F4309 /* LoggerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerNode.swift; sourceTree = ""; }; - 90B60980283E1287006F4309 /* Logable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logable.swift; sourceTree = ""; }; 90B60981283E1287006F4309 /* LoggerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerExtensions.swift; sourceTree = ""; }; - 90B60982283E1287006F4309 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; 90B609C5283E16DC006F4309 /* DTOMapperNodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DTOMapperNodeTests.swift; sourceTree = ""; }; 90B609CE283E16DC006F4309 /* URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift; sourceTree = ""; }; 90B609CF283E16DC006F4309 /* URLQueryArrayKeyEncodingBracketsStartegyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLQueryArrayKeyEncodingBracketsStartegyTests.swift; sourceTree = ""; }; @@ -593,6 +605,26 @@ path = Core; sourceTree = ""; }; + 506091A52D1C40F60042B6EE /* Log */ = { + isa = PBXGroup; + children = ( + 506091A12D1C40F60042B6EE /* Log.swift */, + 506091A22D1C40F60042B6EE /* LoggingProxy.swift */, + 506091A32D1C40F60042B6EE /* LogSession.swift */, + 506091A42D1C40F60042B6EE /* LogType.swift */, + ); + path = Log; + sourceTree = ""; + }; + 506091AA2D1C41120042B6EE /* Chain */ = { + isa = PBXGroup; + children = ( + 506091AC2D1C41460042B6EE /* LogableChain.swift */, + 506091AB2D1C41460042B6EE /* LogChain.swift */, + ); + path = Chain; + sourceTree = ""; + }; 506150AE2BBC24AE00EBBE96 /* Combine */ = { isa = PBXGroup; children = ( @@ -681,6 +713,8 @@ 50816AE52BC706D100A43F3D /* NodeKitMock */ = { isa = PBXGroup; children = ( + 506091B02D1C424A0042B6EE /* LoggingProxyMock.swift */, + 506091AF2D1C424A0042B6EE /* LogSessionMock.swift */, 5013490D2BE3CB05002CC3DA /* Builder */, 50816B002BC7071800A43F3D /* Utils */, 50528E302BAE1F9800E86CB6 /* LoggingContextMock.swift */, @@ -1110,10 +1144,10 @@ 90B6097E283E1287006F4309 /* Logging */ = { isa = PBXGroup; children = ( + 506091AA2D1C41120042B6EE /* Chain */, + 506091A52D1C40F60042B6EE /* Log */, 90B6097F283E1287006F4309 /* LoggerNode.swift */, - 90B60980283E1287006F4309 /* Logable.swift */, 90B60981283E1287006F4309 /* LoggerExtensions.swift */, - 90B60982283E1287006F4309 /* Log.swift */, 502F9D962BAA36CF00151A8D /* LoggingContext.swift */, 50528E282BAE162600E86CB6 /* LoggerStreamNode.swift */, ); @@ -1401,6 +1435,7 @@ 5013494A2BE3F8F3002CC3DA /* URLRouteProviderMock.swift in Sources */, 50816B092BC7078000A43F3D /* Result+Extension.swift in Sources */, 50816B0B2BC7079300A43F3D /* Array+Extension.swift in Sources */, + 506091B12D1C424A0042B6EE /* LogSessionMock.swift in Sources */, 5097DF3C2BCD556E00D422EE /* AsyncPagerDataProviderMock.swift in Sources */, 50816B0E2BC7079900A43F3D /* RawEncodableMock.swift in Sources */, 50816B182BC707AE00A43F3D /* AborterMock.swift in Sources */, @@ -1418,6 +1453,7 @@ 501349132BE3D790002CC3DA /* ChainConfigBuilderMock.swift in Sources */, 50816B1B2BC707B400A43F3D /* CombineCompatibleNodeMock.swift in Sources */, 50816B232BC707C300A43F3D /* MultipartFormDataMock.swift in Sources */, + 506091B22D1C424A0042B6EE /* LoggingProxyMock.swift in Sources */, 50816B152BC707A800A43F3D /* URLProtocolMock.swift in Sources */, 50816B212BC707BF00A43F3D /* MockError.swift in Sources */, 50816B1D2BC707B800A43F3D /* DTOConvertibleMock.swift in Sources */, @@ -1443,8 +1479,10 @@ 90B6099B283E1287006F4309 /* RequestAborterNode.swift in Sources */, 5005EB232BB8A6D900B670CD /* AsyncStreamNode.swift in Sources */, 5013491F2BE3F4AA002CC3DA /* URLETagSaverNode.swift in Sources */, + 506091A72D1C40F60042B6EE /* LoggingProxy.swift in Sources */, 90B609AC283E1287006F4309 /* DefaultLogOrder.swift in Sources */, 50C57AB72BE3871D004C344E /* ServiceChainProvider.swift in Sources */, + 506091AE2D1C41460042B6EE /* LogableChain.swift in Sources */, 50B569702BCFED6F0054DC09 /* AsyncNodeSubscription.swift in Sources */, 90B609BD283E1287006F4309 /* UrlRouting.swift in Sources */, 501349342BE3F6A1002CC3DA /* TransportURLParameters.swift in Sources */, @@ -1464,7 +1502,6 @@ 90B609B1283E1287006F4309 /* MultipartRequestCreatorNode.swift in Sources */, 50B569782BCFEF610054DC09 /* AsyncStreamNodeResultPublisher.swift in Sources */, 90B6098A283E1287006F4309 /* ParametersEncoding.swift in Sources */, - 90B609BF283E1287006F4309 /* Logable.swift in Sources */, 501349282BE3F57B002CC3DA /* URLRouteProvider.swift in Sources */, 5097DFB02BCDFB5200D422EE /* CancellableTask.swift in Sources */, 90B609BB283E1287006F4309 /* StateStorable.swift in Sources */, @@ -1513,6 +1550,7 @@ 5013491B2BE3F40B002CC3DA /* URLNotModifiedTriggerNode.swift in Sources */, 5013491D2BE3F467002CC3DA /* URLCacheWriterNode.swift in Sources */, 90B6091A283E1268006F4309 /* RawMappable+Dictionary.swift in Sources */, + 506091A82D1C40F60042B6EE /* LogSession.swift in Sources */, 90B609A7283E1287006F4309 /* RawEncoderNode.swift in Sources */, 90B609B9283E1287006F4309 /* AsyncPagerIterator.swift in Sources */, 50528E272BADF64F00E86CB6 /* NodeResult.swift in Sources */, @@ -1521,6 +1559,7 @@ 90B6091B283E1268006F4309 /* Array+RawMappable.swift in Sources */, 5060A4662BC3E6E8004E84E2 /* URLSessionDataTaskActor.swift in Sources */, 90B6098D283E1287006F4309 /* Method.swift in Sources */, + 506091A62D1C40F60042B6EE /* Log.swift in Sources */, 502F9D972BAA36CF00151A8D /* LoggingContext.swift in Sources */, 90B60988283E1287006F4309 /* URLQueryArrayKeyEncodingStartegy.swift in Sources */, 50B569762BCFEF470054DC09 /* NodeResultPublisher.swift in Sources */, @@ -1528,6 +1567,7 @@ 501349152BE3E6D2002CC3DA /* AnyAsyncNode.swift in Sources */, 90B609AF283E1287006F4309 /* RequestCreatorNode.swift in Sources */, 501349252BE3F543002CC3DA /* URLJsonRequestEncodingNode.swift in Sources */, + 506091A92D1C40F60042B6EE /* LogType.swift in Sources */, 90B60994283E1287006F4309 /* MetadataConnectorNode.swift in Sources */, 90B609B7283E1287006F4309 /* MetadataProvider.swift in Sources */, 90B60997283E1287006F4309 /* VoidOutputNode.swift in Sources */, @@ -1537,7 +1577,7 @@ 50B5697A2BCFF1A10054DC09 /* CombineCompatibleNode.swift in Sources */, 5060A4962BC44225004E84E2 /* EntryInputDtoOutputNode.swift in Sources */, 502F9DA92BAB0CF000151A8D /* TokenRefresherActor.swift in Sources */, - 90B609C1283E1287006F4309 /* Log.swift in Sources */, + 506091AD2D1C41460042B6EE /* LogChain.swift in Sources */, 90B6091D283E1268006F4309 /* DTOConvertible+Dictionary.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/NodeKit/NodeKit/Utils/Logging/LogChain.swift b/NodeKit/NodeKit/Utils/Logging/Chain/LogChain.swift similarity index 100% rename from NodeKit/NodeKit/Utils/Logging/LogChain.swift rename to NodeKit/NodeKit/Utils/Logging/Chain/LogChain.swift diff --git a/NodeKit/NodeKit/Utils/Logging/LogableChain.swift b/NodeKit/NodeKit/Utils/Logging/Chain/LogableChain.swift similarity index 100% rename from NodeKit/NodeKit/Utils/Logging/LogableChain.swift rename to NodeKit/NodeKit/Utils/Logging/Chain/LogableChain.swift diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/Log.swift b/NodeKit/NodeKit/Utils/Logging/Log/Log.swift similarity index 100% rename from NodeKit/NodeKit/Utils/Logging/Chain/Log.swift rename to NodeKit/NodeKit/Utils/Logging/Log/Log.swift diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/LogSession.swift b/NodeKit/NodeKit/Utils/Logging/Log/LogSession.swift similarity index 100% rename from NodeKit/NodeKit/Utils/Logging/Chain/LogSession.swift rename to NodeKit/NodeKit/Utils/Logging/Log/LogSession.swift diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/LogType.swift b/NodeKit/NodeKit/Utils/Logging/Log/LogType.swift similarity index 100% rename from NodeKit/NodeKit/Utils/Logging/Chain/LogType.swift rename to NodeKit/NodeKit/Utils/Logging/Log/LogType.swift diff --git a/NodeKit/NodeKit/Utils/Logging/Chain/LoggingProxy.swift b/NodeKit/NodeKit/Utils/Logging/Log/LoggingProxy.swift similarity index 100% rename from NodeKit/NodeKit/Utils/Logging/Chain/LoggingProxy.swift rename to NodeKit/NodeKit/Utils/Logging/Log/LoggingProxy.swift From 157c49699f2f8de6df8e0ef2f4e0dced8bd427fe Mon Sep 17 00:00:00 2001 From: Andrey Frolov Date: Wed, 15 Jan 2025 08:39:42 +0100 Subject: [PATCH 4/4] add warning log type --- NodeKit/NodeKit/Utils/Logging/Log/LogType.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/NodeKit/NodeKit/Utils/Logging/Log/LogType.swift b/NodeKit/NodeKit/Utils/Logging/Log/LogType.swift index e982d18c6..9b1890512 100644 --- a/NodeKit/NodeKit/Utils/Logging/Log/LogType.swift +++ b/NodeKit/NodeKit/Utils/Logging/Log/LogType.swift @@ -8,4 +8,5 @@ public enum LogType { case failure case info + case warning }