diff --git a/DEPRECATED.md b/DEPRECATED.md new file mode 100644 index 0000000..660b827 --- /dev/null +++ b/DEPRECATED.md @@ -0,0 +1,32 @@ +# Deprecated package: NLog.Extensions.AzureCosmosTable + +`NLog.Extensions.AzureCosmosTable` is deprecated, unmaintained, and contains known vulnerabilities in its dependency chain. It is no longer supported and will not receive fixes or updates. + +## Guidance + +- Do not use this package in new or existing projects. +- Migrate to `NLog.Extensions.AzureDataTables`, which targets the supported Azure Data Tables APIs. +- Unlist or remove any internal feeds that still carry this package to prevent accidental consumption. + +## Status + +- Maintenance: stopped +- Security: known vulnerabilities, will not be fixed +- NuGet: marked deprecated; recommend unlisting any remaining versions +- Last code commit containing this package: f1c345b490a7353c5fd00d1dde42364d162173ce (2022-01-29 — see tag `archive/azure-cosmos-table-2022-01-29`) + +## Deprecated package: NLog.Extensions.AzureStorage (bundle) + +The legacy bundled package `NLog.Extensions.AzureStorage` was superseded when targets were split. It should not be used. + +### Guidance (bundle) + +- Do not use the bundled package; consume the individual packages (Blob, Queue, EventHub, EventGrid, DataTables, ServiceBus, AccessToken) instead. +- Unlist or remove any internal feeds that still carry the bundle to prevent accidental consumption. + +### Status (bundle) + +- Maintenance: stopped +- Security: inherits vulnerabilities from deprecated dependencies in the bundle; will not be fixed +- NuGet: should be marked deprecated/unlisted +- Last code commit containing this bundle: c8bfb7966d550221e1aeca859705f606c8559dd2 (tag `archive/azure-storage-bundle`) diff --git a/DEPRECATION_PROCESS.md b/DEPRECATION_PROCESS.md new file mode 100644 index 0000000..c2eea3c --- /dev/null +++ b/DEPRECATION_PROCESS.md @@ -0,0 +1,81 @@ +# Deprecation and Removal Playbook + +Practical steps to deprecate and remove a package (while keeping history) without letting vulnerable code stay on the default branch or get republished. + +## Steps + +1. **Declare deprecation** + - Open an issue/PR stating the reason (vulnerability/abandonment) and affected package name. + - Add a loud banner to the package README and a short note in the root README pointing to the safer alternative. + - Add/update `DEPRECATED.md` with the status and migration guidance. + +2. **Stop distribution** + - Remove the project from the solution and CI pack/test pipelines so it cannot be built or packed. + - Unlist all NuGet versions, or publish a final version with release notes that say "deprecated, insecure, unsupported" and link to the alternative. + - Verify no other packages in the repo reference it (remove references or add a compile-time `#error` guard if needed). + +3. **Clean the default branch for scanners** + - Delete the package source folder from `master` (or default branch) and replace it with a small placeholder README that states it was removed, why, and where to find an alternative. + - Keep a brief note in the root README so users understand it was intentionally removed. + +4. **Preserve history without branch sprawl** + - Tag the last commit that still contained the code (e.g., `archive/-YYYY-MM-DD`). + +5. **Security and comms** + - If the risk is security-related, add a short SECURITY/Advisory note: status = won't fix, remediation = use alternative, scope of impact. + - Optionally pin the advisory in the repo and link it from the package README placeholder. + +6. **Validate** + - Run `dotnet build` and targeted tests to confirm removal did not break supported packages. + - Confirm CI pack/test steps skip the removed package. + +## Artifacts to touch (typical) + +- Package README: banner + deprecation note or placeholder. +- Root `README.md`: short note and link to alternative. +- `DEPRECATED.md`: status and guidance. +- Solution file and CI config: remove project, pack, and test entries. +- Optional: SECURITY/advisory file with "won't fix" language. + +## Templates + +**Placeholder README snippet (in the removed package folder):** + +```markdown +# (removed) + +This package was removed from the default branch because it is deprecated and contains known vulnerabilities. It is unmaintained and should not be used. See instead. + +Last code version is preserved at tag: archive/-YYYY-MM-DD. +``` + +**Release notes snippet for the final/last package version:** + +```text +Deprecated and insecure. This package is unmaintained and contains known vulnerabilities. Do not use. Migrate to . +``` + +## Quick command hints + +- Tag the last commit before removal: + +```sh +git tag archive/-YYYY-MM-DD +``` + + +- Remove a project from the solution (example): + +```sh +dotnet sln src/NLog.Extensions.AzureStorage.sln remove src//.csproj +``` + +## Checklist for each deprecation + +- [ ] Banner in package README + root README note +- [ ] Solution/CI pack/test entries removed +- [ ] NuGet versions unlisted or final deprecated version published with clear notes +- [ ] Placeholder README in default branch, code removed +- [ ] Tag created for last code commit (and archive branch if required) +- [ ] Advisory/SECURITY note added when security-driven +- [ ] Build/tests rerun to verify unaffected packages diff --git a/src/NLog.Extensions.AzureCosmosTable/ICloudTableService.cs b/src/NLog.Extensions.AzureCosmosTable/ICloudTableService.cs deleted file mode 100644 index 93b33ee..0000000 --- a/src/NLog.Extensions.AzureCosmosTable/ICloudTableService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -#if NETSTANDARD2_0 || NET461 -using Microsoft.Azure.Cosmos.Table; -#else -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Table; -#endif - -namespace NLog.Extensions.AzureStorage -{ - interface ICloudTableService - { - void Connect(string connectionString, int? defaultTimeToLiveSeconds); - Task ExecuteBatchAsync(string tableName, TableBatchOperation tableOperation, CancellationToken cancellationToken); - } -} diff --git a/src/NLog.Extensions.AzureCosmosTable/NLog.Extensions.AzureCosmosTable.csproj b/src/NLog.Extensions.AzureCosmosTable/NLog.Extensions.AzureCosmosTable.csproj deleted file mode 100644 index 5b9a179..0000000 --- a/src/NLog.Extensions.AzureCosmosTable/NLog.Extensions.AzureCosmosTable.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - net45;net461;netstandard1.3;netstandard2.0 - true - true - true - master - true - snupkg - - 2.8.0 - - NLog TableStorageTarget for writing to Azure Table Storage OR Azure CosmosDB Tables - jdetmar - $([System.DateTime]::Now.ToString(yyyy)) - Copyright (c) $(CurrentYear) - jdetmar - - NLog;azure;CloudTable;cosmos;cosmosdb;documentdb;table;storage;log;logging - logo64.png - README.md - https://github.com/JDetmar/NLog.Extensions.AzureStorage - git - https://github.com/JDetmar/NLog.Extensions.AzureStorage.git - MIT - -Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureCosmosTable/README.md - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NLog.Extensions.AzureCosmosTable/NLogEntity.cs b/src/NLog.Extensions.AzureCosmosTable/NLogEntity.cs deleted file mode 100644 index f516453..0000000 --- a/src/NLog.Extensions.AzureCosmosTable/NLogEntity.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -#if NETSTANDARD2_0 || NET461 -using Microsoft.Azure.Cosmos.Table; -#else -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Table; -#endif -using NLog.Layouts; - -namespace NLog.Extensions.AzureStorage -{ - public class NLogEntity: TableEntity - { - public string LogTimeStamp { get; set; } - public string Level { get; set; } - public string LoggerName { get; set; } - public string Message { get; set; } - public string Exception { get; set; } - public string InnerException { get; set; } - public string StackTrace { get; set; } - public string FullMessage { get; set; } - public string MachineName { get; set; } - - public NLogEntity(LogEventInfo logEvent, string layoutMessage, string machineName, string partitionKey, string rowKey, string logTimeStampFormat) - { - FullMessage = layoutMessage; - Level = logEvent.Level.Name; - LoggerName = logEvent.LoggerName; - Message = logEvent.Message; - LogTimeStamp = logEvent.TimeStamp.ToString(logTimeStampFormat); - MachineName = machineName; - if(logEvent.Exception != null) - { - var exception = logEvent.Exception; - var innerException = exception.InnerException; - if (exception is AggregateException aggregateException) - { - var innerExceptions = aggregateException.Flatten(); - if (innerExceptions.InnerExceptions?.Count == 1) - { - exception = innerExceptions.InnerExceptions[0]; - innerException = null; - } - else - { - innerException = innerExceptions; - } - } - - Exception = string.Concat(exception.Message, " - ", exception.GetType().ToString()); - StackTrace = exception.StackTrace; - if (innerException != null) - { - InnerException = innerException.ToString(); - } - } - RowKey = rowKey; - PartitionKey = partitionKey; - } - public NLogEntity() { } - } -} diff --git a/src/NLog.Extensions.AzureCosmosTable/Properties/AssemblyInfo.cs b/src/NLog.Extensions.AzureCosmosTable/Properties/AssemblyInfo.cs deleted file mode 100644 index ee87564..0000000 --- a/src/NLog.Extensions.AzureCosmosTable/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: ComVisible(false)] - -[assembly: InternalsVisibleTo("NLog.Extensions.AzureCosmosTable.Tests")] diff --git a/src/NLog.Extensions.AzureCosmosTable/README.md b/src/NLog.Extensions.AzureCosmosTable/README.md index 531e321..c25b2d6 100644 --- a/src/NLog.Extensions.AzureCosmosTable/README.md +++ b/src/NLog.Extensions.AzureCosmosTable/README.md @@ -1,138 +1,7 @@ -# Azure Table Storage and Cosmos Table +# NLog.Extensions.AzureCosmosTable (removed) -| Package Name | NuGet | Description | -| ------------------------------------- | :-------------------: | ----------- | -| **NLog.Extensions.AzureCosmosTable** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureCosmosTable.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureCosmosTable/) | Azure Table Storage or Azure CosmosDb Tables | +This package was removed from the default branch because it is deprecated, unmaintained, and contains known vulnerabilities. Do not use it. -## Table Configuration -Supports both Azure Storage Tables and CosmosDB Tables. +Use `NLog.Extensions.AzureDataTables` instead. -### Syntax -```xml - - - - - - - -``` -### Parameters - -_name_ - Name of the target. - -_layout_ - Text to be rendered. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. - -_connectionString_ - Azure storage connection string. Must provide either _connectionString_ or _connectionStringKey_. - -_connectionStringKey_ - App key name of Azure storage connection string. Must provide either _connectionString_ or _connectionStringKey_. - -_tableName_ - Azure table name. [Layout](https://github.com/NLog/NLog/wiki/Layouts) - -_rowKey_ - Azure Table RowKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = "InverseTicks_${guid}" - -_partitionKey_ - Azure PartitionKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = `${logger}` - -_logTimeStampFormat_ - Default Log TimeStamp is set to 'O' for [Round-trip](https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#the-round-trip-o-o-format-specifier) format if not specified. - -_timeToLiveSeconds_ - Default [Time-to-live](https://docs.microsoft.com/en-us/azure/cosmos-db/time-to-live) (TTL) for CosmosDb rows in seconds. Default = 0 (Off - Forever) - -_timeToLiveDays_ - Default [Time-to-live](https://docs.microsoft.com/en-us/azure/cosmos-db/time-to-live) (TTL) for CosmosDb rows in days. Default = 0 (Off - Forever) - -### DynamicTableEntity -Instead of using the predefined NLogEntity-properties, then one can specify wanted properties: - -```xml - - - - - - - - - - - - - - -``` - -It will by default include the hardcoded property `LogTimeStamp` of type DateTime that contains `LogEventInfo.TimeStamp.ToUniversalTime()`. - - This can be overriden by having `` as the first property, where empty property-value means leave out. - -### Batching Policy - -_batchSize_ - Number of EventData items to send in a single batch (Default=100) - -_taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) - -_queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) - -_overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) - -### Retry Policy - -_taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) - -_retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). - -_retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) - -## Azure ConnectionString - -NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). - -#### Lookup ConnectionString from appsettings.json - - > `connectionString="${configsetting:ConnectionStrings.AzureTable}"` - -* Example appsettings.json on .NetCore: - -```json - { - "ConnectionStrings": { - "AzureTable": "Server=tcp:server.database.windows.net;" - } - } -``` - -#### Lookup ConnectionString from app.config - - > `connectionString="${appsetting:ConnectionStrings.AzureTable}"` - -* Example app.config on .NetFramework: - -```xml - - - - - -``` - -#### Lookup ConnectionString from environment-variable - - > `connectionString="${environment:AZURESQLCONNSTR_CONNECTION_STRING}"` - -#### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) - - > `connectionString="${gdc:AzureTableConnectionString}"` - -* Example code for setting GDC-value: - -```c# - NLog.GlobalDiagnosticsContext.Set("AzureTableConnectionString", "Server=tcp:server.database.windows.net;"); -``` \ No newline at end of file +Last code version is preserved at tag `archive/azure-cosmos-table-2022-01-29` (commit f1c345b490a7353c5fd00d1dde42364d162173ce). \ No newline at end of file diff --git a/src/NLog.Extensions.AzureCosmosTable/TableStorageTarget.cs b/src/NLog.Extensions.AzureCosmosTable/TableStorageTarget.cs deleted file mode 100644 index a76f215..0000000 --- a/src/NLog.Extensions.AzureCosmosTable/TableStorageTarget.cs +++ /dev/null @@ -1,415 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -#if NETSTANDARD2_0 || NET461 -using Microsoft.Azure.Cosmos.Table; -#else -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Table; -#endif -using NLog.Common; -using NLog.Config; -using NLog.Extensions.AzureStorage; -using NLog.Layouts; - -namespace NLog.Targets -{ - /// - /// Azure Table Storage NLog Target - /// - [Target("AzureCosmosTable")] - public sealed class TableStorageTarget : AsyncTaskTarget - { - private readonly ICloudTableService _cloudTableService; - private string _machineName; - private readonly AzureStorageNameCache _containerNameCache = new AzureStorageNameCache(); - private readonly Func _checkAndRepairTableNameDelegate; - - //Delegates for bucket sorting - private SortHelpers.KeySelector _getTablePartitionNameDelegate; - - struct TablePartitionKey : IEquatable - { - public readonly string TableName; - public readonly string PartitionId; - - public TablePartitionKey(string tableName, string partitionId) - { - TableName = tableName ?? string.Empty; - PartitionId = partitionId ?? string.Empty; - } - - public bool Equals(TablePartitionKey other) - { - return TableName == other.TableName && - PartitionId == other.PartitionId; - } - - public override bool Equals(object obj) - { - return (obj is TablePartitionKey) && Equals((TablePartitionKey)obj); - } - - public override int GetHashCode() - { - return TableName.GetHashCode() ^ PartitionId.GetHashCode(); - } - } - - public Layout ConnectionString { get; set; } - public string ConnectionStringKey { get; set; } - - [RequiredParameter] - public Layout TableName { get; set; } - - [RequiredParameter] - public Layout PartitionKey { get; set; } = "${logger}"; - - [RequiredParameter] - public Layout RowKey { get; set; } - - public string LogTimeStampFormat { get; set; } = "O"; - - public Layout TimeToLiveSeconds { get; set; } - - public Layout TimeToLiveDays { get; set; } - - public TableStorageTarget() - :this(new CloudTableService()) - { - } - - internal TableStorageTarget(ICloudTableService cloudTableService) - { - TaskDelayMilliseconds = 200; - BatchSize = 100; - - RowKey = Layout.FromMethod(l => string.Concat((DateTime.MaxValue.Ticks - l.TimeStamp.Ticks).ToString("d19"), "__", Guid.NewGuid().ToString()), LayoutRenderOptions.ThreadAgnostic); - - _cloudTableService = cloudTableService; - _checkAndRepairTableNameDelegate = CheckAndRepairTableNamingRules; - } - - /// - /// Initializes the target. Can be used by inheriting classes - /// to initialize logging. - /// - protected override void InitializeTarget() - { - base.InitializeTarget(); - - _machineName = GetMachineName(); - - string connectionString = string.Empty; - try - { - connectionString = ConnectionStringHelper.LookupConnectionString(ConnectionString, ConnectionStringKey); - TimeSpan defaultTimeToLive = RenderDefaultTimeToLive(); - _cloudTableService.Connect(connectionString, (int)defaultTimeToLive.TotalSeconds); - InternalLogger.Trace("AzureTableStorageTarget(Name={0}): Initialized", Name); - } - catch (Exception ex) - { - InternalLogger.Error(ex, "AzureTableStorageTarget(Name={0}): Failed to create TableClient with connectionString={1}.", Name, connectionString); - throw; - } - } - - private TimeSpan RenderDefaultTimeToLive() - { - string timeToLiveSeconds = null; - string timeToLiveDays = null; - - try - { - timeToLiveSeconds = TimeToLiveSeconds?.Render(LogEventInfo.CreateNullEvent()); - if (!string.IsNullOrEmpty(timeToLiveSeconds)) - { - if (int.TryParse(timeToLiveSeconds, out var resultSeconds)) - { - return TimeSpan.FromSeconds(resultSeconds); - } - else - { - InternalLogger.Error("AzureTableStorageTarget(Name={0}): Failed to parse TimeToLiveSeconds={1}", Name, timeToLiveSeconds); - } - } - else - { - timeToLiveDays = TimeToLiveDays?.Render(LogEventInfo.CreateNullEvent()); - if (!string.IsNullOrEmpty(timeToLiveDays)) - { - if (int.TryParse(timeToLiveDays, out var resultDays)) - { - return TimeSpan.FromDays(resultDays); - } - else - { - InternalLogger.Error("AzureTableStorageTarget(Name={0}): Failed to parse TimeToLiveDays={1}", Name, timeToLiveDays); - } - } - } - } - catch (Exception ex) - { - InternalLogger.Error(ex, "AzureTableStorageTarget(Name={0}): Failed to parse TimeToLive value. Seconds={1}, Days={2}", Name, timeToLiveSeconds, timeToLiveDays); - } - - return TimeSpan.Zero; - } - - protected override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - protected override Task WriteAsyncTask(IList logEvents, CancellationToken cancellationToken) - { - //must sort into containers and then into the blobs for the container - if (_getTablePartitionNameDelegate == null) - _getTablePartitionNameDelegate = logEvent => new TablePartitionKey(RenderLogEvent(TableName, logEvent), RenderLogEvent(PartitionKey, logEvent)); - - if (logEvents.Count == 1) - { - var batchItem = GenerateBatch(logEvents, RenderLogEvent(PartitionKey, logEvents[0])); - return WriteToTableAsync(RenderLogEvent(TableName, logEvents[0]), batchItem, cancellationToken); - } - - const int BatchMaxSize = 100; - - var partitionBuckets = SortHelpers.BucketSort(logEvents, _getTablePartitionNameDelegate); - IList multipleTasks = partitionBuckets.Count > 1 ? new List(partitionBuckets.Count) : null; - foreach (var partitionBucket in partitionBuckets) - { - string tableName = partitionBucket.Key.TableName; - - try - { - if (partitionBucket.Value.Count <= BatchMaxSize) - { - var batchItem = GenerateBatch(partitionBucket.Value, partitionBucket.Key.PartitionId); - var writeTask = WriteToTableAsync(partitionBucket.Key.TableName, batchItem, cancellationToken); - if (multipleTasks == null) - return writeTask; - - multipleTasks.Add(writeTask); - } - else - { - // Must chain the tasks together so they don't run concurrently - var batchCollection = GenerateBatches(partitionBucket.Value, partitionBucket.Key.PartitionId, BatchMaxSize); - Task writeTask = WriteMultipleBatchesAsync(batchCollection, tableName, cancellationToken); - if (multipleTasks == null) - return writeTask; - - multipleTasks.Add(writeTask); - } - } - catch (Exception ex) - { - InternalLogger.Error(ex, "AzureTableStorageTarget(Name={0}): Failed to write table={1}", Name, tableName); - if (multipleTasks == null) - throw; - } - } - - return Task.WhenAll(multipleTasks ?? new Task[0]); - } - - private async Task WriteMultipleBatchesAsync(IEnumerable batchCollection, string tableName, CancellationToken cancellationToken) - { - foreach (var batchItem in batchCollection) - { - await WriteToTableAsync(tableName, batchItem, cancellationToken); - } - } - - IEnumerable GenerateBatches(IList source, string partitionId, int batchSize) - { - for (int i = 0; i < source.Count; i += batchSize) - yield return GenerateBatch(source.Skip(i).Take(batchSize), partitionId); - } - - private TableBatchOperation GenerateBatch(IEnumerable logEvents, string partitionId) - { - var batch = new TableBatchOperation(); - foreach (var logEvent in logEvents) - { - var tableEntity = CreateTableEntity(logEvent, partitionId); - batch.Insert(tableEntity); - } - return batch; - } - - private Task WriteToTableAsync(string tableName, TableBatchOperation tableOperation, CancellationToken cancellationToken) - { - try - { - tableName = CheckAndRepairTableName(tableName); - return _cloudTableService.ExecuteBatchAsync(tableName, tableOperation, cancellationToken); - } - catch (Exception ex) - { - InternalLogger.Error(ex, "AzureTableStorageTarget(Name={0}): Failed to write table={1}", Name, tableName); - throw; - } - } - - private ITableEntity CreateTableEntity(LogEventInfo logEvent, string partitionKey) - { - var rowKey = RenderLogEvent(RowKey, logEvent); - - if (ContextProperties.Count > 0) - { - DynamicTableEntity entity = new DynamicTableEntity(); - entity.PartitionKey = partitionKey; - entity.RowKey = rowKey; - - bool logTimeStampOverridden = "LogTimeStamp".Equals(ContextProperties[0].Name, StringComparison.OrdinalIgnoreCase); - if (!logTimeStampOverridden) - { - entity.Properties.Add("LogTimeStamp", new EntityProperty(logEvent.TimeStamp.ToUniversalTime())); - } - - for (int i = 0; i < ContextProperties.Count; ++i) - { - var contextproperty = ContextProperties[i]; - if (string.IsNullOrEmpty(contextproperty.Name)) - continue; - - var propertyValue = contextproperty.Layout != null ? RenderLogEvent(contextproperty.Layout, logEvent) : string.Empty; - if (logTimeStampOverridden && i == 0 && string.IsNullOrEmpty(propertyValue)) - continue; - - entity.Properties.Add(contextproperty.Name, new EntityProperty(propertyValue)); - } - - return entity; - } - else - { - var layoutMessage = RenderLogEvent(Layout, logEvent); - var entity = new NLogEntity(logEvent, layoutMessage, _machineName, partitionKey, rowKey, LogTimeStampFormat); - return entity; - } - } - - private string CheckAndRepairTableName(string tableName) - { - return _containerNameCache.LookupStorageName(tableName, _checkAndRepairTableNameDelegate); - } - - private string CheckAndRepairTableNamingRules(string tableName) - { - InternalLogger.Trace("AzureTableStorageTarget(Name={0}): Requested Table Name: {1}", Name, tableName); - string validTableName = AzureStorageNameCache.CheckAndRepairTableNamingRules(tableName); - if (validTableName == tableName) - { - InternalLogger.Trace("AzureTableStorageTarget(Name={0}): Using Table Name: {0}", Name, validTableName); - } - else - { - InternalLogger.Trace("AzureTableStorageTarget(Name={0}): Using Cleaned Table name: {0}", Name, validTableName); - } - return validTableName; - } - - /// - /// Gets the machine name - /// - private static string GetMachineName() - { - return TryLookupValue(() => Environment.GetEnvironmentVariable("COMPUTERNAME"), "COMPUTERNAME") - ?? TryLookupValue(() => Environment.GetEnvironmentVariable("HOSTNAME"), "HOSTNAME") -#if !NETSTANDARD1_3 - ?? TryLookupValue(() => Environment.MachineName, "MachineName") -#endif - ?? TryLookupValue(() => System.Net.Dns.GetHostName(), "DnsHostName"); - } - - private static string TryLookupValue(Func lookupFunc, string lookupType) - { - try - { - string lookupValue = lookupFunc()?.Trim(); - return string.IsNullOrEmpty(lookupValue) ? null : lookupValue; - } - catch (Exception ex) - { - InternalLogger.Warn(ex, "AzureTableStorageTarget: Failed to lookup {0}", lookupType); - return null; - } - } - - class CloudTableService : ICloudTableService - { - private CloudTableClient _client; - private CloudTable _table; - private int? _defaultTimeToLiveSeconds; - - public void Connect(string connectionString, int? defaultTimeToLiveSeconds) - { - _client = CloudStorageAccount.Parse(connectionString).CreateCloudTableClient(); - _defaultTimeToLiveSeconds = defaultTimeToLiveSeconds == 0 ? null : defaultTimeToLiveSeconds; - } - - public Task ExecuteBatchAsync(string tableName, TableBatchOperation tableOperation, CancellationToken cancellationToken) - { - var table = _table; - if (tableName == null || table?.Name != tableName) - { - return InitializeAndCacheTableAsync(tableName, cancellationToken).ContinueWith(async (t, operation) => await t.Result.ExecuteBatchAsync((TableBatchOperation)operation).ConfigureAwait(false), tableOperation, cancellationToken); - } - else - { -#if NETSTANDARD1_3 - return table.ExecuteBatchAsync(tableOperation); -#else - return table.ExecuteBatchAsync(tableOperation, cancellationToken); -#endif - } - } - - async Task InitializeAndCacheTableAsync(string tableName, CancellationToken cancellationToken) - { - try - { - if (_client == null) - throw new InvalidOperationException("CloudTableClient has not been initialized"); - - var table = _client.GetTableReference(tableName); - -#if NETSTANDARD1_3 - var tableExists = await table.ExistsAsync().ConfigureAwait(false); -#else - var tableExists = await table.ExistsAsync(cancellationToken).ConfigureAwait(false); -#endif - if (!tableExists) - { -#if NETSTANDARD1_3 - await table.CreateIfNotExistsAsync().ConfigureAwait(false); -#elif NET45 - await table.CreateIfNotExistsAsync(cancellationToken).ConfigureAwait(false); -#else - if (_defaultTimeToLiveSeconds.HasValue) - await table.CreateIfNotExistsAsync(Microsoft.Azure.Cosmos.IndexingMode.Consistent, null, _defaultTimeToLiveSeconds, cancellationToken).ConfigureAwait(false); - else - await table.CreateIfNotExistsAsync(cancellationToken).ConfigureAwait(false); -#endif - } - - _table = table; - return table; - } - catch (Exception exception) - { - InternalLogger.Error(exception, "AzureTableStorageTarget: Failed to initialize table={1}", tableName); - throw; - } - } - } - } -} -