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

Unify SQL query generation for sql-based providers. #147

Merged
merged 15 commits into from
Sep 29, 2024
3 changes: 2 additions & 1 deletion Serilog.Ui.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Postgre/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
37 changes: 37 additions & 0 deletions src/Serilog.Ui.Core/QueryBuilder/Sql/SinkColumnNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Serilog.Ui.Core.QueryBuilder.Sql;

/// <summary>
/// Represents the column names used in the SQL-based sink for logging.
/// </summary>
public abstract class SinkColumnNames
{
/// <summary>
/// Gets or sets the message of the log entry.
/// </summary>
public string Message { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the message template of the log entry.
/// </summary>
public string MessageTemplate { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the level of the log entry.
/// </summary>
public string Level { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the timestamp of the log entry.
/// </summary>
public string Timestamp { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the exception of the log entry.
/// </summary>
public string Exception { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the serialized log event like properties.
/// </summary>
public string LogEventSerialized { get; set; } = string.Empty;
}
67 changes: 67 additions & 0 deletions src/Serilog.Ui.Core/QueryBuilder/Sql/SqlQueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Serilog.Ui.Core.Attributes;
using Serilog.Ui.Core.Models;
using System.Reflection;
using static Serilog.Ui.Core.Models.SearchOptions;

namespace Serilog.Ui.Core.QueryBuilder.Sql;

/// <summary>
/// Abstract class that provides methods to build SQL queries for fetching and counting logs.
/// </summary>
public abstract class SqlQueryBuilder<TModel> where TModel : LogModel
{
/// <summary>
/// Builds a SQL query to fetch logs from the specified table.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="schema">The schema of the table.</param>
/// <param name="tableName">The name of the table.</param>
/// <param name="query">The query parameters for fetching logs.</param>
/// <returns>A SQL query string to fetch logs.</returns>
public abstract string BuildFetchLogsQuery(SinkColumnNames columns, string schema, string tableName, FetchLogsQuery query);

/// <summary>
/// Builds a SQL query to count logs in the specified table.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="schema">The schema of the table.</param>
/// <param name="tableName">The name of the table.</param>
/// <param name="query">The query parameters for counting logs.</param>
/// <returns>A SQL query string to count logs.</returns>
public abstract string BuildCountLogsQuery(SinkColumnNames columns, string schema, string tableName, FetchLogsQuery query);

/// <summary>
/// Generates a SQL sort clause based on the specified sort property and direction.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="sortOn">The property to sort on.</param>
/// <param name="sortBy">The direction to sort by.</param>
/// <returns>A SQL sort clause string.</returns>
protected abstract string GenerateSortClause(SinkColumnNames columns, SortProperty sortOn, SortDirection sortBy);

/// <summary>
/// Generates a SQL sort clause based on the specified sort property and direction.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="sortOn">The property to sort on.</param>
/// <returns>A SQL sort clause string.</returns>
protected static string GetSortColumnName(SinkColumnNames columns, SortProperty sortOn) => sortOn switch
{
SortProperty.Timestamp => columns.Timestamp,
SortProperty.Level => columns.Level,
SortProperty.Message => columns.Message,
_ => columns.Timestamp
};

/// <summary>
/// Determines whether to add the exception column to the WHERE clause based on the presence of the RemovedColumnAttribute.
/// </summary>
/// <returns>True if the exception column should be added to the WHERE clause; otherwise, false.</returns>
protected static bool AddExceptionToWhereClause()
{
PropertyInfo? exceptionProperty = typeof(TModel).GetProperty("Exception");
RemovedColumnAttribute? att = exceptionProperty?.GetCustomAttribute<RemovedColumnAttribute>();

return att is null;
}
}
2 changes: 1 addition & 1 deletion src/Serilog.Ui.Core/Serilog.Ui.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.4"/>
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,64 +1,64 @@
using System;
using Dapper;
using Dapper;
using Microsoft.Extensions.DependencyInjection;
using Serilog.Ui.Core;
using Serilog.Ui.Core.Interfaces;
using Serilog.Ui.Core.Models.Options;
using System;

namespace Serilog.Ui.MsSqlServerProvider.Extensions
{
/// <summary>
/// SQL Server data provider specific extension methods for <see cref="ISerilogUiOptionsBuilder"/>.
/// </summary>
public static class SerilogUiOptionBuilderExtensions
{
/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) => optionsBuilder.UseSqlServer<SqlServerLogModel>(setupOptions, dateTimeCustomParsing);

/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <typeparam name="T">The log model, containing any additional columns. It must inherit <see cref="SqlServerLogModel"/>.</typeparam>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer<T>(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) where T : SqlServerLogModel
{
var dbOptions = new RelationalDbOptions("dbo");
setupOptions(dbOptions);
dbOptions.Validate();

var providerName = dbOptions.GetProviderName(SqlServerDataProvider.MsSqlProviderName);
namespace Serilog.Ui.MsSqlServerProvider.Extensions;

optionsBuilder.RegisterExceptionAsStringForProviderKey(providerName);
SqlMapper.AddTypeHandler(new DapperDateTimeHandler(dateTimeCustomParsing));
/// <summary>
/// SQL Server data provider specific extension methods for <see cref="ISerilogUiOptionsBuilder"/>.
/// </summary>
public static class SerilogUiOptionBuilderExtensions
{
/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) => optionsBuilder.UseSqlServer<SqlServerLogModel>(setupOptions, dateTimeCustomParsing);

var customModel = typeof(T) != typeof(SqlServerLogModel);
if (customModel)
{
optionsBuilder.RegisterColumnsInfo<T>(providerName);
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new SqlServerDataProvider<T>(dbOptions));
/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <typeparam name="T">The log model, containing any additional columns. It must inherit <see cref="SqlServerLogModel"/>.</typeparam>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer<T>(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) where T : SqlServerLogModel
{
SqlServerDbOptions dbOptions = new("dbo");
setupOptions(dbOptions);
dbOptions.Validate();

return optionsBuilder;
}
string providerName = dbOptions.GetProviderName(SqlServerDataProvider.MsSqlProviderName);
optionsBuilder.RegisterExceptionAsStringForProviderKey(providerName);
SqlMapper.AddTypeHandler(new DapperDateTimeHandler(dateTimeCustomParsing));

optionsBuilder.Services.AddScoped<IDataProvider>(_ => new SqlServerDataProvider(dbOptions));
return optionsBuilder;
bool customModel = typeof(T) != typeof(SqlServerLogModel);
if (customModel)
{
optionsBuilder.RegisterColumnsInfo<T>(providerName);
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new SqlServerDataProvider<T>(dbOptions, new SqlServerQueryBuilder<T>()));
}
else
{
optionsBuilder.Services.AddScoped<IDataProvider>(_ =>
new SqlServerDataProvider(dbOptions, new SqlServerQueryBuilder<SqlServerLogModel>()));
}

return optionsBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Serilog.Ui.Core.Models.Options;
using Serilog.Ui.Core.QueryBuilder.Sql;
using Serilog.Ui.MsSqlServerProvider.Models;

namespace Serilog.Ui.MsSqlServerProvider.Extensions;

public class SqlServerDbOptions(string defaultSchemaName) : RelationalDbOptions(defaultSchemaName)
{
public SinkColumnNames ColumnNames { get; } = new SqlServerSinkColumnNames();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Serilog.Ui.Core.QueryBuilder.Sql;

namespace Serilog.Ui.MsSqlServerProvider.Models;

internal class SqlServerSinkColumnNames : SinkColumnNames
{
public SqlServerSinkColumnNames()
{
Exception = "Exception";
Level = "Level";
LogEventSerialized = "Properties";
Message = "Message";
MessageTemplate = "";
Timestamp = "TimeStamp";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Version>3.0.0</Version>
<Version>3.1.0</Version>

<Description>Microsoft SQL Server data provider for Serilog UI.</Description>
<PackageTags>serilog serilog-ui serilog.sinks.mssqlserver mssqlserver</PackageTags>
Expand All @@ -18,5 +18,6 @@

<ItemGroup>
<ProjectReference Include="..\Serilog.Ui.Core\Serilog.Ui.Core.csproj" PrivateAssets="All" />
<InternalsVisibleTo Include="MsSql.Tests" />
</ItemGroup>
</Project>
Loading
Loading