Skip to content

Commit

Permalink
customize log params
Browse files Browse the repository at this point in the history
  • Loading branch information
patrofimov committed Aug 19, 2024
1 parent cf0ce5d commit 841236c
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 40 deletions.
4 changes: 2 additions & 2 deletions Vostok.ClusterClient.Core.Tests/Model/Request_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,15 +617,15 @@ public void ToString_should_return_correct_value_when_printing_both_query_and_he
{
request = request.WithHeader("name", "value");

request.ToString(true, true).Should().Be("POST http://foo/bar?a=b" + Environment.NewLine + "name: value");
request.ToString(true, true).Should().Be("POST http://foo/bar?a=b" + Environment.NewLine + "name=value");
}

[Test]
public void ToString_should_return_correct_value_when_printing_headers_but_omitting_query()
{
request = request.WithHeader("name", "value");

request.ToString(false, true).Should().Be("POST http://foo/bar" + Environment.NewLine + "name: value");
request.ToString(false, true).Should().Be("POST http://foo/bar" + Environment.NewLine + "name=value");
}

[Test]
Expand Down
2 changes: 1 addition & 1 deletion Vostok.ClusterClient.Core.Tests/Model/Response_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void ToString_should_return_correct_representation_when_printing_headers(
{
var response = new Response(ResponseCode.Ok, headers: Headers.Empty.Set("name", "value"));

response.ToString(true).Should().Be("200 Ok" + Environment.NewLine + "name: value");
response.ToString(true).Should().Be("200 Ok" + Environment.NewLine + "name=value");
}

[Test]
Expand Down
44 changes: 44 additions & 0 deletions Vostok.ClusterClient.Core/Misc/LoggingOptions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using JetBrains.Annotations;

namespace Vostok.Clusterclient.Core.Misc
Expand All @@ -8,6 +9,10 @@ namespace Vostok.Clusterclient.Core.Misc
[PublicAPI]
public class LoggingOptions
{
private RequestParametersLoggingSettings logQueryString = false;
private RequestParametersLoggingSettings logRequestHeaders = false;
private RequestParametersLoggingSettings logResponseHeaders = false;

/// <summary>
/// <para>Gets or sets whether to log request details before execution.</para>
/// <para>This parameter is optional and has a default value (see <see cref="ClusterClientDefaults.LogRequestDetails"/>).</para>
Expand Down Expand Up @@ -38,5 +43,44 @@ public class LoggingOptions
/// If <see cref="Misc.LoggingMode.SingleVerboseMessage"/> is set, only one detailed message about communication with the cluster will be logged with the results from each replica.
/// </summary>
public LoggingMode LoggingMode { get; set; } = ClusterClientDefaults.LoggingMode;

/// <summary>
/// <para>Request query parameters logging options.</para>
/// <para>By default, query parameters are not logged at all.</para>
/// </summary>
[NotNull]
public RequestParametersLoggingSettings LogQueryString
{
get =>
logQueryString;
set =>
logQueryString = value.ToCaseInsensitive() ?? throw new ArgumentNullException(nameof(LogQueryString));
}

/// <summary>
/// <para>Request headers logging options.</para>
/// <para>By default, request headers are not logged at all.</para>
/// </summary>
[NotNull]
public RequestParametersLoggingSettings LogRequestHeaders
{
get =>
logRequestHeaders;
set =>
logRequestHeaders = value.ToCaseInsensitive() ?? throw new ArgumentNullException(nameof(LogRequestHeaders));
}

/// <summary>
/// <para>Response headers logging options.</para>
/// <para>By default, response headers are not logged at all.</para>
/// </summary>
[NotNull]
public RequestParametersLoggingSettings LogResponseHeaders
{
get =>
logResponseHeaders;
set =>
logResponseHeaders = value.ToCaseInsensitive() ?? throw new ArgumentNullException(nameof(LogResponseHeaders));
}
}
}
76 changes: 76 additions & 0 deletions Vostok.ClusterClient.Core/Misc/LoggingUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Linq;
using System.Text;
using Vostok.Clusterclient.Core.Model;

namespace Vostok.Clusterclient.Core.Misc
{
internal static class LoggingUtils
{
public static void AppendQueryString(StringBuilder builder, Uri uri, RequestParametersLoggingSettings querySettings)
{
if (querySettings.IsEnabledForAllKeys())
{
builder.Append(uri.Query);
return;
}

var writtenFirst = false;
var requestUrlParser = new RequestUrlParser(uri.ToString());
foreach (var pair in requestUrlParser.Where(kvp => querySettings.IsEnabledForKey(kvp.Key)))
{
if (!writtenFirst)
{
builder.Append('?');
writtenFirst = true;
}

builder.Append(pair.Key);
builder.Append('=');
builder.Append(pair.Value);
}
}

public static void AppendHeaders(StringBuilder builder, Headers headers, RequestParametersLoggingSettings headersSettings, bool singleLineManner)
{
var writtenFirst = false;
foreach (var pair in headers)
{
if (!headersSettings.IsEnabledForKey(pair.Name))
continue;

if (!writtenFirst)
{
if (singleLineManner)
{
builder.Append(" ");
}
else
{
builder.AppendLine();
}

builder.Append("Headers:");
writtenFirst = true;
}

if (singleLineManner)
{
builder.Append(" (");
builder.Append(pair.Name);
builder.Append('=');
builder.Append(pair.Value);
builder.Append(')');
}
else
{
builder.AppendLine();
builder.Append('\t');
builder.Append(pair.Name);
builder.Append('=');
builder.Append(pair.Value);
}
}
}
}
}
37 changes: 37 additions & 0 deletions Vostok.ClusterClient.Core/Misc/RequestParametersLoggingSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Generic;
using JetBrains.Annotations;

namespace Vostok.Clusterclient.Core.Misc
{
[PublicAPI]
public class RequestParametersLoggingSettings
{
public RequestParametersLoggingSettings(bool enabled)
{
Enabled = enabled;
}

/// <summary>
/// Flag that decides whether to log request or response parameters.
/// </summary>
public bool Enabled { get; }

/// <summary>
/// <para>Case-insensitive whitelist of parameter keys to be logged.</para>
/// <para><c>null</c> value allows all keys.</para>
/// <para>Takes precedence over <see cref="Blacklist"/>.</para>
/// </summary>
[CanBeNull]
public IReadOnlyCollection<string> Whitelist { get; set; }

/// <summary>
/// <para>Case-insensitive blacklist of parameter keys to be logged.</para>
/// <para><c>null</c> value allows all keys.</para>
/// </summary>
[CanBeNull]
public IReadOnlyCollection<string> Blacklist { get; set; }

public static implicit operator RequestParametersLoggingSettings(bool enabled) =>
new RequestParametersLoggingSettings(enabled);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;

namespace Vostok.Clusterclient.Core.Misc
{
internal static class RequestParametersLoggingSettingsExtensions
{
public static bool IsEnabledForAllKeys([NotNull] this RequestParametersLoggingSettings settings) =>
IsEmpty(settings.Whitelist) && IsEmpty(settings.Blacklist);

public static bool IsEnabledForKey([NotNull] this RequestParametersLoggingSettings settings, [NotNull] string key)
{
if (IsEmpty(settings.Whitelist))
return IsEmpty(settings.Blacklist) || !settings.Blacklist!.Contains(key);

return settings.Whitelist!.Contains(key);
}

public static RequestParametersLoggingSettings ToCaseInsensitive([CanBeNull] this RequestParametersLoggingSettings settings)
=> settings == null
? null
: new RequestParametersLoggingSettings(settings.Enabled)
{
Whitelist = IsEmpty(settings.Whitelist) ? null : new HashSet<string>(settings.Whitelist!, StringComparer.OrdinalIgnoreCase),
Blacklist = IsEmpty(settings.Blacklist) ? null : new HashSet<string>(settings.Blacklist!, StringComparer.OrdinalIgnoreCase)
};

private static bool IsEmpty(IReadOnlyCollection<string> collection) => collection == null || collection.Count == 0;
}
}
38 changes: 28 additions & 10 deletions Vostok.ClusterClient.Core/Model/Request.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Text;
using JetBrains.Annotations;
using Vostok.Clusterclient.Core.Misc;

namespace Vostok.Clusterclient.Core.Model
{
Expand Down Expand Up @@ -229,29 +230,46 @@ public override string ToString()
return ToString(false, false);
}

/// <param name="includeQuery">Append query string to result</param>
/// <param name="includeHeaders">Append all headers to result</param>
/// <returns>String representation of <see cref="Request"/> instance.</returns>
[PublicAPI]
public string ToString(bool includeQuery, bool includeHeaders)
{
return ToString(includeQuery, includeHeaders, singleLineManner: false);
}

/// <inheritdoc cref="ToString(bool,bool)"/>
[PublicAPI]
public string ToString([NotNull] RequestParametersLoggingSettings includeQuery, [NotNull] RequestParametersLoggingSettings includeHeaders)
{
return ToString(includeQuery, includeHeaders, singleLineManner: false);
}

internal string ToString([NotNull] RequestParametersLoggingSettings querySettings, [NotNull] RequestParametersLoggingSettings headersSettings, bool singleLineManner)
{
if (querySettings == null)
throw new ArgumentNullException(nameof(querySettings));
if (headersSettings == null)
throw new ArgumentNullException(nameof(headersSettings));

var builder = new StringBuilder();

builder.Append(Method);
builder.Append(" ");

var urlString = Url.ToString();
// todo (patrofimov) test
var path = Url.GetLeftPart(UriPartial.Path);
builder.Append(path);

if (!includeQuery)
if (querySettings.Enabled)
{
var queryBeginning = urlString.IndexOf("?", StringComparison.Ordinal);
if (queryBeginning >= 0)
urlString = urlString.Substring(0, queryBeginning);
LoggingUtils.AppendQueryString(builder, Url, querySettings);
}

builder.Append(urlString);

if (includeHeaders && Headers != null && Headers.Count > 0)
if (headersSettings.Enabled)
{
builder.AppendLine();
builder.Append(Headers);
LoggingUtils.AppendHeaders(builder, Headers, headersSettings, singleLineManner);
}

return builder.ToString();
Expand Down
14 changes: 11 additions & 3 deletions Vostok.ClusterClient.Core/Model/RequestUrlParser.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using StringComparison = System.StringComparison;

namespace Vostok.Clusterclient.Core.Model;

internal readonly struct RequestUrlParser
internal readonly struct RequestUrlParser : IEnumerable<KeyValuePair<string, string>>
{
private readonly Dictionary<string, string> query = new();

public RequestUrlParser([CanBeNull] string url)
{
if (url == null)
return;

var question = url.IndexOf("?", StringComparison.Ordinal);
if (question < 0)
return;
Expand All @@ -23,7 +25,7 @@ public RequestUrlParser([CanBeNull] string url)
foreach (var parameter in parameters)
{
var tokens = parameter.Split('=');
query[Uri.UnescapeDataString(tokens[0])] =
query[Uri.UnescapeDataString(tokens[0])] =
tokens.Length > 1 && !string.IsNullOrEmpty(tokens[1]) ? Uri.UnescapeDataString(tokens[1]) : string.Empty;
}
}
Expand All @@ -38,4 +40,10 @@ public bool TryGetQueryParameter([CanBeNull] string key, out string value)

return query.TryGetValue(key, out value);
}

public IEnumerator<KeyValuePair<string, string>> GetEnumerator() =>
query.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
24 changes: 20 additions & 4 deletions Vostok.ClusterClient.Core/Model/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.Text;
using JetBrains.Annotations;
using Vostok.Clusterclient.Core.Misc;

namespace Vostok.Clusterclient.Core.Model
{
Expand Down Expand Up @@ -167,20 +168,35 @@ public override string ToString()
return ToString(false);
}

/// <param name="includeHeaders">Append all headers to result</param>
/// <returns>String representation of current <see cref="Response"/> instance.</returns>
[PublicAPI]
public string ToString(bool includeHeaders)
{
return ToString(includeHeaders, singleLineManner: false);
}

/// <inheritdoc cref="ToString(bool)"/>
[PublicAPI]
public string ToString([NotNull] RequestParametersLoggingSettings headersSettings)
{
return ToString(headersSettings, singleLineManner: false);
}

internal string ToString([NotNull] RequestParametersLoggingSettings headersSettings, bool singleLineManner)
{
if (headersSettings == null)
throw new ArgumentNullException(nameof(headersSettings));

var builder = new StringBuilder();

builder.Append((int) Code);
builder.Append((int)Code);
builder.Append(" ");
builder.Append(Code);

if (includeHeaders && headers != null && headers.Count > 0)
if (headersSettings.Enabled)
{
builder.AppendLine();
builder.Append(headers);
LoggingUtils.AppendHeaders(builder, Headers, headersSettings, singleLineManner);
}

return builder.ToString();
Expand Down
Loading

0 comments on commit 841236c

Please sign in to comment.