Skip to content

Commit

Permalink
improve query logging
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Apr 17, 2024
1 parent 7f3e02f commit 1f92732
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 124 deletions.
4 changes: 4 additions & 0 deletions src/FluentCommand.Generators/Properties/launchSettings.json
Expand Up @@ -3,6 +3,10 @@
"FluentCommand.Tests": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\..\\test\\FluentCommand.Tests\\FluentCommand.Tests.csproj"
},
"FluentCommand.Entities": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\..\\test\\FluentCommand.Entities\\FluentCommand.Entities.csproj"
}
}
}
78 changes: 63 additions & 15 deletions src/FluentCommand/DataQueryLogger.cs
@@ -1,29 +1,31 @@
using System.Data;
using System.Text;

using FluentCommand.Extensions;
using FluentCommand.Internal;

using Microsoft.Extensions.Logging;

namespace FluentCommand;

/// <summary>
/// A class for logging queries
/// A class for logging queries
/// </summary>
/// <seealso cref="FluentCommand.IDataQueryLogger" />
public partial class DataQueryLogger : IDataQueryLogger
{
private readonly ILogger<DataQueryLogger> _logger;
private readonly IDataQueryFormatter _formatter;

/// <summary>
/// Initializes a new instance of the <see cref="DataQueryLogger"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="formatter">The formatter for the data command</param>
public DataQueryLogger(ILogger<DataQueryLogger> logger, IDataQueryFormatter formatter)
public DataQueryLogger(ILogger<DataQueryLogger> logger)
{
_logger = logger;
_formatter = formatter;
Logger = logger;
}

protected ILogger Logger { get; }

/// <summary>
/// Log the current specified <paramref name="command" />
/// </summary>
Expand All @@ -34,23 +36,69 @@ public DataQueryLogger(ILogger<DataQueryLogger> logger, IDataQueryFormatter form
/// <exception cref="System.ArgumentNullException">command</exception>
public virtual void LogCommand(IDbCommand command, TimeSpan duration, Exception exception = null, object state = null)
{
if (_logger == null)
if (Logger == null)
return;

if (command is null)
throw new ArgumentNullException(nameof(command));

var output = _formatter.FormatCommand(command, duration, exception);
var elapsed = duration.TotalMilliseconds;
var commandType = command.CommandType;
var commandTimeout = command.CommandTimeout;
var commandText = command.CommandText;
var parameterText = FormatParameters(command);

if (exception == null)
LogCommand(output);
LogCommand(Logger, elapsed, commandType, commandTimeout, commandText, parameterText);
else
LogError(output, exception);
LogError(Logger, elapsed, commandType, commandTimeout, commandText, parameterText, exception);
}

protected static string FormatParameters(IDbCommand command)
{
if (command is null || command.Parameters == null || command.Parameters.Count == 0)
return string.Empty;

var parameterText = StringBuilderCache.Acquire();

foreach (IDataParameter parameter in command.Parameters)
{
int precision = 0;
int scale = 0;
int size = 0;

if (parameter is IDbDataParameter dataParameter)
{
precision = dataParameter.Precision;
scale = dataParameter.Scale;
size = dataParameter.Size;
}

parameterText
.AppendLineIf(() => parameterText.Length > 0)
.Append("-- ")
.Append(parameter.ParameterName)
.Append(": ")
.Append(parameter.Direction)
.Append(" ")
.Append(parameter.DbType)
.Append("(Size=")
.Append(size)
.Append("; Precision=")
.Append(precision)
.Append("; Scale=")
.Append(scale)
.Append(") [")
.Append(parameter.Value)
.Append("]");
}

return parameterText.ToString();
}

[LoggerMessage(0, LogLevel.Debug, "{output}")]
private partial void LogCommand(string output);
[LoggerMessage(0, LogLevel.Debug, "Executed DbCommand ({Elapsed} ms) [CommandType='{CommandType}', CommandTimeout='{CommandTimeout}']\r\n{CommandText}\r\n{ParameterText}")]
protected static partial void LogCommand(ILogger logger, double elapsed, CommandType commandType, int commandTimeout, string commandText, string parameterText);

[LoggerMessage(1, LogLevel.Error, "{output}")]
private partial void LogError(string output, Exception exception);
[LoggerMessage(1, LogLevel.Error, "Error Executing DbCommand ({Elapsed} ms) [CommandType='{CommandType}', CommandTimeout='{CommandTimeout}']\r\n{CommandText}\r\n{ParameterText}")]
protected static partial void LogError(ILogger logger, double elapsed, CommandType commandType, int commandTimeout, string commandText, string parameterText, Exception exception);
}
113 changes: 113 additions & 0 deletions src/FluentCommand/Extensions/StringBuilderExtensions.cs
@@ -0,0 +1,113 @@
using System.Text;

namespace FluentCommand.Extensions;

/// <summary>
/// <see cref="StringBuilder"/> extension methods
/// </summary>
public static class StringBuilderExtensions
{
/// <summary>
/// Appends a copy of the specified string followed by the default line terminator to the end of the StringBuilder object.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
public static StringBuilder AppendLine(this StringBuilder sb, string format, params object[] args)
{
sb.AppendFormat(format, args);
sb.AppendLine();
return sb;
}

/// <summary>
/// Appends a copy of the specified string if <paramref name="condition"/> is met.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="text">The string to append.</param>
/// <param name="condition">The condition delegate to evaluate. If condition is null, String.IsNullOrWhiteSpace method will be used.</param>
public static StringBuilder AppendIf(this StringBuilder sb, string text, Func<string, bool> condition = null)
{
var c = condition ?? (s => !string.IsNullOrEmpty(s));

if (c(text))
sb.Append(text);

return sb;
}

/// <summary>
/// Appends a copy of the specified string if <paramref name="condition"/> is met.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="text">The string to append.</param>
/// <param name="condition">The condition delegate to evaluate. If condition is null, String.IsNullOrWhiteSpace method will be used.</param>
public static StringBuilder AppendIf(this StringBuilder sb, string text, bool condition)
{
if (condition)
sb.Append(text);

return sb;
}

/// <summary>
/// Appends a copy of the specified string followed by the default line terminator if <paramref name="condition"/> is met.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="text">The string to append.</param>
/// <param name="condition">The condition delegate to evaluate. If condition is null, String.IsNullOrWhiteSpace method will be used.</param>
public static StringBuilder AppendLineIf(this StringBuilder sb, string text, Func<string, bool> condition = null)
{
var c = condition ?? (s => !string.IsNullOrEmpty(s));

if (c(text))
sb.AppendLine(text);

return sb;
}

/// <summary>
/// Appends a copy of the specified string followed by the default line terminator if <paramref name="condition"/> is met.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="condition">The condition delegate to evaluate. If condition is null, String.IsNullOrWhiteSpace method will be used.</param>
public static StringBuilder AppendLineIf(this StringBuilder sb, Func<bool> condition)
{
if (condition())
sb.AppendLine();

return sb;
}

/// <summary>
/// Concatenates and appends the members of a collection, using the specified separator between each member.
/// </summary>
/// <typeparam name="T">The type of the members of values.</typeparam>
/// <param name="sb">A reference to this instance after the append operation has completed.</param>
/// <param name="separator">The string to use as a separator. separator is included in the concatenated and appended strings only if values has more than one element.</param>
/// <param name="values">A collection that contains the objects to concatenate and append to the current instance of the string builder.</param>
/// <returns>A reference to this instance after the append operation has completed.</returns>
public static StringBuilder AppendJoin<T>(this StringBuilder sb, string separator, IEnumerable<T> values)
{
if (sb is null)
throw new ArgumentNullException(nameof(sb));
if (values is null)
throw new ArgumentNullException(nameof(values));

separator ??= string.Empty;

var wroteValue = false;

foreach (var value in values)
{
if (wroteValue)
sb.Append(separator);

sb.Append(value);
wroteValue = true;
}

return sb;
}

}
112 changes: 11 additions & 101 deletions src/FluentCommand/Extensions/StringExtensions.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;

#nullable enable

namespace FluentCommand.Extensions;

/// <summary>
Expand All @@ -16,7 +19,7 @@ public static class StringExtensions
/// <returns>
/// <c>true</c> if is null or empty; otherwise, <c>false</c>.
/// </returns>
public static bool IsNullOrEmpty(this string item)
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? item)
{
return string.IsNullOrEmpty(item);
}
Expand All @@ -28,7 +31,7 @@ public static bool IsNullOrEmpty(this string item)
/// <returns>
/// <c>true</c> if is null or empty; otherwise, <c>false</c>.
/// </returns>
public static bool IsNullOrWhiteSpace(this string item)
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? item)
{
if (item == null)
return true;
Expand All @@ -47,112 +50,19 @@ public static bool IsNullOrWhiteSpace(this string item)
/// <returns>
/// <c>true</c> if the specified <paramref name="value"/> is not <see cref="IsNullOrEmpty"/>; otherwise, <c>false</c>.
/// </returns>
public static bool HasValue(this string value)
public static bool HasValue([NotNullWhen(true)] this string? value)
{
return !string.IsNullOrEmpty(value);
}

/// <summary>
/// Uses the string as a format
/// Replaces the format item in a specified string with the string representation of a corresponding object in a specified array.
/// </summary>
/// <param name="format">A String reference</param>
/// <param name="args">Object parameters that should be formatted</param>
/// <returns>Formatted string</returns>
public static string FormatWith(this string format, params object[] args)
/// <param name="format">A composite format string</param>
/// <param name="args">An object array that contains zero or more objects to format</param>
/// <returns>A copy of format in which the format items have been replaced by the string representation of the corresponding objects in args</returns>
public static string FormatWith(this string format, params object?[] args)
{
if (format == null)
throw new ArgumentNullException("format");

return string.Format(format, args);
}

/// <summary>
/// Appends a copy of the specified string followed by the default line terminator to the end of the StringBuilder object.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
public static StringBuilder AppendLine(this StringBuilder sb, string format, params object[] args)
{
sb.AppendFormat(format, args);
sb.AppendLine();
return sb;
}

/// <summary>
/// Appends a copy of the specified string if <paramref name="condition"/> is met.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="text">The string to append.</param>
/// <param name="condition">The condition delegate to evaluate. If condition is null, String.IsNullOrWhiteSpace method will be used.</param>
public static StringBuilder AppendIf(this StringBuilder sb, string text, Func<string, bool> condition = null)
{
var c = condition ?? (s => !string.IsNullOrEmpty(s));

if (c(text))
sb.Append(text);

return sb;
}

/// <summary>
/// Appends a copy of the specified string if <paramref name="condition"/> is met.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="text">The string to append.</param>
/// <param name="condition">The condition delegate to evaluate. If condition is null, String.IsNullOrWhiteSpace method will be used.</param>
public static StringBuilder AppendIf(this StringBuilder sb, string text, bool condition)
{
if (condition)
sb.Append(text);

return sb;
}

/// <summary>
/// Appends a copy of the specified string followed by the default line terminator if <paramref name="condition"/> is met.
/// </summary>
/// <param name="sb">The StringBuilder instance to append to.</param>
/// <param name="text">The string to append.</param>
/// <param name="condition">The condition delegate to evaluate. If condition is null, String.IsNullOrWhiteSpace method will be used.</param>
public static StringBuilder AppendLineIf(this StringBuilder sb, string text, Func<string, bool> condition = null)
{
var c = condition ?? (s => !string.IsNullOrEmpty(s));

if (c(text))
sb.AppendLine(text);

return sb;
}

/// <summary>
/// Concatenates and appends the members of a collection, using the specified separator between each member.
/// </summary>
/// <typeparam name="T">The type of the members of values.</typeparam>
/// <param name="sb">A reference to this instance after the append operation has completed.</param>
/// <param name="separator">The string to use as a separator. separator is included in the concatenated and appended strings only if values has more than one element.</param>
/// <param name="values">A collection that contains the objects to concatenate and append to the current instance of the string builder.</param>
/// <returns>A reference to this instance after the append operation has completed.</returns>
public static StringBuilder AppendJoin<T>(this StringBuilder sb, string separator, IEnumerable<T> values)
{
if (sb is null)
throw new ArgumentNullException(nameof(sb));
if (values is null)
throw new ArgumentNullException(nameof(values));

separator ??= string.Empty;

var wroteValue = false;

foreach (var value in values)
{
if (wroteValue)
sb.Append(separator);

sb.Append(value);
wroteValue = true;
}

return sb;
}
}

0 comments on commit 1f92732

Please sign in to comment.