using Azure.Core; using Azure; using System.Diagnostics; using System.Extensions; using System.Linq.Expressions; using Azure.Data.Tables; using System.Core.Extensions; using Azure.Data.Tables.Models; namespace Net.Sdk.Web.Extensions.Azure; public class NamedTableClient : TableClient { private readonly ILogger> logger; public NamedTableClient(ILogger> logger) { this.logger = logger.ThrowIfNull(); } public NamedTableClient(ILogger> logger, Uri endpoint, TableClientOptions? options = null) : base(endpoint, options) { this.logger = logger.ThrowIfNull(); } public NamedTableClient(ILogger> logger, string connectionString, string tableName) : base(connectionString, tableName) { this.logger = logger.ThrowIfNull(); } public NamedTableClient(ILogger> logger, Uri endpoint, AzureSasCredential credential, TableClientOptions? options = null) : base(endpoint, credential, options) { this.logger = logger.ThrowIfNull(); } public NamedTableClient(ILogger> logger, Uri endpoint, string tableName, TableSharedKeyCredential credential) : base(endpoint, tableName, credential) { this.logger = logger.ThrowIfNull(); } public NamedTableClient(ILogger> logger, string connectionString, string tableName, TableClientOptions? options = null) : base(connectionString, tableName, options) { this.logger = logger.ThrowIfNull(); } public NamedTableClient(ILogger> logger, Uri endpoint, string tableName, TableSharedKeyCredential credential, TableClientOptions? options = null) : base(endpoint, tableName, credential, options) { this.logger = logger.ThrowIfNull(); } public NamedTableClient(ILogger> logger, Uri endpoint, string tableName, TokenCredential tokenCredential, TableClientOptions? options = null) : base(endpoint, tableName, tokenCredential, options) { this.logger = logger.ThrowIfNull(); } public override Response Create(CancellationToken cancellationToken = default) { return this.LogOperation(() => base.Create(cancellationToken), nameof(this.Create)); } public override Response CreateIfNotExists(CancellationToken cancellationToken = default) { return this.LogOperation(() => base.CreateIfNotExists(cancellationToken), nameof(this.CreateIfNotExists)); } public override Response AddEntity(T entity, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.AddEntity(entity, cancellationToken), nameof(this.AddEntity)); } public override Response Delete(CancellationToken cancellationToken = default) { return this.LogOperation(() => base.Delete(cancellationToken), nameof(this.Delete)); } public override Response DeleteEntity(string partitionKey, string rowKey, ETag ifMatch = default, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.DeleteEntity(partitionKey, rowKey, ifMatch, cancellationToken), nameof(this.DeleteEntity)); } public override Response UpdateEntity(T entity, ETag ifMatch, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.UpdateEntity(entity, ifMatch, mode, cancellationToken), nameof(this.UpdateEntity)); } public override Response UpsertEntity(T entity, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.UpsertEntity(entity, mode, cancellationToken), nameof(this.UpsertEntity)); } public override Response GetEntity(string partitionKey, string rowKey, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.GetEntity(partitionKey, rowKey, select, cancellationToken), nameof(this.GetEntity)); } public override NullableResponse GetEntityIfExists(string partitionKey, string rowKey, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.GetEntityIfExists(partitionKey, rowKey, select, cancellationToken), nameof(GetEntityIfExists)); } public override Pageable Query(string? filter = null, int? maxPerPage = null, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.Query(filter, maxPerPage, select, cancellationToken), nameof(this.Query)); } public override Pageable Query(Expression> filter, int? maxPerPage = null, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.Query(filter, maxPerPage, select, cancellationToken), nameof(this.Query)); } public override Response> SubmitTransaction(IEnumerable transactionActions, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.SubmitTransaction(transactionActions, cancellationToken), nameof(this.SubmitTransaction)); } public override Task> CreateAsync(CancellationToken cancellationToken = default) { return this.LogOperation(() => base.CreateAsync(cancellationToken), nameof(this.CreateAsync)); } public override Task> CreateIfNotExistsAsync(CancellationToken cancellationToken = default) { return this.LogOperation(() => base.CreateIfNotExistsAsync(cancellationToken), nameof(this.CreateIfNotExistsAsync)); } public override Task AddEntityAsync(T entity, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.AddEntityAsync(entity, cancellationToken), nameof(this.AddEntityAsync)); } public override Task DeleteAsync(CancellationToken cancellationToken = default) { return this.LogOperation(() => base.DeleteAsync(cancellationToken), nameof(this.DeleteAsync)); } public override Task DeleteEntityAsync(string partitionKey, string rowKey, ETag ifMatch = default, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.DeleteEntityAsync(partitionKey, rowKey, ifMatch, cancellationToken), nameof(this.DeleteEntityAsync)); } public override Task UpdateEntityAsync(T entity, ETag ifMatch, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.UpdateEntityAsync(entity, ifMatch, mode, cancellationToken), nameof(this.UpdateEntityAsync)); } public override Task UpsertEntityAsync(T entity, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.UpsertEntityAsync(entity, mode, cancellationToken), nameof(this.UpsertEntityAsync)); } public override Task> GetEntityAsync(string partitionKey, string rowKey, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.GetEntityAsync(partitionKey, rowKey, select, cancellationToken), nameof(this.GetEntityAsync)); } public override Task> GetEntityIfExistsAsync(string partitionKey, string rowKey, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.GetEntityIfExistsAsync(partitionKey, rowKey, select, cancellationToken), nameof(this.GetEntityIfExistsAsync)); } public override AsyncPageable QueryAsync(Expression> filter, int? maxPerPage = null, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.QueryAsync(filter, maxPerPage, select, cancellationToken), nameof(this.QueryAsync)); } public override AsyncPageable QueryAsync(string? filter = null, int? maxPerPage = null, IEnumerable? select = null, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.QueryAsync(filter, maxPerPage, select, cancellationToken), nameof(this.QueryAsync)); } public override Task>> SubmitTransactionAsync(IEnumerable transactionActions, CancellationToken cancellationToken = default) { return this.LogOperation(() => base.SubmitTransactionAsync(transactionActions, cancellationToken), nameof(this.SubmitTransactionAsync)); } private Response LogOperation(Func func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = func(); scopedLogger.LogInformation("<< {0} {1}ms", response.Status, sw.ElapsedMilliseconds); return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } private Response LogOperation(Func> func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = func(); scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds); return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } private NullableResponse LogOperation(Func> func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = func(); scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds); return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } private Pageable LogOperation(Func> func, string operationName) where T : notnull { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); var response = func(); return new InterceptingPageable(response, page => { scopedLogger.LogInformation("<< {0} {1}ms", page.GetRawResponse().Status, sw.ElapsedMilliseconds); }, () => { scopedLogger.LogInformation("<< 200 {1}ms", sw.ElapsedMilliseconds); }); } private Response> LogOperation(Func>> func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = func(); foreach (var action in response.Value) { scopedLogger.LogInformation("<< {0} {1}ms", action.Status, sw.ElapsedMilliseconds); } return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } private async Task LogOperation(Func> func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = await func(); scopedLogger.LogInformation("<< {0} {1}ms", response.Status, sw.ElapsedMilliseconds); return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } private async Task> LogOperation(Func>> func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = await func(); scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds); return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } private async Task> LogOperation(Func>> func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = await func(); scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds); return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } private AsyncPageable LogOperation(Func> func, string operationName) where T : notnull { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); var response = func(); return new InterceptingAsyncPageable(response, page => { scopedLogger.LogInformation("<< {0} {1}ms", page.GetRawResponse().Status, sw.ElapsedMilliseconds); }, () => { scopedLogger.LogInformation("<< 200 {1}ms", sw.ElapsedMilliseconds); }); } private async Task>> LogOperation(Func>>> func, string operationName) { var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty); var sw = Stopwatch.StartNew(); scopedLogger.LogInformation(">> {0}", this.Uri); try { var response = await func(); foreach (var action in response.Value) { scopedLogger.LogInformation("<< {0} {1}ms", action.Status, sw.ElapsedMilliseconds); } return response; } catch (RequestFailedException ex) { scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds); throw; } } }