diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 769c381..2b37080 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -113,6 +113,11 @@ namespace CryptoExchange.Net return new WebCallResult(request.Error!); var result = await GetResponseAsync(request.Data, deserializer, cancellationToken, true).ConfigureAwait(false); + if (!result) + _logger.Log(LogLevel.Warning, $"[{result.RequestId}] Error received in {result.ResponseTime!.Value.TotalMilliseconds}ms: {result.Error}"); + else + _logger.Log(LogLevel.Debug, $"[{result.RequestId}] Response received in {result.ResponseTime!.Value.TotalMilliseconds}ms{(OutputOriginalData ? (": " + result.OriginalData) : "")}"); + if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false)) continue; @@ -160,6 +165,11 @@ namespace CryptoExchange.Net return new WebCallResult(request.Error!); var result = await GetResponseAsync(request.Data, deserializer, cancellationToken, false).ConfigureAwait(false); + if (!result) + _logger.Log(LogLevel.Warning, $"[{result.RequestId}] Error received in {result.ResponseTime!.Value.TotalMilliseconds}ms: {result.Error}"); + else + _logger.Log(LogLevel.Debug, $"[{result.RequestId}] Response received in {result.ResponseTime!.Value.TotalMilliseconds}ms{(OutputOriginalData ? (": " + result.OriginalData) : "")}"); + if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false)) continue; @@ -261,9 +271,9 @@ namespace CryptoExchange.Net CancellationToken cancellationToken, bool expectedEmptyResponse) { + var sw = Stopwatch.StartNew(); try { - var sw = Stopwatch.StartNew(); var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false); sw.Stop(); var statusCode = response.StatusCode; @@ -281,23 +291,22 @@ namespace CryptoExchange.Net responseLength ??= data.Length; responseStream.Close(); response.Close(); - _logger.Log(LogLevel.Debug, $"[{request.RequestId}] Response received in {sw.ElapsedMilliseconds}ms{(OutputOriginalData ? (": " + data) : "")}"); if (!expectedEmptyResponse) { // Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example var parseResult = ValidateJson(data); if (!parseResult.Success) - return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); + return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); // Let the library implementation see if it is an error response, and if so parse the error var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false); if (error != null) - return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); + return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); // Not an error, so continue deserializing var deserializeResult = Deserialize(parseResult.Data, deserializer, request.RequestId); - return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error); + return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error); } else { @@ -306,16 +315,16 @@ namespace CryptoExchange.Net var parseResult = ValidateJson(data); if (!parseResult.Success) // Not empty, and not json - return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); + return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false); if (error != null) // Error response - return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); + return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); } // Empty success response; okay - return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default); + return new WebCallResult(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default); } } else @@ -326,7 +335,7 @@ namespace CryptoExchange.Net responseStream.Close(); response.Close(); - return new WebCallResult(statusCode, headers, sw.Elapsed, 0, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null); + return new WebCallResult(statusCode, headers, sw.Elapsed, 0, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null); } // Success status code, and we don't have to check for errors. Continue deserializing directly from the stream @@ -334,7 +343,7 @@ namespace CryptoExchange.Net responseStream.Close(); response.Close(); - return new WebCallResult(statusCode, headers, sw.Elapsed, responseLength, OutputOriginalData ? desResult.OriginalData : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error); + return new WebCallResult(statusCode, headers, sw.Elapsed, responseLength, OutputOriginalData ? desResult.OriginalData : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error); } } else @@ -342,7 +351,6 @@ namespace CryptoExchange.Net // Http status code indicates error using var reader = new StreamReader(responseStream); var data = await reader.ReadToEndAsync().ConfigureAwait(false); - _logger.Log(LogLevel.Warning, $"[{request.RequestId}] Error received in {sw.ElapsedMilliseconds}ms: {data}"); responseStream.Close(); response.Close(); @@ -354,29 +362,26 @@ namespace CryptoExchange.Net if (error.Code == null || error.Code == 0) error.Code = (int)response.StatusCode; - return new WebCallResult(statusCode, headers, sw.Elapsed, data.Length, data, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error); + return new WebCallResult(statusCode, headers, sw.Elapsed, data.Length, data, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error); } } catch (HttpRequestException requestException) { // Request exception, can't reach server for instance var exceptionInfo = requestException.ToLogString(); - _logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request exception: " + exceptionInfo); - return new WebCallResult(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo)); + return new WebCallResult(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo)); } catch (OperationCanceledException canceledException) { if (cancellationToken != default && canceledException.CancellationToken == cancellationToken) { // Cancellation token canceled by caller - _logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request canceled by cancellation token"); - return new WebCallResult(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError()); + return new WebCallResult(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError()); } else { // Request timed out - _logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request timed out: " + canceledException.ToLogString()); - return new WebCallResult(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"[{request.RequestId}] Request timed out")); + return new WebCallResult(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"Request timed out")); } } } @@ -580,14 +585,14 @@ namespace CryptoExchange.Net { var timeSyncParams = GetTimeSyncInfo(); if (timeSyncParams == null) - return new WebCallResult(null, null, null, null, null, null, null, null, null, true, null); + return new WebCallResult(null, null, null, null, null, null, null, null, null, null, true, null); if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false)) { if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval)) { timeSyncParams.TimeSyncState.Semaphore.Release(); - return new WebCallResult(null, null, null, null, null, null, null, null, null, true, null); + return new WebCallResult(null, null, null, null, null, null, null, null, null, null, true, null); } var localTime = DateTime.UtcNow; @@ -616,7 +621,7 @@ namespace CryptoExchange.Net timeSyncParams.TimeSyncState.Semaphore.Release(); } - return new WebCallResult(null, null, null, null, null, null, null, null, null, true, null); + return new WebCallResult(null, null, null, null, null, null, null, null, null, null, true, null); } } } diff --git a/CryptoExchange.Net/Objects/CallResult.cs b/CryptoExchange.Net/Objects/CallResult.cs index f24302e..c72f470 100644 --- a/CryptoExchange.Net/Objects/CallResult.cs +++ b/CryptoExchange.Net/Objects/CallResult.cs @@ -186,6 +186,11 @@ namespace CryptoExchange.Net.Objects /// public IEnumerable>>? RequestHeaders { get; set; } + /// + /// The request id + /// + public int? RequestId { get; set; } + /// /// The url which was requested /// @@ -217,6 +222,7 @@ namespace CryptoExchange.Net.Objects /// /// /// + /// /// /// /// @@ -226,6 +232,7 @@ namespace CryptoExchange.Net.Objects HttpStatusCode? code, IEnumerable>>? responseHeaders, TimeSpan? responseTime, + int? requestId, string? requestUrl, string? requestBody, HttpMethod? requestMethod, @@ -235,6 +242,7 @@ namespace CryptoExchange.Net.Objects ResponseStatusCode = code; ResponseHeaders = responseHeaders; ResponseTime = responseTime; + RequestId = requestId; RequestUrl = requestUrl; RequestBody = requestBody; @@ -255,7 +263,7 @@ namespace CryptoExchange.Net.Objects /// public WebCallResult AsError(Error error) { - return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); + return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); } /// @@ -281,6 +289,11 @@ namespace CryptoExchange.Net.Objects /// public IEnumerable>>? RequestHeaders { get; set; } + /// + /// The request id + /// + public int? RequestId { get; set; } + /// /// The url which was requested /// @@ -319,6 +332,7 @@ namespace CryptoExchange.Net.Objects /// /// /// + /// /// /// /// @@ -331,6 +345,7 @@ namespace CryptoExchange.Net.Objects TimeSpan? responseTime, long? responseLength, string? originalData, + int? requestId, string? requestUrl, string? requestBody, HttpMethod? requestMethod, @@ -343,6 +358,7 @@ namespace CryptoExchange.Net.Objects ResponseTime = responseTime; ResponseLength = responseLength; + RequestId = requestId; RequestUrl = requestUrl; RequestBody = requestBody; RequestHeaders = requestHeaders; @@ -355,7 +371,7 @@ namespace CryptoExchange.Net.Objects /// public new WebCallResult AsDataless() { - return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error); + return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error); } /// /// Copy as a dataless result @@ -363,14 +379,14 @@ namespace CryptoExchange.Net.Objects /// public new WebCallResult AsDatalessError(Error error) { - return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); + return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); } /// /// Create a new error result /// /// The error - public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, default, error) { } + public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, default, error) { } /// /// Copy the WebCallResult to a new data type @@ -380,7 +396,7 @@ namespace CryptoExchange.Net.Objects /// public new WebCallResult As([AllowNull] K data) { - return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, data, Error); + return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, data, Error); } /// @@ -391,7 +407,7 @@ namespace CryptoExchange.Net.Objects /// public new WebCallResult AsError(Error error) { - return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, default, error); + return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, default, error); } /// diff --git a/CryptoExchange.Net/Objects/Error.cs b/CryptoExchange.Net/Objects/Error.cs index a78fa67..580a014 100644 --- a/CryptoExchange.Net/Objects/Error.cs +++ b/CryptoExchange.Net/Objects/Error.cs @@ -41,7 +41,7 @@ namespace CryptoExchange.Net.Objects /// public override string ToString() { - return Code != null ? $"{Code}: {Message} {Data}" : $"{Message} {Data}"; + return Code != null ? $"[{GetType().Name}] {Code}: {Message} {Data}" : $"[{GetType().Name}] {Message} {Data}"; } }