diff --git a/src/EFCore.Cosmos/Diagnostics/CosmosEventId.cs b/src/EFCore.Cosmos/Diagnostics/CosmosEventId.cs index 21116209ed4..96eab5d1bbb 100644 --- a/src/EFCore.Cosmos/Diagnostics/CosmosEventId.cs +++ b/src/EFCore.Cosmos/Diagnostics/CosmosEventId.cs @@ -23,37 +23,116 @@ public static class CosmosEventId // Try to use naming and be consistent with existing names. private enum Id { - // Update events + // Database events - // Query events - // These events are actually in Event in `DbLoggerCategory.Database.Command`. - // Leaving the ID unchanged to avoid changing it after release. + // Command events ExecutingSqlQuery = CoreEventId.ProviderBaseId + 100, - ExecutingReadItem + ExecutingReadItem, + ExecutedReadNext, + ExecutedReadItem, + ExecutedCreateItem, + ExecutedReplaceItem, + ExecutedDeleteItem } private static readonly string _commandPrefix = DbLoggerCategory.Database.Command.Name + "."; /// /// - /// A SQL query was executed. + /// A SQL query is going to be executed. /// /// /// This event is in the category. /// + /// + /// This event uses the payload when used with a . + /// /// public static readonly EventId ExecutingSqlQuery = new((int)Id.ExecutingSqlQuery, _commandPrefix + Id.ExecutingSqlQuery); /// /// - /// ReadItem was executed. + /// ReadItem is going to be executed. /// /// /// This event is in the category. /// + /// + /// This event uses the payload when used with a . + /// /// public static readonly EventId ExecutingReadItem = new((int)Id.ExecutingReadItem, _commandPrefix + Id.ExecutingReadItem); + + /// + /// + /// ReadNext was executed. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ExecutedReadNext + = new((int)Id.ExecutedReadNext, _commandPrefix + Id.ExecutedReadNext); + + /// + /// + /// ReadItem was executed. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ExecutedReadItem + = new((int)Id.ExecutedReadItem, _commandPrefix + Id.ExecutedReadItem); + + /// + /// + /// CreateItem was executed. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ExecutedCreateItem + = new((int)Id.ExecutedCreateItem, _commandPrefix + Id.ExecutedCreateItem); + + /// + /// + /// ReplaceItem was executed. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ExecutedReplaceItem + = new((int)Id.ExecutedReplaceItem, _commandPrefix + Id.ExecutedReplaceItem); + + /// + /// + /// DeleteItem was executed. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ExecutedDeleteItem + = new((int)Id.ExecutedDeleteItem, _commandPrefix + Id.ExecutedDeleteItem); } } diff --git a/src/EFCore.Cosmos/Diagnostics/CosmosItemCommandExecutedEventData.cs b/src/EFCore.Cosmos/Diagnostics/CosmosItemCommandExecutedEventData.cs new file mode 100644 index 00000000000..c7aed77e541 --- /dev/null +++ b/src/EFCore.Cosmos/Diagnostics/CosmosItemCommandExecutedEventData.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.EntityFrameworkCore.Diagnostics +{ + /// + /// A event payload class for Cosmos item command executed events. + /// + public class CosmosItemCommandExecutedEventData : EventData + { + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// The time elapsed since the command was sent to the database. + /// The request charge in RU. + /// The activity ID. + /// The ID of the resource being read. + /// The ID of the Cosmos container being queried. + /// The key of the Cosmos partition that the query is using. + /// Indicates whether the application allows logging of sensitive data. + public CosmosItemCommandExecutedEventData( + EventDefinitionBase eventDefinition, + Func messageGenerator, + TimeSpan elapsed, + double requestCharge, + string activityId, + string containerId, + string resourceId, + string? partitionKey, + bool logSensitiveData) + : base(eventDefinition, messageGenerator) + { + Elapsed = elapsed; + RequestCharge = requestCharge; + ActivityId = activityId; + ContainerId = containerId; + ResourceId = resourceId; + PartitionKey = partitionKey; + LogSensitiveData = logSensitiveData; + } + + /// + /// The time elapsed since the command was sent to the database. + /// + public virtual TimeSpan Elapsed { get; } + + /// + /// The request charge in RU. + /// + public virtual double RequestCharge { get; } + + /// + /// The activity ID. + /// + public virtual string ActivityId { get; } + + /// + /// The ID of the Cosmos container being queried. + /// + public virtual string ContainerId { get; } + + /// + /// The ID of the resource being read. + /// + public virtual string ResourceId { get; } + + /// + /// The key of the Cosmos partition that the query is using. + /// + public virtual string? PartitionKey { get; } + + /// + /// Indicates whether the application allows logging of sensitive data. + /// + public virtual bool LogSensitiveData { get; } + } +} diff --git a/src/EFCore.Cosmos/Diagnostics/CosmosQueryEventData.cs b/src/EFCore.Cosmos/Diagnostics/CosmosQueryEventData.cs index a12c5bd0c3f..5214caa8006 100644 --- a/src/EFCore.Cosmos/Diagnostics/CosmosQueryEventData.cs +++ b/src/EFCore.Cosmos/Diagnostics/CosmosQueryEventData.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Diagnostics; namespace Microsoft.EntityFrameworkCore.Diagnostics @@ -21,7 +19,7 @@ public class CosmosQueryEventData : EventData /// The key of the Cosmos partition that the query is using. /// Name/values for each parameter in the Cosmos Query. /// The SQL representing the query. - /// Indicates whether or not the application allows logging of sensitive data. + /// Indicates whether the application allows logging of sensitive data. public CosmosQueryEventData( EventDefinitionBase eventDefinition, Func messageGenerator, @@ -60,7 +58,7 @@ public CosmosQueryEventData( public virtual string QuerySql { get; } /// - /// Indicates whether or not the application allows logging of sensitive data. + /// Indicates whether the application allows logging of sensitive data. /// public virtual bool LogSensitiveData { get; } } diff --git a/src/EFCore.Cosmos/Diagnostics/CosmosQueryExecutedEventData.cs b/src/EFCore.Cosmos/Diagnostics/CosmosQueryExecutedEventData.cs new file mode 100644 index 00000000000..7208e8c56c6 --- /dev/null +++ b/src/EFCore.Cosmos/Diagnostics/CosmosQueryExecutedEventData.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.EntityFrameworkCore.Diagnostics +{ + /// + /// A event payload class for Cosmos query events. + /// + public class CosmosQueryExecutedEventData : EventData + { + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// The time elapsed since the command was sent to the database. + /// The request charge in RU. + /// The activity ID. + /// The ID of the Cosmos container being queried. + /// The key of the Cosmos partition that the query is using. + /// Name/values for each parameter in the Cosmos Query. + /// The SQL representing the query. + /// Indicates whether the application allows logging of sensitive data. + public CosmosQueryExecutedEventData( + EventDefinitionBase eventDefinition, + Func messageGenerator, + TimeSpan elapsed, + double requestCharge, + string activityId, + string containerId, + string? partitionKey, + IReadOnlyList<(string Name, object? Value)> parameters, + string querySql, + bool logSensitiveData) + : base(eventDefinition, messageGenerator) + { + Elapsed = elapsed; + RequestCharge = requestCharge; + ActivityId = activityId; + ContainerId = containerId; + PartitionKey = partitionKey; + Parameters = parameters; + QuerySql = querySql; + LogSensitiveData = logSensitiveData; + } + + /// + /// The time elapsed since the command was sent to the database. + /// + public virtual TimeSpan Elapsed { get; } + + /// + /// The request charge in RU. + /// + public virtual double RequestCharge { get; } + + /// + /// The activity ID. + /// + public virtual string ActivityId { get; } + + /// + /// The ID of the Cosmos container being queried. + /// + public virtual string ContainerId { get; } + + /// + /// The key of the Cosmos partition that the query is using. + /// + public virtual string? PartitionKey { get; } + + /// + /// Name/values for each parameter in the Cosmos Query. + /// + public virtual IReadOnlyList<(string Name, object? Value)> Parameters { get; } + + /// + /// The SQL representing the query. + /// + public virtual string QuerySql { get; } + + /// + /// Indicates whether the application allows logging of sensitive data. + /// + public virtual bool LogSensitiveData { get; } + } +} diff --git a/src/EFCore.Cosmos/Diagnostics/CosmosReadItemEventData.cs b/src/EFCore.Cosmos/Diagnostics/CosmosReadItemEventData.cs index 1c690c8b5fc..b949d3a5e06 100644 --- a/src/EFCore.Cosmos/Diagnostics/CosmosReadItemEventData.cs +++ b/src/EFCore.Cosmos/Diagnostics/CosmosReadItemEventData.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics; namespace Microsoft.EntityFrameworkCore.Diagnostics @@ -19,7 +18,7 @@ public class CosmosReadItemEventData : EventData /// The ID of the resource being read. /// The ID of the Cosmos container being queried. /// The key of the Cosmos partition that the query is using. - /// Indicates whether or not the application allows logging of sensitive data. + /// Indicates whether the application allows logging of sensitive data. public CosmosReadItemEventData( EventDefinitionBase eventDefinition, Func messageGenerator, @@ -51,7 +50,7 @@ public CosmosReadItemEventData( public virtual string? PartitionKey { get; } /// - /// Indicates whether or not the application allows logging of sensitive data. + /// Indicates whether the application allows logging of sensitive data. /// public virtual bool LogSensitiveData { get; } } diff --git a/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggerExtensions.cs b/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggerExtensions.cs index 3c29d9d4f63..16c2457bdea 100644 --- a/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggerExtensions.cs +++ b/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggerExtensions.cs @@ -1,14 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text; using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -118,6 +116,316 @@ private static string ExecutingReadItem(EventDefinitionBase definition, EventDat return d.GenerateMessage(p.LogSensitiveData ? p.ResourceId : "?", p.ContainerId, p.LogSensitiveData ? p.PartitionKey : "?"); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void ExecutedReadNext( + this IDiagnosticsLogger diagnostics, + TimeSpan elapsed, + double requestCharge, + string activityId, + string containerId, + string? partitionKey, + CosmosSqlQuery cosmosSqlQuery) + { + var definition = CosmosResources.LogExecutedReadNext(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + var logSensitiveData = diagnostics.ShouldLogSensitiveData(); + + definition.Log( + diagnostics, + l => l.Log( + definition.Level, + definition.EventId, + definition.MessageFormat, + elapsed.TotalMilliseconds, + requestCharge, + activityId, + containerId, + logSensitiveData ? partitionKey : "?", + FormatParameters(cosmosSqlQuery.Parameters, logSensitiveData && cosmosSqlQuery.Parameters.Count > 0), + Environment.NewLine, + cosmosSqlQuery.Query)); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new CosmosQueryExecutedEventData( + definition, + ExecutedReadNext, + elapsed, + requestCharge, + activityId, + containerId, + partitionKey, + cosmosSqlQuery.Parameters.Select(p => (p.Name, p.Value)).ToList(), + cosmosSqlQuery.Query, + diagnostics.ShouldLogSensitiveData()); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ExecutedReadNext(EventDefinitionBase definition, EventData payload) + { + var d = (FallbackEventDefinition)definition; + var p = (CosmosQueryExecutedEventData)payload; + return d.GenerateMessage( + l => l.Log( + d.Level, + d.EventId, + d.MessageFormat, + p.Elapsed.TotalMilliseconds, + p.RequestCharge, + p.ActivityId, + p.ContainerId, + p.LogSensitiveData ? p.PartitionKey : "?", + FormatParameters(p.Parameters, p.LogSensitiveData && p.Parameters.Count > 0), + Environment.NewLine, + p.QuerySql)); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void ExecutedReadItem( + this IDiagnosticsLogger diagnostics, + TimeSpan elapsed, + double requestCharge, + string activityId, + string resourceId, + string containerId, + string? partitionKey) + { + var definition = CosmosResources.LogExecutedReadItem(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + var logSensitiveData = diagnostics.ShouldLogSensitiveData(); + definition.Log(diagnostics, + elapsed.TotalMilliseconds.ToString(), + requestCharge.ToString(), + activityId, + containerId, + logSensitiveData ? resourceId : "?", + logSensitiveData ? partitionKey : "?"); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new CosmosItemCommandExecutedEventData( + definition, + ExecutedReadItem, + elapsed, + requestCharge, + activityId, + containerId, + resourceId, + partitionKey, + diagnostics.ShouldLogSensitiveData()); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ExecutedReadItem(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (CosmosItemCommandExecutedEventData)payload; + return d.GenerateMessage( + p.Elapsed.Milliseconds.ToString(), + p.RequestCharge.ToString(), + p.ActivityId, + p.ContainerId, + p.LogSensitiveData ? p.ResourceId : "?", + p.LogSensitiveData ? p.PartitionKey : "?"); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void ExecutedCreateItem( + this IDiagnosticsLogger diagnostics, + TimeSpan elapsed, + double requestCharge, + string activityId, + string resourceId, + string containerId, + string? partitionKey) + { + var definition = CosmosResources.LogExecutedCreateItem(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + var logSensitiveData = diagnostics.ShouldLogSensitiveData(); + definition.Log(diagnostics, + elapsed.TotalMilliseconds.ToString(), + requestCharge.ToString(), + activityId, + containerId, + logSensitiveData ? resourceId : "?", + logSensitiveData ? partitionKey : "?"); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new CosmosItemCommandExecutedEventData( + definition, + ExecutedCreateItem, + elapsed, + requestCharge, + activityId, + containerId, + resourceId, + partitionKey, + diagnostics.ShouldLogSensitiveData()); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ExecutedCreateItem(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (CosmosItemCommandExecutedEventData)payload; + return d.GenerateMessage( + p.Elapsed.Milliseconds.ToString(), + p.RequestCharge.ToString(), + p.ActivityId, + p.ContainerId, + p.LogSensitiveData ? p.ResourceId : "?", + p.LogSensitiveData ? p.PartitionKey : "?"); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void ExecutedDeleteItem( + this IDiagnosticsLogger diagnostics, + TimeSpan elapsed, + double requestCharge, + string activityId, + string resourceId, + string containerId, + string? partitionKey) + { + var definition = CosmosResources.LogExecutedDeleteItem(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + var logSensitiveData = diagnostics.ShouldLogSensitiveData(); + definition.Log(diagnostics, + elapsed.TotalMilliseconds.ToString(), + requestCharge.ToString(), + activityId, + containerId, + logSensitiveData ? resourceId : "?", + logSensitiveData ? partitionKey : "?"); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new CosmosItemCommandExecutedEventData( + definition, + ExecutedDeleteItem, + elapsed, + requestCharge, + activityId, + containerId, + resourceId, + partitionKey, + diagnostics.ShouldLogSensitiveData()); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ExecutedDeleteItem(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (CosmosItemCommandExecutedEventData)payload; + return d.GenerateMessage( + p.Elapsed.Milliseconds.ToString(), + p.RequestCharge.ToString(), + p.ActivityId, + p.ContainerId, + p.LogSensitiveData ? p.ResourceId : "?", + p.LogSensitiveData ? p.PartitionKey : "?"); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void ExecutedReplaceItem( + this IDiagnosticsLogger diagnostics, + TimeSpan elapsed, + double requestCharge, + string activityId, + string resourceId, + string containerId, + string? partitionKey) + { + var definition = CosmosResources.LogExecutedReplaceItem(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + var logSensitiveData = diagnostics.ShouldLogSensitiveData(); + definition.Log(diagnostics, + elapsed.TotalMilliseconds.ToString(), + requestCharge.ToString(), + activityId, + containerId, + logSensitiveData ? resourceId : "?", + logSensitiveData ? partitionKey : "?"); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new CosmosItemCommandExecutedEventData( + definition, + ExecutedReplaceItem, + elapsed, + requestCharge, + activityId, + containerId, + resourceId, + partitionKey, + diagnostics.ShouldLogSensitiveData()); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ExecutedReplaceItem(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (CosmosItemCommandExecutedEventData)payload; + return d.GenerateMessage( + p.Elapsed.Milliseconds.ToString(), + p.RequestCharge.ToString(), + p.ActivityId, + p.ContainerId, + p.LogSensitiveData ? p.ResourceId : "?", + p.LogSensitiveData ? p.PartitionKey : "?"); + } + private static string FormatParameters(IReadOnlyList<(string Name, object? Value)> parameters, bool shouldLogParameterValues) => FormatParameters(parameters.Select(p => new SqlParameter(p.Name, p.Value)).ToList(), shouldLogParameterValues); diff --git a/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggingDefinitions.cs b/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggingDefinitions.cs index 6230fe37f84..2287682ab65 100644 --- a/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggingDefinitions.cs +++ b/src/EFCore.Cosmos/Diagnostics/Internal/CosmosLoggingDefinitions.cs @@ -28,5 +28,45 @@ public class CosmosLoggingDefinitions : LoggingDefinitions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public EventDefinitionBase? LogExecutingReadItem; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EventDefinitionBase? LogExecutedReadNext; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EventDefinitionBase? LogExecutedReadItem; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EventDefinitionBase? LogExecutedCreateItem; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EventDefinitionBase? LogExecutedReplaceItem; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EventDefinitionBase? LogExecutedDeleteItem; } } diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs index 3758de9d63a..d4202d762da 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs @@ -344,6 +344,128 @@ public static class CosmosResources private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Cosmos.Properties.CosmosStrings", typeof(CosmosResources).Assembly); + /// + /// Executed CreateItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + /// + public static EventDefinition LogExecutedCreateItem(IDiagnosticsLogger logger) + { + var definition = ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedCreateItem; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedCreateItem, + logger, + static logger => new EventDefinition( + logger.Options, + CosmosEventId.ExecutedCreateItem, + LogLevel.Information, + "CosmosEventId.ExecutedCreateItem", + level => LoggerMessage.Define( + level, + CosmosEventId.ExecutedCreateItem, + _resourceManager.GetString("LogExecutedCreateItem")!))); + } + + return (EventDefinition)definition; + } + + /// + /// Executed DeleteItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + /// + public static EventDefinition LogExecutedDeleteItem(IDiagnosticsLogger logger) + { + var definition = ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedDeleteItem; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedDeleteItem, + logger, + static logger => new EventDefinition( + logger.Options, + CosmosEventId.ExecutedDeleteItem, + LogLevel.Information, + "CosmosEventId.ExecutedDeleteItem", + level => LoggerMessage.Define( + level, + CosmosEventId.ExecutedDeleteItem, + _resourceManager.GetString("LogExecutedDeleteItem")!))); + } + + return (EventDefinition)definition; + } + + /// + /// Executed ReadItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + /// + public static EventDefinition LogExecutedReadItem(IDiagnosticsLogger logger) + { + var definition = ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedReadItem; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedReadItem, + logger, + static logger => new EventDefinition( + logger.Options, + CosmosEventId.ExecutedReadItem, + LogLevel.Information, + "CosmosEventId.ExecutedReadItem", + level => LoggerMessage.Define( + level, + CosmosEventId.ExecutedReadItem, + _resourceManager.GetString("LogExecutedReadItem")!))); + } + + return (EventDefinition)definition; + } + + /// + /// Executed ReadNext ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Partition='{partitionKey}', Parameters=[{parameters}]{newLine}{sql} + /// + public static FallbackEventDefinition LogExecutedReadNext(IDiagnosticsLogger logger) + { + var definition = ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedReadNext; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedReadNext, + logger, + static logger => new FallbackEventDefinition( + logger.Options, + CosmosEventId.ExecutedReadNext, + LogLevel.Information, + "CosmosEventId.ExecutedReadNext", + _resourceManager.GetString("LogExecutedReadNext")!)); + } + + return (FallbackEventDefinition)definition; + } + + /// + /// Executed ReplaceItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + /// + public static EventDefinition LogExecutedReplaceItem(IDiagnosticsLogger logger) + { + var definition = ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedReplaceItem; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((Diagnostics.Internal.CosmosLoggingDefinitions)logger.Definitions).LogExecutedReplaceItem, + logger, + static logger => new EventDefinition( + logger.Options, + CosmosEventId.ExecutedReplaceItem, + LogLevel.Information, + "CosmosEventId.ExecutedReplaceItem", + level => LoggerMessage.Define( + level, + CosmosEventId.ExecutedReplaceItem, + _resourceManager.GetString("LogExecutedReplaceItem")!))); + } + + return (EventDefinition)definition; + } + /// /// Reading resource '{resourceId}' item from container '{containerId}' in partition '{partitionKey}'. /// diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx index c5f72982bde..2f21c2993e5 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx @@ -150,6 +150,26 @@ Both properties '{property1}' and '{property2}' on entity type '{entityType}' are mapped to '{storeName}'. Map one of the properties to a different JSON property. + + Executed CreateItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + Information CosmosEventId.ExecutedCreateItem string string string string string string? + + + Executed DeleteItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + Information CosmosEventId.ExecutedDeleteItem string string string string string string? + + + Executed ReadItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + Information CosmosEventId.ExecutedReadItem string string string string string string? + + + Executed ReadNext ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Partition='{partitionKey}', Parameters=[{parameters}]{newLine}{sql} + Information CosmosEventId.ExecutedReadNext string string string string string? string string string string + + + Executed ReplaceItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}' + Information CosmosEventId.ExecutedReplaceItem string string string string string string? + Reading resource '{resourceId}' item from container '{containerId}' in partition '{partitionKey}'. Information CosmosEventId.ExecutingReadItem string string string? diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs index 14d0270d2a2..1c3160238b5 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs @@ -1,17 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; using System.Net; using System.Runtime.CompilerServices; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; @@ -296,8 +290,21 @@ private async Task CreateItemOnceAsync( var itemRequestOptions = CreateItemRequestOptions(entry); var partitionKey = CreatePartitionKey(entry); - using var response = await container.CreateItemStreamAsync(stream, partitionKey, itemRequestOptions, cancellationToken) + var response = await container.CreateItemStreamAsync( + stream, + partitionKey == null ? PartitionKey.None : new PartitionKey(partitionKey), + itemRequestOptions, + cancellationToken) .ConfigureAwait(false); + + _commandLogger.ExecutedCreateItem( + response.Diagnostics.GetClientElapsedTime(), + response.Headers.RequestCharge, + response.Headers.ActivityId, + parameters.Document["id"].ToString(), + parameters.ContainerId, + partitionKey); + ProcessResponse(response, entry); return response.StatusCode == HttpStatusCode.Created; @@ -344,7 +351,7 @@ public virtual Task ReplaceItemAsync( private async Task ReplaceItemOnceAsync( DbContext _, - (string ContainerId, string ItemId, JObject Document, IUpdateEntry Entry) parameters, + (string ContainerId, string ResourceId, JObject Document, IUpdateEntry Entry) parameters, CancellationToken cancellationToken = default) { using var stream = new MemoryStream(); @@ -359,8 +366,21 @@ private async Task ReplaceItemOnceAsync( var partitionKey = CreatePartitionKey(entry); using var response = await container.ReplaceItemStreamAsync( - stream, parameters.ItemId, partitionKey, itemRequestOptions, cancellationToken) + stream, + parameters.ResourceId, + partitionKey == null ? PartitionKey.None : new PartitionKey(partitionKey), + itemRequestOptions, + cancellationToken) .ConfigureAwait(false); + + _commandLogger.ExecutedReplaceItem( + response.Diagnostics.GetClientElapsedTime(), + response.Headers.RequestCharge, + response.Headers.ActivityId, + parameters.ResourceId, + parameters.ContainerId, + partitionKey); + ProcessResponse(response, entry); return response.StatusCode == HttpStatusCode.OK; @@ -412,7 +432,7 @@ public virtual Task DeleteItemAsync( /// public virtual async Task DeleteItemOnceAsync( DbContext? _, - (string ContainerId, string DocumentId, IUpdateEntry Entry) parameters, + (string ContainerId, string ResourceId, IUpdateEntry Entry) parameters, CancellationToken cancellationToken = default) { var entry = parameters.Entry; @@ -422,8 +442,20 @@ public virtual async Task DeleteItemOnceAsync( var partitionKey = CreatePartitionKey(entry); using var response = await items.DeleteItemStreamAsync( - parameters.DocumentId, partitionKey, itemRequestOptions, cancellationToken: cancellationToken) + parameters.ResourceId, + partitionKey == null ? PartitionKey.None : new PartitionKey(partitionKey), + itemRequestOptions, + cancellationToken: cancellationToken) .ConfigureAwait(false); + + _commandLogger.ExecutedDeleteItem( + response.Diagnostics.GetClientElapsedTime(), + response.Headers.RequestCharge, + response.Headers.ActivityId, + parameters.ResourceId, + parameters.ContainerId, + partitionKey); + ProcessResponse(response, entry); return response.StatusCode == HttpStatusCode.NoContent; @@ -450,7 +482,7 @@ public virtual async Task DeleteItemOnceAsync( return new ItemRequestOptions { IfMatchEtag = (string?)etag, EnableContentResponseOnWrite = enabledContentResponse }; } - private static PartitionKey CreatePartitionKey(IUpdateEntry entry) + private static string? CreatePartitionKey(IUpdateEntry entry) { object? partitionKey = null; var partitionKeyPropertyName = entry.EntityType.GetPartitionKeyPropertyName(); @@ -466,7 +498,7 @@ private static PartitionKey CreatePartitionKey(IUpdateEntry entry) } } - return partitionKey == null ? PartitionKey.None : new PartitionKey((string)partitionKey); + return (string?)partitionKey; } private static void ProcessResponse(ResponseMessage response, IUpdateEntry entry) @@ -538,10 +570,18 @@ public virtual JObject ExecuteReadItem( { _commandLogger.ExecutingReadItem(containerId, partitionKey, resourceId); - var responseMessage = CreateSingleItemQuery( + var response = CreateSingleItemQuery( containerId, partitionKey, resourceId).GetAwaiter().GetResult(); - return JObjectFromReadItemResponseMessage(responseMessage); + _commandLogger.ExecutedReadItem( + response.Diagnostics.GetClientElapsedTime(), + response.Headers.RequestCharge, + response.Headers.ActivityId, + resourceId, + containerId, + partitionKey); + + return JObjectFromReadItemResponseMessage(response); } /// @@ -558,11 +598,19 @@ public virtual async Task ExecuteReadItemAsync( { _commandLogger.ExecutingReadItem(containerId, partitionKey, resourceId); - var responseMessage = await CreateSingleItemQuery( + var response = await CreateSingleItemQuery( containerId, partitionKey, resourceId, cancellationToken) .ConfigureAwait(false); - return JObjectFromReadItemResponseMessage(responseMessage); + _commandLogger.ExecutedReadItem( + response.Diagnostics.GetClientElapsedTime(), + response.Headers.RequestCharge, + response.Headers.ActivityId, + resourceId, + containerId, + partitionKey); + + return JObjectFromReadItemResponseMessage(response); } private static JObject JObjectFromReadItemResponseMessage(ResponseMessage responseMessage) @@ -662,13 +710,13 @@ private static bool TryReadJObject(JsonTextReader jsonReader, [NotNullWhen(true) private sealed class DocumentEnumerable : IEnumerable { - private readonly ICosmosClientWrapper _cosmosClient; + private readonly CosmosClientWrapper _cosmosClient; private readonly string _containerId; private readonly string? _partitionKey; private readonly CosmosSqlQuery _cosmosSqlQuery; public DocumentEnumerable( - ICosmosClientWrapper cosmosClient, + CosmosClientWrapper cosmosClient, string containerId, string? partitionKey, CosmosSqlQuery cosmosSqlQuery) @@ -687,7 +735,7 @@ IEnumerator IEnumerable.GetEnumerator() private sealed class Enumerator : IEnumerator { - private readonly ICosmosClientWrapper _cosmosClientWrapper; + private readonly CosmosClientWrapper _cosmosClientWrapper; private readonly string _containerId; private readonly string? _partitionKey; private readonly CosmosSqlQuery _cosmosSqlQuery; @@ -727,6 +775,15 @@ public bool MoveNext() } _responseMessage = _query.ReadNextAsync().GetAwaiter().GetResult(); + + _cosmosClientWrapper._commandLogger.ExecutedReadNext( + _responseMessage.Diagnostics.GetClientElapsedTime(), + _responseMessage.Headers.RequestCharge, + _responseMessage.Headers.ActivityId, + _containerId, + _partitionKey, + _cosmosSqlQuery); + _responseMessage.EnsureSuccessStatusCode(); _responseStream = _responseMessage.Content; @@ -770,13 +827,13 @@ public void Reset() private sealed class DocumentAsyncEnumerable : IAsyncEnumerable { - private readonly ICosmosClientWrapper _cosmosClient; + private readonly CosmosClientWrapper _cosmosClient; private readonly string _containerId; private readonly string? _partitionKey; private readonly CosmosSqlQuery _cosmosSqlQuery; public DocumentAsyncEnumerable( - ICosmosClientWrapper cosmosClient, + CosmosClientWrapper cosmosClient, string containerId, string? partitionKey, CosmosSqlQuery cosmosSqlQuery) @@ -792,7 +849,7 @@ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellati private sealed class AsyncEnumerator : IAsyncEnumerator { - private readonly ICosmosClientWrapper _cosmosClientWrapper; + private readonly CosmosClientWrapper _cosmosClientWrapper; private readonly string _containerId; private readonly string? _partitionKey; private readonly CosmosSqlQuery _cosmosSqlQuery; @@ -833,6 +890,15 @@ public async ValueTask MoveNextAsync() } _responseMessage = await _query.ReadNextAsync(_cancellationToken).ConfigureAwait(false); + + _cosmosClientWrapper._commandLogger.ExecutedReadNext( + _responseMessage.Diagnostics.GetClientElapsedTime(), + _responseMessage.Headers.RequestCharge, + _responseMessage.Headers.ActivityId, + _containerId, + _partitionKey, + _cosmosSqlQuery); + _responseMessage.EnsureSuccessStatusCode(); _responseStream = _responseMessage.Content; diff --git a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs index 9fe5c7bc24b..0b226fd6179 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Internal; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -43,23 +44,43 @@ public void Can_add_update_delete_end_to_end() context.Add(customer); context.SaveChanges(); + + var logEntry = TestSqlLoggerFactory.Log.Single(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("CreateItem", logEntry.Message); } using (var context = new CustomerContext(options)) { + TestSqlLoggerFactory.Clear(); var customerFromStore = context.Set().Single(); + var logEntry = TestSqlLoggerFactory.Log.Last(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("ReadNext", logEntry.Message); + TestSqlLoggerFactory.Clear(); + Assert.Equal(42, customerFromStore.Id); Assert.Equal("Theon", customerFromStore.Name); customerFromStore.Name = "Theon Greyjoy"; context.SaveChanges(); + + logEntry = TestSqlLoggerFactory.Log.Single(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("ReplaceItem", logEntry.Message); } using (var context = new CustomerContext(options)) { - var customerFromStore = context.Set().Single(); + TestSqlLoggerFactory.Clear(); + var customerFromStore = context.Find(42); + + var logEntry = TestSqlLoggerFactory.Log.Last(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("ReadItem", logEntry.Message); + TestSqlLoggerFactory.Clear(); Assert.Equal(42, customerFromStore.Id); Assert.Equal("Theon Greyjoy", customerFromStore.Name); @@ -67,6 +88,10 @@ public void Can_add_update_delete_end_to_end() context.Remove(customerFromStore); context.SaveChanges(); + + logEntry = TestSqlLoggerFactory.Log.Single(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("DeleteItem", logEntry.Message); } using (var context = new CustomerContext(options)) @@ -89,23 +114,41 @@ public async Task Can_add_update_delete_end_to_end_async() context.Add(customer); await context.SaveChangesAsync(); + + var logEntry = TestSqlLoggerFactory.Log.Single(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("CreateItem", logEntry.Message); } using (var context = new CustomerContext(options)) { var customerFromStore = await context.Set().SingleAsync(); + var logEntry = TestSqlLoggerFactory.Log.Last(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("ReadNext", logEntry.Message); + TestSqlLoggerFactory.Clear(); + Assert.Equal(42, customerFromStore.Id); Assert.Equal("Theon", customerFromStore.Name); customerFromStore.Name = "Theon Greyjoy"; await context.SaveChangesAsync(); + + logEntry = TestSqlLoggerFactory.Log.Single(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("ReplaceItem", logEntry.Message); } using (var context = new CustomerContext(options)) { - var customerFromStore = await context.Set().SingleAsync(); + var customerFromStore = await context.FindAsync(42); + + var logEntry = TestSqlLoggerFactory.Log.Last(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("ReadItem", logEntry.Message); + TestSqlLoggerFactory.Clear(); Assert.Equal(42, customerFromStore.Id); Assert.Equal("Theon Greyjoy", customerFromStore.Name); @@ -113,6 +156,10 @@ public async Task Can_add_update_delete_end_to_end_async() context.Remove(customerFromStore); await context.SaveChangesAsync(); + + logEntry = TestSqlLoggerFactory.Log.Single(); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.Contains("DeleteItem", logEntry.Message); } using (var context = new CustomerContext(options)) @@ -1628,6 +1675,17 @@ private void AssertSql(DbContext context, params string[] expected) logger.AssertBaseline(expected); } + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)Fixture.ListLoggerFactory; + + protected void AssertSql(params string[] expected) + => TestSqlLoggerFactory.AssertBaseline(expected); + + protected void AssertContainsSql(params string[] expected) + => TestSqlLoggerFactory.AssertBaseline(expected, assertOrder: false); + + protected ListLoggerFactory LoggerFactory { get; } + public class CosmosFixture : ServiceProviderFixtureBase, IAsyncLifetime { public CosmosFixture() @@ -1647,6 +1705,9 @@ public DbContextOptions CreateOptions() return CreateOptions(TestStore); } + protected override bool ShouldLogCategory(string logCategory) + => logCategory == DbLoggerCategory.Database.Command.Name; + public Task InitializeAsync() => Task.CompletedTask; diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs index d80be4c2089..f8b8ae1a5e2 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs @@ -1,10 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using Xunit; @@ -26,7 +22,7 @@ public TestSqlLoggerFactory() public TestSqlLoggerFactory(Func shouldLogCategory) : base(c => shouldLogCategory(c) || c == DbLoggerCategory.Database.Command.Name) { - Logger = new TestSqlLogger(shouldLogCategory(DbLoggerCategory.Database.Command.Name)); + Logger = new TestSqlLogger(); } public IReadOnlyList SqlStatements @@ -102,15 +98,12 @@ public void AssertBaseline(string[] expected, bool assertOrder = true) throw; } + + Clear(); } protected class TestSqlLogger : ListLogger { - private readonly bool _shouldLogCommands; - - public TestSqlLogger(bool shouldLogCommands) - => _shouldLogCommands = shouldLogCommands; - public List SqlStatements { get; } = new(); public List Parameters { get; } = new(); @@ -131,11 +124,6 @@ protected override void UnsafeLog( { if (eventId.Id == CosmosEventId.ExecutingSqlQuery) { - if (_shouldLogCommands) - { - base.UnsafeLog(logLevel, eventId, message, state, exception); - } - if (message != null) { var structure = (IReadOnlyList>)state; @@ -155,11 +143,6 @@ protected override void UnsafeLog( if (eventId.Id == CosmosEventId.ExecutingReadItem) { - if (_shouldLogCommands) - { - base.UnsafeLog(logLevel, eventId, message, state, exception); - } - if (message != null) { var structure = (IReadOnlyList>)state; @@ -170,10 +153,8 @@ protected override void UnsafeLog( SqlStatements.Add($"ReadItem({partitionKey}, {resourceId})"); } } - else - { - base.UnsafeLog(logLevel, eventId, message, state, exception); - } + + base.UnsafeLog(logLevel, eventId, message, state, exception); } } }