Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(csharp): add logs and logger factory #2640

Merged
merged 4 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using Algolia.Search.Http;
using Algolia.Search.Models;
using Algolia.Search.Models.Common;
using Algolia.Search.Serializer;
using Algolia.Search.Transport;
using Algolia.Search.Utils;
using Microsoft.Extensions.Logging;

namespace Algolia.Search.Clients
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Algolia.Search.Http
{
Expand All @@ -17,10 +19,18 @@ internal class AlgoliaHttpRequester : IHttpRequester
/// https://docs.microsoft.com/en-gb/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client
/// </summary>
private readonly HttpClient _httpClient = new(
new TimeoutHandler
{
InnerHandler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }
});
new TimeoutHandler
{
InnerHandler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }
});

private readonly ILogger<AlgoliaHttpRequester> _logger;

public AlgoliaHttpRequester(ILoggerFactory loggerFactory)
{
var logger = loggerFactory ?? NullLoggerFactory.Instance;
_logger = logger.CreateLogger<AlgoliaHttpRequester>();
}

/// <summary>
/// Don't use it directly
Expand All @@ -31,8 +41,8 @@ internal class AlgoliaHttpRequester : IHttpRequester
/// <param name="connectTimeout">Connect timeout</param>
/// <param name="ct">Optional cancellation token</param>
/// <returns></returns>
public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan requestTimeout, TimeSpan connectTimeout,
CancellationToken ct = default)
public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan requestTimeout,
TimeSpan connectTimeout, CancellationToken ct = default)
{
if (request.Method == null)
{
Expand Down Expand Up @@ -63,14 +73,13 @@ public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpa
try
{
using (httpRequestMessage)
using (HttpResponseMessage response =
await _httpClient.SendAsync(httpRequestMessage, ct).ConfigureAwait(false))
using (var response = await _httpClient.SendAsync(httpRequestMessage, ct).ConfigureAwait(false))
{
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
if (response.IsSuccessStatusCode)
{
MemoryStream outputStream = new MemoryStream();
var outputStream = new MemoryStream();
await stream.CopyToAsync(outputStream).ConfigureAwait(false);
outputStream.Seek(0, SeekOrigin.Begin);

Expand All @@ -89,29 +98,35 @@ await _httpClient.SendAsync(httpRequestMessage, ct).ConfigureAwait(false))
}
}
}
catch (TimeoutException e)
catch (TimeoutException ex)
{
return new AlgoliaHttpResponse { IsTimedOut = true, Error = e.ToString() };
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(ex, "Timeout while sending request");
}

return new AlgoliaHttpResponse { IsTimedOut = true, Error = ex.ToString() };
}
catch (HttpRequestException e)
catch (HttpRequestException ex)
{
// HttpRequestException is thrown when an underlying issue happened such as
// network connectivity, DNS failure, server certificate validation.
return new AlgoliaHttpResponse { IsNetworkError = true, Error = e.Message };
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(ex, "Error while sending request");
}

return new AlgoliaHttpResponse { IsNetworkError = true, Error = ex.Message };
}
}

private async Task<string> StreamToStringAsync(Stream stream)
{
string content;

if (stream == null)
return null;

using (var sr = new StreamReader(stream))
{
content = await sr.ReadToEndAsync().ConfigureAwait(false);
}
using var sr = new StreamReader(stream);
var content = await sr.ReadToEndAsync().ConfigureAwait(false);

return content;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
//
// Code generated by OpenAPI Generator (https://openapi-generator.tech), manual changes will be lost - read more on https://github.com/algolia/api-clients-automation. DO NOT EDIT.
//

using System;
using System.Collections;
using System.Collections.Generic;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Extensions.Logging;

namespace Algolia.Search.Http
{
Expand Down Expand Up @@ -44,8 +45,7 @@ private static Dictionary<string, string> SplitQuery(string query)
/// <param name="ct"></param>
/// <returns></returns>
public Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan requestTimeout,
TimeSpan connectTimeout,
CancellationToken ct = default)
TimeSpan connectTimeout, CancellationToken ct = default)
{
string body = null;
if (!_bodyAsStream && request.Body != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
using System.Threading.Tasks;
using Algolia.Search.Exceptions;
using Algolia.Search.Models.Common;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Algolia.Search.Serializer;

internal class DefaultJsonSerializer
internal class DefaultJsonSerializer : ISerializer
{
private readonly JsonSerializerSettings _serializerSettings;
private readonly ILogger<DefaultJsonSerializer> _logger;

public DefaultJsonSerializer(JsonSerializerSettings serializerSettings)
public DefaultJsonSerializer(JsonSerializerSettings serializerSettings, ILoggerFactory logger)
{
_serializerSettings = serializerSettings;
_logger = logger.CreateLogger<DefaultJsonSerializer>();
}

/// <summary>
Expand Down Expand Up @@ -79,9 +82,14 @@ private async Task<object> Deserialize(Stream response, Type type)
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject(text, type, _serializerSettings);
}
catch (Exception e)
catch (Exception ex)
{
throw new AlgoliaException(e.Message);
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.Log(LogLevel.Debug, ex, "Error while deserializing response");
}

throw new AlgoliaException(ex.Message);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System.Threading.Tasks;
using Algolia.Search.Models.Common;

namespace Algolia.Search.Serializer
Expand All @@ -12,17 +13,14 @@ internal interface ISerializer
/// Converts the value of a specified type into a JSON string.
/// </summary>
/// <param name="data">The value to convert and write.</param>
/// <param name="stream">The Stream containing the data to read.</param>
/// <param name="compressionType">How the stream should be compressed <see cref="CompressionType"/></param>
/// <typeparam name="T">The type of the value to convert.</typeparam>
void Serialize<T>(T data, Stream stream, CompressionType compressionType);
string Serialize(object data);

/// <summary>
/// Parses the stream into an instance of a specified type.
/// </summary>
/// <param name="stream">The Stream containing the data to read.</param>
/// <typeparam name="T">The type of the value to convert.</typeparam>
/// <returns></returns>
T Deserialize<T>(Stream stream);
Task<T> Deserialize<T>(Stream stream);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Text;
Expand All @@ -10,9 +9,9 @@
using Algolia.Search.Clients;
using Algolia.Search.Exceptions;
using Algolia.Search.Http;
using Algolia.Search.Models.Common;
using Algolia.Search.Serializer;
using Algolia.Search.Utils;
using Microsoft.Extensions.Logging;

namespace Algolia.Search.Transport;

Expand All @@ -23,10 +22,11 @@ namespace Algolia.Search.Transport;
internal class HttpTransport
{
private readonly IHttpRequester _httpClient;
private readonly DefaultJsonSerializer _serializer = new(JsonConfig.AlgoliaJsonSerializerSettings);
private readonly ISerializer _serializer;
private readonly RetryStrategy _retryStrategy;
private readonly AlgoliaConfig _algoliaConfig;
private string _errorMessage;
private readonly ILogger<HttpTransport> _logger;

private class VoidResult
{
Expand All @@ -37,11 +37,14 @@ private class VoidResult
/// </summary>
/// <param name="config">Algolia Config</param>
/// <param name="httpClient">An implementation of http requester <see cref="IHttpRequester"/> </param>
public HttpTransport(AlgoliaConfig config, IHttpRequester httpClient)
/// <param name="loggerFactory">Logger factory</param>
public HttpTransport(AlgoliaConfig config, IHttpRequester httpClient, ILoggerFactory loggerFactory)
{
_algoliaConfig = config ?? throw new ArgumentNullException(nameof(config));
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_retryStrategy = new RetryStrategy(config);
_serializer = new DefaultJsonSerializer(JsonConfig.AlgoliaJsonSerializerSettings, loggerFactory);
_logger = loggerFactory.CreateLogger<HttpTransport>();
}

/// <summary>
Expand Down Expand Up @@ -112,7 +115,7 @@ private async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod metho

foreach (var host in _retryStrategy.GetTryableHost(callType))
{
request.Body = CreateRequestContent(requestOptions?.Data, request.CanCompress);
request.Body = CreateRequestContent(requestOptions?.Data, request.CanCompress, _logger);
request.Uri = BuildUri(host.Url, uri, requestOptions?.CustomPathParameters, requestOptions?.PathParameters,
requestOptions?.QueryParameters);
var requestTimeout =
Expand All @@ -123,6 +126,11 @@ private async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod metho
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{}"));
}

if (_logger.IsEnabled(LogLevel.Trace))
{
_logger.LogTrace("Sending request to {Method} {Uri}", request.Method, request.Uri);
}

var response = await _httpClient
.SendRequestAsync(request, requestTimeout, _algoliaConfig.ConnectTimeout ?? Defaults.ConnectTimeout, ct)
.ConfigureAwait(false);
Expand All @@ -137,16 +145,51 @@ private async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod metho
return new VoidResult() as TResult;
}

return await _serializer.Deserialize<TResult>(response.Body);
if (_logger.IsEnabled(LogLevel.Trace))
{
var reader = new StreamReader(response.Body);
var json = await reader.ReadToEndAsync().ConfigureAwait(false);
_logger.LogTrace(
"Response HTTP {HttpCode}: {Json}", response.HttpStatusCode, json);
response.Body.Seek(0, SeekOrigin.Begin);
}

var deserialized = await _serializer.Deserialize<TResult>(response.Body);

if (_logger.IsEnabled(LogLevel.Trace))
{
_logger.LogTrace("Object created: {objectCreated}", deserialized);
}

return deserialized;
case RetryOutcomeType.Retry:
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(
"Retrying ... Retryable error for response HTTP {HttpCode} : {Error}", response.HttpStatusCode,
response.Error);
}

continue;
case RetryOutcomeType.Failure:
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(
"Retry strategy with failure outcome. Response HTTP{HttpCode} : {Error}", response.HttpStatusCode,
response.Error);
}

throw new AlgoliaApiException(response.Error, response.HttpStatusCode);
default:
throw new ArgumentOutOfRangeException();
}
}

if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Retry strategy failed: {ErrorMessage}", _errorMessage);
}

throw new AlgoliaUnreachableHostException("RetryStrategy failed to connect to Algolia. Reason: " + _errorMessage);
}

Expand All @@ -155,11 +198,19 @@ private async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod metho
/// </summary>
/// <param name="data">Data to send</param>
/// <param name="compress">Whether the stream should be compressed or not</param>
/// <param name="logger">Logger</param>
/// <typeparam name="T">Type of the data to send/retrieve</typeparam>
/// <returns></returns>
private MemoryStream CreateRequestContent<T>(T data, bool compress)
private MemoryStream CreateRequestContent<T>(T data, bool compress, ILogger logger)
{
return data == null ? null : Compression.CreateStream(_serializer.Serialize(data), compress);
var serializedData = _serializer.Serialize(data);

if (_logger.IsEnabled(LogLevel.Trace))
{
logger.LogTrace("Serialized request data: {Json}", serializedData);
}

return data == null ? null : Compression.CreateStream(serializedData, compress);
}

/// <summary>
Expand Down
Loading
Loading