├── logo64.png ├── src ├── NLog.Extensions.AzureEventHub │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── IEventHubService.cs │ ├── NLog.Extensions.AzureEventHub.csproj │ └── README.md ├── NLog.Extensions.AzureAccessToken │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── NLog.Extensions.AzureAccessToken.csproj │ ├── README.md │ └── AccessTokenLayoutRenderer.cs ├── NLog.Extensions.AzureBlobStorage │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ICloudBlobService.cs │ ├── NLog.Extensions.AzureBlobStorage.csproj │ └── README.md ├── NLog.Extensions.AzureDataTables │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ICloudTableService.cs │ ├── NLog.Extensions.AzureDataTables.csproj │ ├── NLogEntity.cs │ └── README.md ├── NLog.Extensions.AzureEventGrid │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── IEventGridService.cs │ ├── NLog.Extensions.AzureEventGrid.csproj │ └── README.md ├── NLog.Extensions.AzureServiceBus │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ICloudServiceBus.cs │ ├── NLog.Extensions.AzureServiceBus.csproj │ └── README.md ├── NLog.Extensions.AzureQueueStorage │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ICloudQueueService.cs │ ├── NLog.Extensions.AzureQueueStorage.csproj │ └── README.md ├── NLog.Extensions.AzureCosmosTable │ └── README.md ├── NLog.Extensions.AzureStorage │ ├── AzureCredentialHelper.cs │ ├── SortHelpers.cs │ ├── ProxyHelpers.cs │ └── AzureStorageNameCache.cs └── NLog.Extensions.AzureStorage.sln ├── .devcontainer └── devcontainer.json ├── test ├── NLog.Extensions.AzureEventHub.Tests │ ├── NLog.Extensions.AzureEventHub.Tests.csproj │ ├── EventHubServiceMock.cs │ └── EventHubTargetTest.cs ├── NLog.Extensions.AzureDataTables.Tests │ ├── NLog.Extensions.AzureDataTables.Tests.csproj │ ├── CloudTableServiceMock.cs │ └── DataTablesTargetTests.cs ├── NLog.Extensions.AzureEventGrid.Tests │ ├── NLog.Extensions.AzureEventGrid.Tests.csproj │ ├── EventGridTargetTest.cs │ └── EventGridServiceMock.cs ├── NLog.Extensions.AzureServiceBus.Tests │ ├── NLog.Extensions.AzureServiceBus.Tests.csproj │ ├── ServiceBusMock.cs │ └── ServiceBusTargetTest.cs ├── NLog.Extensions.AzureAccessToken.Tests │ ├── NLog.Extensions.AzureAccessToken.Tests.csproj │ ├── AzureServiceTokenProviderMock.cs │ └── AccessTokenLayoutRendererTests.cs ├── NLog.Extensions.AzureBlobStorage.Tests │ ├── NLog.Extensions.AzureBlobStorage.Tests.csproj │ ├── CloudBlobServiceMock.cs │ └── BlobStorageTargetTest.cs ├── NLog.Extensions.AzureCosmosTable.Tests │ ├── NLog.Extensions.AzureCosmosTable.Tests.csproj │ ├── CloudTableServiceMock.cs │ └── TableStorageTargetTest.cs ├── NLog.Extensions.AzureQueueStorage.Tests │ ├── NLog.Extensions.AzureQueueStorage.Tests.csproj │ ├── QueueStorageTargetTest.cs │ └── CloudQueueServiceMock.cs └── NLog.Extensions.AzureStorage.IntegrationTest │ ├── NLog.Extensions.AzureStorage.IntegrationTest.csproj │ └── Program.cs ├── .github ├── dependabot.yml └── workflows │ ├── stale.yml │ ├── ci.yml │ └── publish.yml ├── LICENSE ├── DEPRECATED.md ├── DEPENDENCY_MANAGEMENT.md ├── .gitattributes ├── appveyor.yml ├── DEPRECATION_PROCESS.md ├── .gitignore └── README.md /logo64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JDetmar/NLog.Extensions.AzureStorage/HEAD/logo64.png -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventHub/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: InternalsVisibleTo("NLog.Extensions.AzureEventHub.Tests")] -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureAccessToken/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: InternalsVisibleTo("NLog.Extensions.AzureAccessToken.Tests")] 8 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureBlobStorage/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: InternalsVisibleTo("NLog.Extensions.AzureBlobStorage.Tests")] 8 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureDataTables/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: InternalsVisibleTo("NLog.Extensions.AzureDataTables.Tests")] 8 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventGrid/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: InternalsVisibleTo("NLog.Extensions.AzureEventGrid.Tests")] 8 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureServiceBus/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: InternalsVisibleTo("NLog.Extensions.AzureServiceBus.Tests")] 8 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureQueueStorage/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: InternalsVisibleTo("NLog.Extensions.AzureQueueStorage.Tests")] 8 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureCosmosTable/README.md: -------------------------------------------------------------------------------- 1 | # NLog.Extensions.AzureCosmosTable (removed) 2 | 3 | This package was removed from the default branch because it is deprecated, unmaintained, and contains known vulnerabilities. Do not use it. 4 | 5 | Use `NLog.Extensions.AzureDataTables` instead. 6 | 7 | Last code version is preserved at tag `archive/azure-cosmos-table-2022-01-29` (commit f1c345b490a7353c5fd00d1dde42364d162173ce). -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NLog.Extensions.AzureStorage", 3 | "image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-jammy", 4 | "features": { 5 | "ghcr.io/devcontainers/features/github-cli:1": {} 6 | }, 7 | "customizations": { 8 | "vscode": { 9 | "extensions": [ 10 | "ms-dotnettools.csharp", 11 | "ms-dotnettools.vscode-dotnet-runtime" 12 | ], 13 | "settings": { 14 | "dotnet.defaultSolution": "src/NLog.Extensions.AzureStorage.sln" 15 | } 16 | } 17 | }, 18 | "postCreateCommand": "dotnet restore src/NLog.Extensions.AzureStorage.sln" 19 | } -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureDataTables/ICloudTableService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Data.Tables; 5 | using NLog.Extensions.AzureBlobStorage; 6 | 7 | namespace NLog.Extensions.AzureStorage 8 | { 9 | interface ICloudTableService 10 | { 11 | void Connect(string connectionString, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, ProxySettings proxySettings = null); 12 | Task SubmitTransactionAsync(string tableName, IEnumerable tableTransaction, CancellationToken cancellationToken); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventGrid/IEventGridService.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging; 2 | using Azure.Messaging.EventGrid; 3 | using NLog.Extensions.AzureBlobStorage; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace NLog.Extensions.AzureStorage 8 | { 9 | internal interface IEventGridService 10 | { 11 | string Topic { get; } 12 | 13 | void Connect(string topic, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string accessKey, string clientAuthId, string clientAuthSecret, ProxySettings proxySettings = null); 14 | 15 | Task SendEventAsync(EventGridEvent gridEvent, CancellationToken cancellationToken); 16 | 17 | Task SendEventAsync(CloudEvent cloudEvent, CancellationToken cancellationToken); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureQueueStorage/ICloudQueueService.cs: -------------------------------------------------------------------------------- 1 | using NLog.Extensions.AzureBlobStorage; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace NLog.Extensions.AzureStorage 8 | { 9 | internal interface ICloudQueueService 10 | { 11 | void Connect(string connectionString, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, TimeSpan? timeToLive, IDictionary queueMetadata, ProxySettings proxySettings = null); 12 | Task AddMessageAsync(string queueName, string queueMessage, CancellationToken cancellationToken); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureEventHub.Tests/NLog.Extensions.AzureEventHub.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureBlobStorage/ICloudBlobService.cs: -------------------------------------------------------------------------------- 1 | using NLog.Extensions.AzureBlobStorage; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace NLog.Extensions.AzureStorage 7 | { 8 | interface ICloudBlobService 9 | { 10 | void Connect(string connectionString, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, IDictionary blobMetadata, IDictionary blobTags, ProxySettings proxySettings = null); 11 | Task AppendFromByteArrayAsync(string containerName, string blobName, string contentType, byte[] buffer, CancellationToken cancellationToken); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureDataTables.Tests/NLog.Extensions.AzureDataTables.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureEventGrid.Tests/NLog.Extensions.AzureEventGrid.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureServiceBus.Tests/NLog.Extensions.AzureServiceBus.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Security updates only for NuGet packages 4 | - package-ecosystem: "nuget" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | # Only create PRs for security updates 9 | open-pull-requests-limit: 10 10 | # Group all security updates into a single PR when possible 11 | groups: 12 | security-updates: 13 | patterns: 14 | - "*" 15 | update-types: 16 | - "patch" 17 | - "minor" 18 | labels: 19 | - "dependencies" 20 | - "security" 21 | commit-message: 22 | prefix: "deps" 23 | prefix-development: "deps-dev" 24 | include: "scope" 25 | # Ignore major version updates to avoid breaking changes 26 | ignore: 27 | - dependency-name: "*" 28 | update-types: ["version-update:semver-major"] 29 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureAccessToken.Tests/NLog.Extensions.AzureAccessToken.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureBlobStorage.Tests/NLog.Extensions.AzureBlobStorage.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureCosmosTable.Tests/NLog.Extensions.AzureCosmosTable.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureQueueStorage.Tests/NLog.Extensions.AzureQueueStorage.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureStorage.IntegrationTest/NLog.Extensions.AzureStorage.IntegrationTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventHub/IEventHubService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Messaging.EventHubs; 5 | using NLog.Extensions.AzureBlobStorage; 6 | 7 | namespace NLog.Extensions.AzureStorage 8 | { 9 | internal interface IEventHubService 10 | { 11 | string EventHubName { get; } 12 | void Connect(string connectionString, string eventHubName, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, string eventProducerIdentifier, bool useWebSockets, string endPointAddress, ProxySettings proxySettings); 13 | Task CloseAsync(); 14 | Task SendAsync(IEnumerable eventDataBatch, string partitionKey, CancellationToken cancellationToken); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureServiceBus/ICloudServiceBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Azure.Messaging.ServiceBus; 6 | using NLog.Extensions.AzureBlobStorage; 7 | 8 | namespace NLog.Extensions.AzureStorage 9 | { 10 | internal interface ICloudServiceBus 11 | { 12 | string EntityPath { get; } 13 | TimeSpan? DefaultTimeToLive { get; } 14 | void Connect(string connectionString, string queueOrTopicName, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, string eventProducerIdentifier, bool useWebSockets, string endPointAddress, TimeSpan? timeToLive, ProxySettings proxySettings); 15 | Task SendAsync(IEnumerable messages, CancellationToken cancellationToken); 16 | Task CloseAsync(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureAccessToken.Tests/AzureServiceTokenProviderMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace NLog.Extensions.AzureAccessToken.Tests 7 | { 8 | class AzureServiceTokenProviderMock : AccessTokenLayoutRenderer.IAzureServiceTokenProviderService 9 | { 10 | private readonly string _accessToken; 11 | private readonly TimeSpan _refreshInterval; 12 | 13 | public AzureServiceTokenProviderMock(string accessToken, TimeSpan refreshInterval) 14 | { 15 | _accessToken = accessToken; 16 | _refreshInterval = refreshInterval; 17 | } 18 | 19 | public async Task> GetAuthenticationResultAsync(string resource, string tenantId, CancellationToken cancellationToken) 20 | { 21 | await Task.Delay(1); 22 | return new KeyValuePair(_accessToken ?? Guid.NewGuid().ToString(), DateTimeOffset.UtcNow.Add(_refreshInterval)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Justin Detmar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureStorage.IntegrationTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace NLog.Extensions.AzureStorage.IntegrationTest 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | var logger = LogManager.GetCurrentClassLogger(); 16 | 17 | for (int i = 0; i < 100; i++) 18 | { 19 | logger.Trace("Trace Message: " + i); 20 | logger.Debug("Debug Message: " + i); 21 | logger.Info("Info Message: " + i); 22 | logger.Warn("Warn Message: " + i); 23 | logger.Error("Error Message: " + i); 24 | logger.Fatal("Fatal Message: " + i); 25 | Thread.Sleep(10); 26 | } 27 | 28 | try 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | catch (Exception ex) 33 | { 34 | logger.Error(ex, "We threw an exception"); 35 | } 36 | 37 | Console.ReadLine(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureStorage/AzureCredentialHelper.cs: -------------------------------------------------------------------------------- 1 | namespace NLog.Extensions.AzureStorage 2 | { 3 | internal static class AzureCredentialHelpers 4 | { 5 | internal static Azure.Identity.DefaultAzureCredential CreateTokenCredentials(string managedIdentityClientId, string tenantIdentity, string managedIdentityResourceId) 6 | { 7 | var options = new Azure.Identity.DefaultAzureCredentialOptions(); 8 | 9 | if (!string.IsNullOrWhiteSpace(tenantIdentity)) 10 | { 11 | options.TenantId = tenantIdentity; 12 | } 13 | 14 | if (!string.IsNullOrWhiteSpace(managedIdentityClientId)) 15 | { 16 | options.ManagedIdentityClientId = managedIdentityClientId; 17 | options.WorkloadIdentityClientId = managedIdentityClientId; 18 | } 19 | else 20 | { 21 | if (!string.IsNullOrWhiteSpace(managedIdentityResourceId)) 22 | { 23 | options.ManagedIdentityResourceId = new Azure.Core.ResourceIdentifier(managedIdentityResourceId); 24 | } 25 | else if (string.IsNullOrWhiteSpace(tenantIdentity)) 26 | { 27 | return new Azure.Identity.DefaultAzureCredential(); // Default Azure Credential 28 | } 29 | } 30 | 31 | return new Azure.Identity.DefaultAzureCredential(options); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /DEPRECATED.md: -------------------------------------------------------------------------------- 1 | # Deprecated package: NLog.Extensions.AzureCosmosTable 2 | 3 | `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. 4 | 5 | ## Guidance 6 | 7 | - Do not use this package in new or existing projects. 8 | - Migrate to `NLog.Extensions.AzureDataTables`, which targets the supported Azure Data Tables APIs. 9 | - Unlist or remove any internal feeds that still carry this package to prevent accidental consumption. 10 | 11 | ## Status 12 | 13 | - Maintenance: stopped 14 | - Security: known vulnerabilities, will not be fixed 15 | - NuGet: marked deprecated; recommend unlisting any remaining versions 16 | - Last code commit containing this package: f1c345b490a7353c5fd00d1dde42364d162173ce (2022-01-29 — see tag `archive/azure-cosmos-table-2022-01-29`) 17 | 18 | ## Deprecated package: NLog.Extensions.AzureStorage (bundle) 19 | 20 | The legacy bundled package `NLog.Extensions.AzureStorage` was superseded when targets were split. It should not be used. 21 | 22 | ### Guidance (bundle) 23 | 24 | - Do not use the bundled package; consume the individual packages (Blob, Queue, EventHub, EventGrid, DataTables, ServiceBus, AccessToken) instead. 25 | - Unlist or remove any internal feeds that still carry the bundle to prevent accidental consumption. 26 | 27 | ### Status (bundle) 28 | 29 | - Maintenance: stopped 30 | - Security: inherits vulnerabilities from deprecated dependencies in the bundle; will not be fixed 31 | - NuGet: should be marked deprecated/unlisted 32 | - Last code commit containing this bundle: c8bfb7966d550221e1aeca859705f606c8559dd2 (tag `archive/azure-storage-bundle`) 33 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureCosmosTable.Tests/CloudTableServiceMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.Cosmos.Table; 7 | using NLog.Extensions.AzureStorage; 8 | 9 | namespace NLog.Extensions.AzureTableStorage.Tests 10 | { 11 | class CloudTableServiceMock : ICloudTableService 12 | { 13 | public Dictionary BatchExecuted { get; } = new Dictionary(); 14 | public string ConnectionString { get; private set; } 15 | 16 | public void Connect(string connectionString, int? defaultTimeToLiveSeconds) 17 | { 18 | ConnectionString = connectionString; 19 | } 20 | 21 | public Task ExecuteBatchAsync(string tableName, TableBatchOperation tableOperation, CancellationToken cancellationToken) 22 | { 23 | if (string.IsNullOrEmpty(ConnectionString)) 24 | throw new InvalidOperationException("CloudTableService not connected"); 25 | 26 | return Task.Delay(10).ContinueWith(t => 27 | { 28 | lock (BatchExecuted) 29 | BatchExecuted[tableName] = tableOperation; 30 | }); 31 | } 32 | 33 | public IEnumerable PeekLastAdded(string tableName) 34 | { 35 | lock (BatchExecuted) 36 | { 37 | if (BatchExecuted.TryGetValue(tableName, out var tableOperation)) 38 | { 39 | return tableOperation.Select(t => t.Entity); 40 | } 41 | } 42 | 43 | return null; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureQueueStorage.Tests/QueueStorageTargetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NLog.Targets; 4 | using Xunit; 5 | 6 | namespace NLog.Extensions.AzureQueueStorage.Tests 7 | { 8 | public class QueueStorageTargetTest 9 | { 10 | [Fact] 11 | public void SingleLogEventTest() 12 | { 13 | var logFactory = new LogFactory(); 14 | var logConfig = new Config.LoggingConfiguration(logFactory); 15 | logConfig.Variables["ConnectionString"] = nameof(QueueStorageTargetTest); 16 | var cloudQueueService = new CloudQueueServiceMock(); 17 | var queueStorageTarget = new QueueStorageTarget(cloudQueueService); 18 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 19 | queueStorageTarget.QueueName = "${logger}"; 20 | queueStorageTarget.Layout = "${message}"; 21 | queueStorageTarget.QueueMetadata.Add(new TargetPropertyWithContext() { Name = "MyMeta", Layout = "MyMetaValue" }); 22 | logConfig.AddRuleForAllLevels(queueStorageTarget); 23 | logFactory.Configuration = logConfig; 24 | logFactory.GetLogger("test").Info("Hello World"); 25 | logFactory.Flush(); 26 | Assert.Equal(nameof(QueueStorageTargetTest), cloudQueueService.ConnectionString); 27 | Assert.Single(cloudQueueService.QueueMetadata); 28 | Assert.Contains(new KeyValuePair("MyMeta", "MyMetaValue"), cloudQueueService.QueueMetadata); 29 | Assert.Single(cloudQueueService.MessagesAdded); // One queue 30 | Assert.Equal("Hello World", cloudQueueService.PeekLastAdded("test")); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DEPENDENCY_MANAGEMENT.md: -------------------------------------------------------------------------------- 1 | # Dependency Management 2 | 3 | ## TL;DR 4 | 5 | **Dependabot handles security updates automatically.** Just review PRs labeled `security` and merge if tests pass. 6 | 7 | ## How It Works 8 | 9 | 1. Dependabot scans weekly for security vulnerabilities 10 | 2. Creates PRs automatically for security patches (ignores major version bumps) 11 | 3. You review the PR (~5 min) 12 | 4. Tests run in CI 13 | 5. Merge if green 14 | 6. Update version number in `.csproj` and add release note 15 | 16 | ## When to Update 17 | 18 | ✅ **Security fixes** - Always 19 | ⚠️ **New features** - Only if needed 20 | ❌ **Major versions** - Avoid unless necessary 21 | 22 | ## Quick Reference 23 | 24 | ### Approve a Dependabot PR 25 | 26 | ```bash 27 | # Tests run automatically in CI, but you can run locally: 28 | dotnet test test\NLog.Extensions.AzureBlobStorage.Tests /p:Configuration=Release 29 | dotnet test test\NLog.Extensions.AzureDataTables.Tests /p:Configuration=Release 30 | dotnet test test\NLog.Extensions.AzureQueueStorage.Tests /p:Configuration=Release 31 | dotnet test test\NLog.Extensions.AzureEventGrid.Tests /p:Configuration=Release 32 | dotnet test test\NLog.Extensions.AzureEventHub.Tests /p:Configuration=Release 33 | dotnet test test\NLog.Extensions.AzureServiceBus.Tests /p:Configuration=Release 34 | ``` 35 | 36 | After merge: Bump version in affected `.csproj` files and update ``. 37 | 38 | ### Check for Vulnerabilities Manually 39 | 40 | ```bash 41 | dotnet list package --vulnerable 42 | ``` 43 | 44 | ### GitHub Settings 45 | 46 | Enable these notifications: 47 | 48 | - Dependabot alerts (Settings → Security) 49 | - PRs labeled `security` 50 | 51 | ## Versioning 52 | 53 | - **Patch** (x.y.Z): Security fixes, dependency updates 54 | - **Minor** (x.Y.0): New features 55 | - **Major** (X.0.0): Breaking changes (rare) 56 | 57 | ## Time Commitment 58 | 59 | - **Most months**: 0 minutes (no security issues) 60 | - **Security update**: 10-15 minutes (review + merge PR) 61 | - **Quarterly check**: Optional 62 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureDataTables.Tests/CloudTableServiceMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Azure.Data.Tables; 7 | using NLog.Extensions.AzureBlobStorage; 8 | using NLog.Extensions.AzureStorage; 9 | 10 | namespace NLog.Extensions.AzureTableStorage.Tests 11 | { 12 | class CloudTableServiceMock : ICloudTableService 13 | { 14 | public Dictionary> BatchExecuted { get; } = new Dictionary>(); 15 | public string ConnectionString { get; private set; } 16 | 17 | public void Connect(string connectionString, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, ProxySettings proxySettings = null) 18 | { 19 | ConnectionString = connectionString; 20 | } 21 | 22 | public Task SubmitTransactionAsync(string tableName, IEnumerable tableTransaction, CancellationToken cancellationToken) 23 | { 24 | if (string.IsNullOrEmpty(ConnectionString)) 25 | throw new InvalidOperationException("CloudTableService not connected"); 26 | 27 | return Task.Delay(10).ContinueWith(t => 28 | { 29 | lock (BatchExecuted) 30 | BatchExecuted[tableName] = tableTransaction; 31 | }); 32 | } 33 | 34 | public IEnumerable PeekLastAdded(string tableName) 35 | { 36 | lock (BatchExecuted) 37 | { 38 | if (BatchExecuted.TryGetValue(tableName, out var tableOperation)) 39 | { 40 | return tableOperation.Select(t => t.Entity); 41 | } 42 | } 43 | 44 | return null; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Manage stale issues and PRs' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * MON' # Run weekly on Mondays at midnight UTC 6 | workflow_dispatch: # Allow manual triggering 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/stale@v9 17 | with: 18 | # Issue settings 19 | stale-issue-message: > 20 | This issue has been inactive for 90 days and has been marked as stale. 21 | If you're still experiencing this with the latest version, please comment 22 | and we'll reopen it. Otherwise, this will be closed in 14 days. 23 | Thank you for your contribution! 24 | close-issue-message: > 25 | This issue was closed due to inactivity. If this is still relevant, 26 | please feel free to create a new issue with updated information. 27 | days-before-stale: 90 28 | days-before-close: 14 29 | stale-issue-label: 'stale' 30 | close-issue-label: 'closed-stale' 31 | exempt-issue-labels: 'enhancement' 32 | 33 | # PR settings (more aggressive timeline) 34 | stale-pr-message: > 35 | This pull request has been inactive for 60 days and has been marked as stale. 36 | Please update if you're still working on this. This will be closed in 14 days 37 | if there's no further activity. 38 | close-pr-message: > 39 | This pull request was closed due to inactivity. Feel free to reopen 40 | if you'd like to continue working on this. 41 | days-before-pr-stale: 60 42 | days-before-pr-close: 14 43 | stale-pr-label: 'stale' 44 | close-pr-label: 'closed-stale' 45 | exempt-pr-labels: 'enhancement,work-in-progress' 46 | 47 | # Performance settings 48 | operations-per-run: 30 49 | remove-stale-when-updated: true 50 | ascending: true # Process oldest first 51 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureQueueStorage.Tests/CloudQueueServiceMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using NLog.Extensions.AzureBlobStorage; 6 | using NLog.Extensions.AzureStorage; 7 | 8 | namespace NLog.Extensions.AzureQueueStorage.Tests 9 | { 10 | class CloudQueueServiceMock : ICloudQueueService 11 | { 12 | public Dictionary MessagesAdded { get; } = new Dictionary(); 13 | 14 | public string ConnectionString { get; private set; } 15 | 16 | public TimeSpan? TimeToLive { get; private set; } 17 | 18 | public IDictionary QueueMetadata { get; private set; } 19 | 20 | public Task AddMessageAsync(string queueName, string queueMessage, CancellationToken cancellationToken) 21 | { 22 | if (string.IsNullOrEmpty(ConnectionString)) 23 | throw new InvalidOperationException("CloudQueueService not connected"); 24 | 25 | return Task.Delay(10, cancellationToken).ContinueWith(t => 26 | { 27 | lock (MessagesAdded) 28 | MessagesAdded[queueName] = queueMessage; 29 | }); 30 | } 31 | 32 | public void Connect(string connectionString, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, TimeSpan? timeToLive, IDictionary queueMetadata, ProxySettings proxySettings = null) 33 | { 34 | ConnectionString = connectionString; 35 | QueueMetadata = queueMetadata; 36 | TimeToLive = timeToLive; 37 | } 38 | 39 | public string PeekLastAdded(string queueName) 40 | { 41 | lock (MessagesAdded) 42 | { 43 | if (MessagesAdded.TryGetValue(queueName, out var queueMessage)) 44 | { 45 | return queueMessage; 46 | } 47 | } 48 | 49 | return null; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureAccessToken/NLog.Extensions.AzureAccessToken.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard1.4;net461;net452 5 | true 6 | true 7 | true 8 | master 9 | true 10 | snupkg 11 | 12 | 2.3.0 13 | 14 | NLog AccessTokenLayoutRenderer for application authentication and acquire AccessToken for resource. 15 | jdetmar 16 | $([System.DateTime]::Now.ToString(yyyy)) 17 | Copyright (c) $(CurrentYear) - jdetmar 18 | 19 | NLog;Azure;AccessToken;Authentication;AppAuthentication 20 | logo64.png 21 | README.md 22 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 23 | git 24 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 25 | MIT 26 | 27 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureAccessToken/README.md 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureBlobStorage.Tests/CloudBlobServiceMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using NLog.Extensions.AzureStorage; 7 | 8 | namespace NLog.Extensions.AzureBlobStorage.Tests 9 | { 10 | class CloudBlobServiceMock : ICloudBlobService 11 | { 12 | public Dictionary, byte[]> AppendBlob { get; } = new Dictionary, byte[]>(); 13 | 14 | public string ConnectionString { get; private set; } 15 | 16 | public IDictionary BlobMetadata { get; private set; } 17 | 18 | public IDictionary BlobTags { get; private set; } 19 | 20 | public void Connect(string connectionString, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, IDictionary blobMetadata, IDictionary blobTags, ProxySettings proxySettings = null) 21 | { 22 | ConnectionString = connectionString; 23 | BlobMetadata = blobMetadata; 24 | BlobTags = blobTags; 25 | } 26 | 27 | public Task AppendFromByteArrayAsync(string containerName, string blobName, string contentType, byte[] buffer, CancellationToken cancellationToken) 28 | { 29 | if (string.IsNullOrEmpty(ConnectionString)) 30 | throw new InvalidOperationException("CloudBlobService not connected"); 31 | 32 | lock (AppendBlob) 33 | AppendBlob[new KeyValuePair(containerName, blobName)] = buffer; 34 | return Task.Delay(10, cancellationToken); 35 | } 36 | 37 | public string PeekLastAppendBlob(string containerName, string blobName) 38 | { 39 | lock (AppendBlob) 40 | { 41 | if (AppendBlob.TryGetValue(new KeyValuePair(containerName, blobName), out var payload)) 42 | { 43 | return Encoding.UTF8.GetString(payload); 44 | } 45 | } 46 | 47 | return null; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventGrid/NLog.Extensions.AzureEventGrid.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0;net10.0 5 | true 6 | true 7 | true 8 | master 9 | true 10 | snupkg 11 | 12 | 4.7.1 13 | 14 | NLog EventGridTarget for writing to Azure Event Grid 15 | jdetmar 16 | $([System.DateTime]::Now.ToString(yyyy)) 17 | Copyright (c) $(CurrentYear) - jdetmar 18 | 19 | NLog;azure;EventGrid;Event;Grid;CloudEvents;log;logging 20 | logo64.png 21 | README.md 22 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 23 | git 24 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 25 | MIT 26 | 27 | - Updated Azure.Identity to ver 1.17.1 (Security fix) 28 | 29 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureEventGrid/README.md 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventHub/NLog.Extensions.AzureEventHub.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0;net10.0 5 | true 6 | true 7 | true 8 | master 9 | true 10 | snupkg 11 | 12 | 4.7.1 13 | 14 | NLog EventHubTarget for writing to Azure Event Hubs datastreams 15 | jdetmar 16 | $([System.DateTime]::Now.ToString(yyyy)) 17 | Copyright (c) $(CurrentYear) - jdetmar 18 | 19 | NLog;azure;eventhubs;AMQP;log;logging 20 | logo64.png 21 | README.md 22 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 23 | git 24 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 25 | MIT 26 | 27 | - Updated Azure.Identity to ver 1.17.1 (Security fix) 28 | 29 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureEventHub/README.md 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureEventGrid.Tests/EventGridTargetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NLog.Targets; 4 | using Xunit; 5 | 6 | namespace NLog.Extensions.AzureEventGrid.Tests 7 | { 8 | public class EventGridTargetTest 9 | { 10 | [Fact] 11 | public void SingleGridEventTest() 12 | { 13 | var logFactory = new LogFactory(); 14 | var logConfig = new Config.LoggingConfiguration(logFactory); 15 | logConfig.Variables["TopicUrl"] = nameof(EventGridTargetTest); 16 | var eventGridService = new EventGridServiceMock(); 17 | var eventGridTarget = new EventGridTarget(eventGridService); 18 | eventGridTarget.Topic = "${var:TopicUrl}"; 19 | eventGridTarget.Layout = "${message}"; 20 | eventGridTarget.GridEventSubject = "${logger}"; 21 | eventGridTarget.MessageProperties.Add(new TargetPropertyWithContext() { Name = "MyMeta", Layout = "MyMetaValue" }); 22 | logConfig.AddRuleForAllLevels(eventGridTarget); 23 | logFactory.Configuration = logConfig; 24 | logFactory.GetLogger("test").Info("Hello World"); 25 | logFactory.Flush(); 26 | Assert.Equal(nameof(EventGridTargetTest), eventGridService.Topic); 27 | Assert.Single(eventGridService.GridEvents); // One queue 28 | Assert.Equal("Hello World", eventGridService.PeekLastGridEvent()); 29 | } 30 | 31 | [Fact] 32 | public void SingleCloudEventTest() 33 | { 34 | var logFactory = new LogFactory(); 35 | var logConfig = new Config.LoggingConfiguration(logFactory); 36 | logConfig.Variables["TopicUrl"] = nameof(EventGridTargetTest); 37 | var eventGridService = new EventGridServiceMock(); 38 | var eventGridTarget = new EventGridTarget(eventGridService); 39 | eventGridTarget.Topic = "${var:TopicUrl}"; 40 | eventGridTarget.Layout = "${message}"; 41 | eventGridTarget.CloudEventSource = "${logger}"; 42 | eventGridTarget.MessageProperties.Add(new TargetPropertyWithContext() { Name = "MyMeta", Layout = "MyMetaValue" }); 43 | logConfig.AddRuleForAllLevels(eventGridTarget); 44 | logFactory.Configuration = logConfig; 45 | logFactory.GetLogger("test").Info("Hello World"); 46 | logFactory.Flush(); 47 | Assert.Equal(nameof(EventGridTargetTest), eventGridService.Topic); 48 | Assert.Single(eventGridService.CloudEvents); // One queue 49 | Assert.Equal("Hello World", eventGridService.PeekLastCloudEvent()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureServiceBus.Tests/ServiceBusMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Azure.Messaging.ServiceBus; 6 | using NLog.Extensions.AzureBlobStorage; 7 | using NLog.Extensions.AzureStorage; 8 | 9 | namespace NLog.Extensions.AzureServiceBus.Test 10 | { 11 | class ServiceBusMock : ICloudServiceBus 12 | { 13 | public List> MessageDataSent { get; } = new List>(); 14 | public string ConnectionString { get; private set; } 15 | public string EntityPath { get; private set; } 16 | public TimeSpan? DefaultTimeToLive { get; private set; } 17 | 18 | public string PeekLastMessageBody() 19 | { 20 | lock (MessageDataSent) 21 | { 22 | if (MessageDataSent.Count > 0) 23 | { 24 | var messages = MessageDataSent[MessageDataSent.Count - 1]; 25 | if (messages.Count > 0) 26 | { 27 | return Encoding.UTF8.GetString(messages[messages.Count - 1].Body); 28 | } 29 | } 30 | } 31 | 32 | return null; 33 | } 34 | 35 | public void Connect(string connectionString, string queueOrTopicName, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, string eventProducerIdentifier, bool useWebSockets, string endPointAddress, TimeSpan? timeToLive, ProxySettings proxySettings) 36 | { 37 | ConnectionString = connectionString; 38 | EntityPath = queueOrTopicName; 39 | DefaultTimeToLive = timeToLive; 40 | } 41 | 42 | public Task SendAsync(IEnumerable messages, System.Threading.CancellationToken cancellationToken) 43 | { 44 | if (string.IsNullOrEmpty(ConnectionString)) 45 | throw new InvalidOperationException("ServiceBusService not connected"); 46 | 47 | return Task.Delay(10, cancellationToken).ContinueWith(t => 48 | { 49 | lock (MessageDataSent) 50 | MessageDataSent.Add(new List(messages)); 51 | }, cancellationToken); 52 | } 53 | 54 | public async Task CloseAsync() 55 | { 56 | await Task.Delay(1).ConfigureAwait(false); 57 | lock (MessageDataSent) 58 | MessageDataSent.Clear(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureBlobStorage/NLog.Extensions.AzureBlobStorage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0;net10.0 5 | true 6 | true 7 | true 8 | master 9 | true 10 | snupkg 11 | 12 | 4.7.1 13 | 14 | NLog BlobStorageTarget for writing to Azure Cloud Blob Storage 15 | jdetmar 16 | $([System.DateTime]::Now.ToString(yyyy)) 17 | Copyright (c) $(CurrentYear) - jdetmar 18 | 19 | NLog;azure;CloudBlob;blob;storage;log;logging 20 | logo64.png 21 | README.md 22 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 23 | git 24 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 25 | MIT 26 | 27 | - Updated Azure.Identity to ver 1.17.1 (Security fix) 28 | 29 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureBlobStorage/README.md 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureQueueStorage/NLog.Extensions.AzureQueueStorage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0;net10.0 5 | true 6 | true 7 | true 8 | master 9 | true 10 | snupkg 11 | 4.7.1 12 | 13 | NLog QueueStorageTarget for writing to Azure Cloud Queue Storage 14 | jdetmar 15 | $([System.DateTime]::Now.ToString(yyyy)) 16 | Copyright (c) $(CurrentYear) - jdetmar 17 | 18 | NLog;azure;cloudqueue;queue;storage;log;logging 19 | logo64.png 20 | README.md 21 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 22 | git 23 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 24 | MIT 25 | 26 | - Updated Azure.Identity to ver 1.17.1 (Security fix) 27 | 28 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureQueueStorage/README.md 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureEventGrid.Tests/EventGridServiceMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Azure.Messaging; 8 | using Azure.Messaging.EventGrid; 9 | using NLog.Extensions.AzureBlobStorage; 10 | using NLog.Extensions.AzureStorage; 11 | 12 | namespace NLog.Extensions.AzureEventGrid.Tests 13 | { 14 | public class EventGridServiceMock : IEventGridService 15 | { 16 | public string Topic { get; set; } 17 | 18 | public List GridEvents { get; } = new List(); 19 | 20 | public List CloudEvents { get; } = new List(); 21 | 22 | public void Connect(string topic, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string accessKey, string clientAuthId, string clientAuthSecret, ProxySettings proxySettings = null) 23 | { 24 | Topic = topic; 25 | } 26 | 27 | public Task SendEventAsync(EventGridEvent gridEvent, CancellationToken cancellationToken) 28 | { 29 | if (string.IsNullOrEmpty(Topic)) 30 | throw new InvalidOperationException("TopicUri not connected"); 31 | 32 | return Task.Delay(10, cancellationToken).ContinueWith(t => 33 | { 34 | lock (GridEvents) 35 | GridEvents.Add(gridEvent); 36 | }); 37 | } 38 | 39 | public Task SendEventAsync(CloudEvent cloudEvent, CancellationToken cancellationToken) 40 | { 41 | if (string.IsNullOrEmpty(Topic)) 42 | throw new InvalidOperationException("TopicUri not connected"); 43 | 44 | return Task.Delay(10, cancellationToken).ContinueWith(t => 45 | { 46 | lock (CloudEvents) 47 | CloudEvents.Add(cloudEvent); 48 | }); 49 | } 50 | 51 | public string PeekLastGridEvent() 52 | { 53 | lock (GridEvents) 54 | { 55 | var gridEvent = GridEvents.LastOrDefault(); 56 | if (gridEvent != null) 57 | return Encoding.UTF8.GetString(gridEvent.Data.ToArray()); 58 | } 59 | 60 | return null; 61 | } 62 | 63 | public string PeekLastCloudEvent() 64 | { 65 | lock (CloudEvents) 66 | { 67 | var cloudEvent = CloudEvents.LastOrDefault(); 68 | if (cloudEvent != null) 69 | return Encoding.UTF8.GetString(cloudEvent.Data.ToArray()); 70 | } 71 | 72 | return null; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureServiceBus/NLog.Extensions.AzureServiceBus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0;net10.0 5 | true 6 | true 7 | true 8 | master 9 | true 10 | snupkg 11 | 12 | 4.7.1 13 | 14 | NLog ServiceBusTarget for writing to Azure Service Bus Topic or Queue 15 | jdetmar 16 | $([System.DateTime]::Now.ToString(yyyy)) 17 | Copyright (c) $(CurrentYear) - jdetmar 18 | 19 | NLog;azure;servicebus;AMQP;bus;queue;topic;log;logging 20 | logo64.png 21 | README.md 22 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 23 | git 24 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 25 | MIT 26 | 27 | - Updated Azure.Identity to ver 1.17.1 (Security fix) 28 | 29 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureServiceBus/README.md 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureDataTables/NLog.Extensions.AzureDataTables.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0;net10.0 5 | true 6 | true 7 | true 8 | master 9 | true 10 | snupkg 11 | 12 | 4.7.1 13 | 14 | NLog DataTablesTarget for writing to Azure Tables in Azure storage accounts or Azure Cosmos DB table API 15 | jdetmar 16 | $([System.DateTime]::Now.ToString(yyyy)) 17 | Copyright (c) $(CurrentYear) - jdetmar 18 | 19 | NLog;azure;CloudTable;cosmos;cosmosdb;documentdb;table;tables;storage;log;logging 20 | logo64.png 21 | README.md 22 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 23 | git 24 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 25 | MIT 26 | 27 | - Updated Azure.Identity to ver 1.17.1 (Security fix) 28 | 29 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureDataTables/README.md 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureEventHub.Tests/EventHubServiceMock.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.EventHubs; 2 | using NLog.Extensions.AzureBlobStorage; 3 | using NLog.Extensions.AzureStorage; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace NLog.Extensions.AzureEventHub.Test 11 | { 12 | class EventHubServiceMock : IEventHubService 13 | { 14 | private readonly Random _random = new Random(); 15 | 16 | public Dictionary> EventDataSent { get; } = new Dictionary>(); 17 | public string ConnectionString { get; private set; } 18 | public string EventHubName { get; private set; } 19 | 20 | public async Task CloseAsync() 21 | { 22 | await Task.Delay(1).ConfigureAwait(false); 23 | lock (EventDataSent) 24 | EventDataSent.Clear(); 25 | } 26 | 27 | public void Connect(string connectionString, string eventHubName, string serviceUri, string tenantIdentity, string managedIdentityResourceId, string managedIdentityClientId, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientAuthId, string clientAuthSecret, string eventProducerIdentifier, bool useWebSockets, string endPointAddress, ProxySettings proxySettings) 28 | { 29 | ConnectionString = connectionString; 30 | EventHubName = eventHubName; 31 | } 32 | 33 | public Task SendAsync(IEnumerable eventDataBatch, string partitionKey, CancellationToken cancellationToken) 34 | { 35 | if (string.IsNullOrEmpty(ConnectionString)) 36 | throw new InvalidOperationException("EventHubService not connected"); 37 | 38 | return Task.Delay(_random.Next(5, 10), cancellationToken).ContinueWith(t => 39 | { 40 | lock (EventDataSent) 41 | { 42 | if (EventDataSent.TryGetValue(partitionKey, out var existingBatch)) 43 | existingBatch.AddRange(eventDataBatch); 44 | else 45 | EventDataSent[partitionKey] = new List(eventDataBatch); 46 | } 47 | }, cancellationToken); 48 | } 49 | 50 | public string PeekLastSent(string partitionKey) 51 | { 52 | lock (EventDataSent) 53 | { 54 | if (EventDataSent.TryGetValue(partitionKey, out var eventData)) 55 | { 56 | if (eventData.Count > 0) 57 | { 58 | return Encoding.UTF8.GetString(eventData[eventData.Count - 1].Body.ToArray()); 59 | } 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | image: Visual Studio 2022 3 | configuration: Release 4 | 5 | install: 6 | - ps: | 7 | Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -UseBasicParsing -OutFile "$env:temp\dotnet-install.ps1" 8 | & $env:temp\dotnet-install.ps1 -Architecture x64 -Version '10.0.100' -InstallDir "$env:ProgramFiles\dotnet" 9 | 10 | before_build: 11 | - cmd: dotnet --version 12 | 13 | build_script: 14 | - cmd: msbuild /t:Restore src\NLog.Extensions.AzureStorage.sln /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PublishRepositoryUrl=true /verbosity:minimal 15 | - cmd: msbuild /t:Pack src\NLog.Extensions.AzureBlobStorage /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PublishRepositoryUrl=true /verbosity:minimal 16 | - cmd: msbuild /t:Pack src\NLog.Extensions.AzureDataTables /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PublishRepositoryUrl=true /verbosity:minimal 17 | - cmd: msbuild /t:Pack src\NLog.Extensions.AzureQueueStorage /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PublishRepositoryUrl=true /verbosity:minimal 18 | - cmd: msbuild /t:Pack src\NLog.Extensions.AzureEventGrid /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PublishRepositoryUrl=true /verbosity:minimal 19 | - cmd: msbuild /t:Pack src\NLog.Extensions.AzureEventHub /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PublishRepositoryUrl=true /verbosity:minimal 20 | - cmd: msbuild /t:Pack src\NLog.Extensions.AzureServiceBus /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PublishRepositoryUrl=true /verbosity:minimal 21 | 22 | test_script: 23 | - cmd: dotnet test test\NLog.Extensions.AzureBlobStorage.Tests /p:Configuration=Release --verbosity minimal 24 | - cmd: dotnet test test\NLog.Extensions.AzureDataTables.Tests /p:Configuration=Release --verbosity minimal 25 | - cmd: dotnet test test\NLog.Extensions.AzureQueueStorage.Tests /p:Configuration=Release --verbosity minimal 26 | - cmd: dotnet test test\NLog.Extensions.AzureEventGrid.Tests /p:Configuration=Release --verbosity minimal 27 | - cmd: dotnet test test\NLog.Extensions.AzureEventHub.Tests /p:Configuration=Release --verbosity minimal 28 | - cmd: dotnet test test\NLog.Extensions.AzureServiceBus.Tests /p:Configuration=Release --verbosity minimal 29 | 30 | artifacts: 31 | - path: '**\NLog.Extensions.Azure*.nupkg' 32 | - path: '**\NLog.Extensions.Azure*.snupkg' -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureStorage/SortHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NLog.Extensions.AzureStorage 5 | { 6 | internal static class SortHelpers 7 | { 8 | /// 9 | /// Key Selector Delegate 10 | /// 11 | /// The type of the value. 12 | /// The type of the key. 13 | /// The value. 14 | /// 15 | internal delegate TKey KeySelector(TValue value); 16 | 17 | /// 18 | /// Buckets sorts returning a dictionary of lists 19 | /// 20 | /// The type of the value. 21 | /// The type of the key. 22 | /// The inputs. 23 | /// The key selector. 24 | /// 25 | internal static ICollection>> BucketSort(IList inputs, KeySelector keySelector) where TKey : IEquatable 26 | { 27 | if (inputs.Count == 0) 28 | return Array.Empty>>(); 29 | 30 | Dictionary> buckets = null; 31 | TKey firstBucketKey = keySelector(inputs[0]); 32 | for (int i = 1; i < inputs.Count; ++i) 33 | { 34 | TKey keyValue = keySelector(inputs[i]); 35 | if (buckets is null) 36 | { 37 | if (!firstBucketKey.Equals(keyValue)) 38 | { 39 | // Multiple buckets needed, allocate full dictionary 40 | buckets = CreateBucketDictionaryWithValue(inputs, i, firstBucketKey, keyValue); 41 | } 42 | } 43 | else 44 | { 45 | if (!buckets.TryGetValue(keyValue, out var eventsInBucket)) 46 | { 47 | eventsInBucket = new List(); 48 | buckets.Add(keyValue, eventsInBucket); 49 | } 50 | eventsInBucket.Add(inputs[i]); 51 | } 52 | } 53 | 54 | if (buckets is null) 55 | { 56 | // All inputs belong to the same bucket 57 | return new[] { new KeyValuePair>(firstBucketKey, inputs) }; 58 | } 59 | else 60 | { 61 | return buckets; 62 | } 63 | } 64 | 65 | private static Dictionary> CreateBucketDictionaryWithValue(IList inputs, int currentIndex, TKey firstBucketKey, TKey nextBucketKey) 66 | { 67 | var buckets = new Dictionary>(); 68 | var firstBucket = new List(Math.Max(currentIndex + 2, 4)); 69 | for (int i = 0; i < currentIndex; i++) 70 | { 71 | firstBucket.Add(inputs[i]); 72 | } 73 | buckets[firstBucketKey] = firstBucket; 74 | 75 | var nextBucket = new List { inputs[currentIndex] }; 76 | buckets[nextBucketKey] = nextBucket; 77 | return buckets; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureAccessToken/README.md: -------------------------------------------------------------------------------- 1 | # Azure AccessToken 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureAccessToken** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureAccessToken.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureAccessToken/) | Azure App Authentication Access Token | 6 | 7 | 8 | ## Using Active Directory Default authentication 9 | 10 | Microsoft.Data.SqlClient 2.0.0 (and newer) supports `Authentication` option in the ConnectionString, 11 | that enables builtin AD authentication. This removes the need for using `NLog.Extensions.AzureAccessToken`. 12 | 13 | Example with `Authentication` assigned to `Active Directory Default`: 14 | 15 | ```charp 16 | string ConnectionString = @"Server=demo.database.windows.net; Authentication=Active Directory Default; Database=testdb;"; 17 | ``` 18 | 19 | `Active Directory Default` uses `DefaultAzureCredential` from Azure.Identity-package that supports the following identity-providers: 20 | 21 | - EnvironmentCredential - Authentication to Azure Active Directory based on environment variables. 22 | - ManagedIdentityCredential - Authentication to Azure Active Directory using identity assigned to deployment environment. 23 | - SharedTokenCacheCredential - Authentication using tokens in the local cache shared between Microsoft applications. 24 | - VisualStudioCredential - Authentication to Azure Active Directory using data from Visual Studio 25 | - VisualStudioCodeCredential - Authentication to Azure Active Directory using data from Visual Studio Code 26 | - AzureCliCredential - Authentication to Azure Active Directory using Azure CLI to obtain an access token 27 | 28 | See also: [Using Active Directory Default authentication](https://docs.microsoft.com/en-us/sql/connect/ado-net/sql/azure-active-directory-authentication?view=sql-server-ver15#using-active-directory-default-authentication) 29 | 30 | ## Managed Identity Configuration with DatabaseTarget 31 | 32 | Remember to setup the DbProvider for the DatabaseTarget to use Microsoft SqlConnection, and also remember to add the matching nuget-package. 33 | 34 | ### Syntax 35 | ```xml 36 | 37 | 38 | 39 | 40 | 41 | 42 | Microsoft.Data.SqlClient.SqlConnection, Microsoft.Data.SqlClient 43 | 44 | 45 | 46 | ``` 47 | 48 | ```c# 49 | NLog.GlobalDiagnosticsContext.Set("DatabaseHostSuffix", $"https://{DatabaseHostSuffix}/"); 50 | NLog.LogManager.LoadConfiguration("nlog.config"); 51 | ``` 52 | 53 | ### Parameters 54 | 55 | _ResourceName_ - AzureServiceTokenProvider Resource (Ex. https://management.azure.com or https://database.windows.net/ or https://storage.azure.com/). [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 56 | 57 | _TenantId_ - AzureServiceTokenProvider TenantId (or directory Id) of your Azure Active Directory. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Optional. 58 | 59 | _ConnectionString_ - AzureServiceTokenProvider ConnectionString [Layout](https://github.com/NLog/NLog/wiki/Layouts) Optional. 60 | 61 | _AzureAdInstance_ - AzureServiceTokenProvider AzureAdInstance [Layout](https://github.com/NLog/NLog/wiki/Layouts) Optional. -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureBlobStorage.Tests/BlobStorageTargetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NLog.Targets; 5 | using Xunit; 6 | 7 | namespace NLog.Extensions.AzureBlobStorage.Tests 8 | { 9 | public class BlobStorageTargetTest 10 | { 11 | [Fact] 12 | public void SingleLogEventTest() 13 | { 14 | var logFactory = new LogFactory(); 15 | var logConfig = new Config.LoggingConfiguration(logFactory); 16 | logConfig.Variables["ConnectionString"] = nameof(BlobStorageTargetTest); 17 | var cloudBlobService = new CloudBlobServiceMock(); 18 | var blobStorageTarget = new BlobStorageTarget(cloudBlobService); 19 | blobStorageTarget.ConnectionString = "${var:ConnectionString}"; 20 | blobStorageTarget.Container = "${level}"; 21 | blobStorageTarget.BlobName = "${logger}"; 22 | blobStorageTarget.Layout = "${message}"; 23 | blobStorageTarget.BlobTags.Add(new TargetPropertyWithContext() { Name = "MyTag", Layout = "MyTagValue" }); 24 | blobStorageTarget.BlobMetadata.Add(new TargetPropertyWithContext() { Name = "MyMetadata", Layout = "MyMetadataValue" }); 25 | logConfig.AddRuleForAllLevels(blobStorageTarget); 26 | logFactory.Configuration = logConfig; 27 | logFactory.GetLogger("Test").Info("Hello World"); 28 | logFactory.Flush(); 29 | Assert.Equal(nameof(BlobStorageTargetTest), cloudBlobService.ConnectionString); 30 | Assert.Single(cloudBlobService.BlobMetadata); 31 | Assert.Contains(new KeyValuePair("MyMetadata", "MyMetadataValue"), cloudBlobService.BlobMetadata); 32 | Assert.Single(cloudBlobService.BlobTags); 33 | Assert.Contains(new KeyValuePair("MyTag", "MyTagValue"), cloudBlobService.BlobTags); 34 | Assert.Single(cloudBlobService.AppendBlob); // One partition 35 | Assert.Equal("Hello World" + System.Environment.NewLine, cloudBlobService.PeekLastAppendBlob("info", "Test")); 36 | } 37 | 38 | [Fact] 39 | public void MultiplePartitionKeysTest() 40 | { 41 | var logFactory = new LogFactory(); 42 | var logConfig = new Config.LoggingConfiguration(logFactory); 43 | logConfig.Variables["ConnectionString"] = nameof(BlobStorageTargetTest); 44 | var cloudBlobService = new CloudBlobServiceMock(); 45 | var blobStorageTarget = new BlobStorageTarget(cloudBlobService); 46 | blobStorageTarget.ConnectionString = "${var:ConnectionString}"; 47 | blobStorageTarget.Container = "${level}"; 48 | blobStorageTarget.BlobName = "${logger}"; 49 | blobStorageTarget.Layout = "${message}"; 50 | logConfig.AddRuleForAllLevels(blobStorageTarget); 51 | logFactory.Configuration = logConfig; 52 | for (int i = 0; i < 50; ++i) 53 | { 54 | logFactory.GetLogger("Test1").Info("Hello"); 55 | logFactory.GetLogger("Test2").Debug("Goodbye"); 56 | } 57 | logFactory.Flush(); 58 | Assert.Equal(2, cloudBlobService.AppendBlob.Count); // Two partitions 59 | Assert.NotEqual(string.Empty, cloudBlobService.PeekLastAppendBlob("info", "Test1")); 60 | Assert.NotEqual(string.Empty, cloudBlobService.PeekLastAppendBlob("debug", "Test2")); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DEPRECATION_PROCESS.md: -------------------------------------------------------------------------------- 1 | # Deprecation and Removal Playbook 2 | 3 | Practical steps to deprecate and remove a package (while keeping history) without letting vulnerable code stay on the default branch or get republished. 4 | 5 | ## Steps 6 | 7 | 1. **Declare deprecation** 8 | - Open an issue/PR stating the reason (vulnerability/abandonment) and affected package name. 9 | - Add a loud banner to the package README and a short note in the root README pointing to the safer alternative. 10 | - Add/update `DEPRECATED.md` with the status and migration guidance. 11 | 12 | 2. **Stop distribution** 13 | - Remove the project from the solution and CI pack/test pipelines so it cannot be built or packed. 14 | - Unlist all NuGet versions, or publish a final version with release notes that say "deprecated, insecure, unsupported" and link to the alternative. 15 | - Verify no other packages in the repo reference it (remove references or add a compile-time `#error` guard if needed). 16 | 17 | 3. **Clean the default branch for scanners** 18 | - 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. 19 | - Keep a brief note in the root README so users understand it was intentionally removed. 20 | 21 | 4. **Preserve history without branch sprawl** 22 | - Tag the last commit that still contained the code (e.g., `archive/-YYYY-MM-DD`). 23 | 24 | 5. **Security and comms** 25 | - If the risk is security-related, add a short SECURITY/Advisory note: status = won't fix, remediation = use alternative, scope of impact. 26 | - Optionally pin the advisory in the repo and link it from the package README placeholder. 27 | 28 | 6. **Validate** 29 | - Run `dotnet build` and targeted tests to confirm removal did not break supported packages. 30 | - Confirm CI pack/test steps skip the removed package. 31 | 32 | ## Artifacts to touch (typical) 33 | 34 | - Package README: banner + deprecation note or placeholder. 35 | - Root `README.md`: short note and link to alternative. 36 | - `DEPRECATED.md`: status and guidance. 37 | - Solution file and CI config: remove project, pack, and test entries. 38 | - Optional: SECURITY/advisory file with "won't fix" language. 39 | 40 | ## Templates 41 | 42 | **Placeholder README snippet (in the removed package folder):** 43 | 44 | ```markdown 45 | # (removed) 46 | 47 | 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. 48 | 49 | Last code version is preserved at tag: archive/-YYYY-MM-DD. 50 | ``` 51 | 52 | **Release notes snippet for the final/last package version:** 53 | 54 | ```text 55 | Deprecated and insecure. This package is unmaintained and contains known vulnerabilities. Do not use. Migrate to . 56 | ``` 57 | 58 | ## Quick command hints 59 | 60 | - Tag the last commit before removal: 61 | 62 | ```sh 63 | git tag archive/-YYYY-MM-DD 64 | ``` 65 | 66 | 67 | - Remove a project from the solution (example): 68 | 69 | ```sh 70 | dotnet sln src/NLog.Extensions.AzureStorage.sln remove src//.csproj 71 | ``` 72 | 73 | ## Checklist for each deprecation 74 | 75 | - [ ] Banner in package README + root README note 76 | - [ ] Solution/CI pack/test entries removed 77 | - [ ] NuGet versions unlisted or final deprecated version published with clear notes 78 | - [ ] Placeholder README in default branch, code removed 79 | - [ ] Tag created for last code commit (and archive branch if required) 80 | - [ ] Advisory/SECURITY note added when security-driven 81 | - [ ] Build/tests rerun to verify unaffected packages 82 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureStorage/ProxyHelpers.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core.Pipeline; 2 | using System.Net; 3 | using System.Net.Http; 4 | 5 | namespace NLog.Extensions.AzureBlobStorage 6 | { 7 | /// 8 | /// Contains all settings to use to connect through a proxy server 9 | /// 10 | public class ProxySettings 11 | { 12 | /// 13 | /// bypasses any proxy server that may have been configured 14 | /// 15 | public bool NoProxy { get; set; } 16 | 17 | /// 18 | /// address of the proxy server (including port number) 19 | /// 20 | public string Address { get; set; } 21 | 22 | /// 23 | /// login to the proxy server 24 | /// 25 | public string Login { get; set; } 26 | 27 | /// 28 | /// Password to use for the proxy server. 29 | /// 30 | public string Password { get; set; } 31 | 32 | /// 33 | /// If this value is set to true, the default credentials for the proxy 34 | /// 35 | public bool UseDefaultCredentials { get; set; } 36 | 37 | /// 38 | /// Determines whether a custom proxy is required for the given settings 39 | /// 40 | /// if a custom proxy is required; otherwise, . 41 | public bool RequiresManualProxyConfiguration => !string.IsNullOrEmpty(Address) || NoProxy || (!string.IsNullOrEmpty(Login) && !string.IsNullOrEmpty(Password)) || UseDefaultCredentials; 42 | 43 | /// 44 | /// creates a custom HttpPipelineTransport to be used as for storage targets 45 | /// 46 | /// 47 | public HttpClientTransport CreateHttpClientTransport() 48 | { 49 | if (RequiresManualProxyConfiguration) 50 | { 51 | var handler = new HttpClientHandler 52 | { 53 | UseProxy = !NoProxy, 54 | }; 55 | 56 | if (NoProxy) 57 | { 58 | handler.Proxy = null; 59 | } 60 | else if (!string.IsNullOrEmpty(Address)) 61 | { 62 | handler.Proxy = CreateWebProxy(); 63 | } 64 | else 65 | { 66 | if (UseDefaultCredentials) 67 | handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; 68 | else if (!string.IsNullOrEmpty(Login) && !string.IsNullOrEmpty(Password)) 69 | handler.DefaultProxyCredentials = new NetworkCredential(Login, Password); 70 | } 71 | 72 | return new HttpClientTransport(new HttpClient(handler)); 73 | } 74 | 75 | return null; 76 | } 77 | 78 | /// 79 | /// Creates new WebProxy-object based on the configured proxy-options. 80 | /// 81 | public IWebProxy CreateWebProxy(IWebProxy defaultProxy = null) 82 | { 83 | if (NoProxy) 84 | return null; 85 | if (string.IsNullOrEmpty(Address)) 86 | return defaultProxy; 87 | 88 | var proxy = new WebProxy(Address, BypassOnLocal: true); 89 | if (UseDefaultCredentials) 90 | proxy.Credentials = CredentialCache.DefaultCredentials; 91 | else if (!string.IsNullOrEmpty(Login) && !string.IsNullOrEmpty(Password)) 92 | proxy.Credentials = new NetworkCredential(Login, Password); 93 | return proxy; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureServiceBus.Tests/ServiceBusTargetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NLog; 4 | using NLog.Targets; 5 | using Xunit; 6 | 7 | namespace NLog.Extensions.AzureServiceBus.Test 8 | { 9 | public class ServiceBusTargetTest 10 | { 11 | [Fact] 12 | public void SingleLogEventTest() 13 | { 14 | var logFactory = new LogFactory(); 15 | var logConfig = new Config.LoggingConfiguration(logFactory); 16 | logConfig.Variables["ConnectionString"] = nameof(ServiceBusTargetTest); 17 | var eventHubService = new ServiceBusMock(); 18 | var serviceBusTarget = new ServiceBusTarget(eventHubService); 19 | serviceBusTarget.ConnectionString = "${var:ConnectionString}"; 20 | serviceBusTarget.QueueName = "${shortdate}"; 21 | serviceBusTarget.PartitionKey = "${logger}"; 22 | serviceBusTarget.Layout = "${message}"; 23 | logConfig.AddRuleForAllLevels(serviceBusTarget); 24 | logFactory.Configuration = logConfig; 25 | logFactory.GetLogger("Test").Info("Hello World"); 26 | logFactory.Flush(); 27 | Assert.Equal(nameof(ServiceBusTargetTest), eventHubService.ConnectionString); 28 | Assert.Single( eventHubService.MessageDataSent); // One partition 29 | Assert.Equal("Hello World", eventHubService.PeekLastMessageBody()); 30 | } 31 | 32 | [Fact] 33 | public void MultiplePartitionKeysTest() 34 | { 35 | var logFactory = new LogFactory(); 36 | var logConfig = new Config.LoggingConfiguration(logFactory); 37 | var serviceBusMock = new ServiceBusMock(); 38 | var serviceBusTarget = new ServiceBusTarget(serviceBusMock); 39 | serviceBusTarget.ConnectionString = "LocalEventHub"; 40 | serviceBusTarget.QueueName = "${shortdate}"; 41 | serviceBusTarget.PartitionKey = "${logger}"; 42 | serviceBusTarget.Layout = "${message}"; 43 | logConfig.AddRuleForAllLevels(serviceBusTarget); 44 | logFactory.Configuration = logConfig; 45 | for (int i = 0; i < 50; ++i) 46 | { 47 | logFactory.GetLogger("Test1").Info("Hello"); 48 | logFactory.GetLogger("Test2").Debug("Goodbye"); 49 | } 50 | logFactory.Flush(); 51 | Assert.Equal(2, serviceBusMock.MessageDataSent.Count); // Two partitions 52 | Assert.Equal(50, serviceBusMock.MessageDataSent[0].Count); 53 | Assert.Equal(50, serviceBusMock.MessageDataSent[1].Count); 54 | } 55 | 56 | [Fact] 57 | public void ServiceBusUserPropertiesTest() 58 | { 59 | var logFactory = new LogFactory(); 60 | var logConfig = new Config.LoggingConfiguration(logFactory); 61 | var serviceBusMock = new ServiceBusMock(); 62 | var serviceBusTarget = new ServiceBusTarget(serviceBusMock); 63 | serviceBusTarget.ConnectionString = "LocalEventHub"; 64 | serviceBusTarget.QueueName = "${shortdate}"; 65 | serviceBusTarget.PartitionKey = "${logger}"; 66 | serviceBusTarget.Layout = "${message}"; 67 | serviceBusTarget.ContextProperties.Add(new Targets.TargetPropertyWithContext("Level", "${level}")); 68 | logConfig.AddRuleForAllLevels(serviceBusTarget); 69 | logFactory.Configuration = logConfig; 70 | logFactory.GetLogger("Test").Info("Hello"); 71 | logFactory.Flush(); 72 | Assert.Single(serviceBusMock.MessageDataSent); 73 | Assert.Equal("Hello", serviceBusMock.PeekLastMessageBody()); 74 | Assert.Single(serviceBusMock.MessageDataSent.First().First().ApplicationProperties); 75 | Assert.Equal(LogLevel.Info.ToString(), serviceBusMock.MessageDataSent.First().First().ApplicationProperties["Level"]); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventGrid/README.md: -------------------------------------------------------------------------------- 1 | # Azure Event Grid 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureEventGrid** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureEventGrid.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureEventGrid/) | Azure Event Grid | 6 | 7 | ## Queue Configuration 8 | 9 | ### Syntax 10 | ```xml 11 | 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | ``` 29 | 30 | ### General Options 31 | 32 | _name_ - Name of the target. 33 | 34 | _layout_ - Event data-payload. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 35 | 36 | _topic_ - Topic EndPoint Uri. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 37 | 38 | _cloudEventSource_ - Only for [CloudEvent](https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema)-format (Recommended event format) and specify context where event occurred. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 39 | 40 | _gridEventSubject_ - Only for [GridEvent](https://learn.microsoft.com/en-us/azure/event-grid/event-schema)-format and specify resource path relative to the topic path. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 41 | 42 | _eventType_ - Type of the event. Ex. "Contoso.Items.ItemReceived". [Layout](https://github.com/NLog/NLog/wiki/Layouts) 43 | 44 | _contentType_ - Content type of the data-payload. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 45 | 46 | _dataFormat_ - Format of the data-payload (Binary / Json). Default Binary. `String` 47 | 48 | _dataSchema_ - Schema version of the data-payload. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 49 | 50 | ### Authentication Options 51 | 52 | _managedIdentityClientId_ - clientId for `ManagedIdentityClientId` on `DefaultAzureCredentialOptions`. Requires `serviceUri`. 53 | 54 | _managedIdentityResourceId_ - resourceId for `ManagedIdentityResourceId` on `DefaultAzureCredentialOptions`, do not use together with `ManagedIdentityClientId`. Requires `serviceUri`. 55 | 56 | _tenantIdentity_ - tenantId for `DefaultAzureCredentialOptions`. Requires `serviceUri`. 57 | 58 | _sharedAccessSignature_ - Access signature for `AzureSasCredential` authentication. Requires `serviceUri`. 59 | 60 | _accessKey_ - Key for `AzureKeyCredential` authentication. Requires `serviceUri`. 61 | 62 | _clientAuthId_ - clientId for `ClientSecretCredential` OAuth2 authentication. Requires `tenantIdentity` and `clientAuthSecret`. 63 | 64 | _clientAuthSecret_ - clientSecret for `ClientSecretCredential` OAuth2 authentication. Requires `tenantIdentity` and `clientAuthId`. 65 | 66 | When `DefaultAzureCredential` is used for Azure Identity, then it can also resolve from environment variables: 67 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 68 | - `AZURE_TENANT_ID` - For TenantId 69 | 70 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 71 | 72 | ### Proxy Options 73 | 74 | _noProxy_ - Bypasses any system proxy and proxy in `ProxyAddress` when set to `true`. 75 | 76 | _proxyAddress_ - Address of the proxy server to use (e.g. http://proxyserver:8080). 77 | 78 | _proxyLogin_ - Login to use for the proxy server. Requires `proxyPassword`. 79 | 80 | _proxyPassword_ - Password to use for the proxy server. Requires `proxyLogin`. 81 | 82 | _useDefaultCredentialsForProxy_ - Uses the default credentials (`System.Net.CredentialCache.DefaultCredentials`) for the proxy server. Take precedence over `proxyLogin` and `proxyPassword` when set to `true`. 83 | 84 | ### Retry Policy 85 | 86 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 87 | 88 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 89 | 90 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 91 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | # Cancel in-progress runs for the same branch/PR 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | # Minimal permissions - security best practice 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | build: 20 | runs-on: windows-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Setup .NET 10.0 26 | uses: actions/setup-dotnet@v4 27 | with: 28 | dotnet-version: '10.0.x' 29 | 30 | - name: Setup .NET 8.0 31 | uses: actions/setup-dotnet@v4 32 | with: 33 | dotnet-version: '8.0.x' 34 | 35 | # Cache NuGet packages to speed up builds 36 | - name: Cache NuGet packages 37 | uses: actions/cache@v4 38 | with: 39 | path: ~/.nuget/packages 40 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} 41 | restore-keys: | 42 | ${{ runner.os }}-nuget- 43 | 44 | - name: Display .NET version 45 | run: dotnet --version 46 | 47 | - name: Restore and Build 48 | run: dotnet build src\NLog.Extensions.AzureStorage.sln -c Release --verbosity minimal 49 | 50 | - name: Upload build artifacts for matrix jobs 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: build-output 54 | path: | 55 | src\**\bin\Release\** 56 | src\**\obj\** 57 | test\**\bin\Release\** 58 | test\**\obj\** 59 | retention-days: 1 60 | 61 | pack: 62 | needs: build 63 | runs-on: windows-latest 64 | strategy: 65 | matrix: 66 | project: 67 | - NLog.Extensions.AzureBlobStorage 68 | - NLog.Extensions.AzureDataTables 69 | - NLog.Extensions.AzureQueueStorage 70 | - NLog.Extensions.AzureEventGrid 71 | - NLog.Extensions.AzureEventHub 72 | - NLog.Extensions.AzureServiceBus 73 | 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | - name: Setup .NET 78 | uses: actions/setup-dotnet@v4 79 | with: 80 | dotnet-version: | 81 | 8.0.x 82 | 10.0.x 83 | 84 | - name: Cache NuGet packages 85 | uses: actions/cache@v4 86 | with: 87 | path: ~/.nuget/packages 88 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} 89 | restore-keys: | 90 | ${{ runner.os }}-nuget- 91 | 92 | - name: Download build artifacts 93 | uses: actions/download-artifact@v4 94 | with: 95 | name: build-output 96 | 97 | - name: Restore ${{ matrix.project }} 98 | run: dotnet restore src\${{ matrix.project }} 99 | 100 | - name: Pack ${{ matrix.project }} 101 | run: dotnet pack src\${{ matrix.project }} -c Release --no-build -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -p:ContinuousIntegrationBuild=true -p:EmbedUntrackedSources=true -p:PublishRepositoryUrl=true --verbosity minimal 102 | 103 | - name: Upload package 104 | uses: actions/upload-artifact@v4 105 | with: 106 | name: package-${{ matrix.project }} 107 | path: | 108 | src\${{ matrix.project }}\bin\Release\*.nupkg 109 | src\${{ matrix.project }}\bin\Release\*.snupkg 110 | retention-days: 90 111 | 112 | test: 113 | needs: build 114 | runs-on: windows-latest 115 | strategy: 116 | matrix: 117 | project: 118 | - NLog.Extensions.AzureBlobStorage.Tests 119 | - NLog.Extensions.AzureDataTables.Tests 120 | - NLog.Extensions.AzureQueueStorage.Tests 121 | - NLog.Extensions.AzureEventGrid.Tests 122 | - NLog.Extensions.AzureEventHub.Tests 123 | - NLog.Extensions.AzureServiceBus.Tests 124 | 125 | steps: 126 | - uses: actions/checkout@v4 127 | 128 | - name: Setup .NET 129 | uses: actions/setup-dotnet@v4 130 | with: 131 | dotnet-version: | 132 | 8.0.x 133 | 10.0.x 134 | 135 | - name: Cache NuGet packages 136 | uses: actions/cache@v4 137 | with: 138 | path: ~/.nuget/packages 139 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} 140 | restore-keys: | 141 | ${{ runner.os }}-nuget- 142 | 143 | - name: Download build artifacts 144 | uses: actions/download-artifact@v4 145 | with: 146 | name: build-output 147 | 148 | - name: Test ${{ matrix.project }} 149 | run: dotnet test test\${{ matrix.project }} /p:Configuration=Release --verbosity minimal --no-build 150 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureDataTables.Tests/DataTablesTargetTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Azure.Data.Tables; 4 | using NLog.Targets; 5 | using Xunit; 6 | 7 | namespace NLog.Extensions.AzureTableStorage.Tests 8 | { 9 | public class DataTablesTargetTests 10 | { 11 | [Fact] 12 | public void SingleLogEventTest() 13 | { 14 | var logFactory = new LogFactory(); 15 | var logConfig = new Config.LoggingConfiguration(logFactory); 16 | logConfig.Variables["ConnectionString"] = nameof(DataTablesTargetTests); 17 | var cloudTableService = new CloudTableServiceMock(); 18 | var queueStorageTarget = new DataTablesTarget(cloudTableService); 19 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 20 | queueStorageTarget.TableName = "${logger}"; 21 | queueStorageTarget.Layout = "${message}"; 22 | logConfig.AddRuleForAllLevels(queueStorageTarget); 23 | logFactory.Configuration = logConfig; 24 | logFactory.GetLogger("test").Info("Hello World"); 25 | logFactory.Flush(); 26 | Assert.Equal(nameof(DataTablesTargetTests), cloudTableService.ConnectionString); 27 | Assert.Single(cloudTableService.BatchExecuted); // One queue 28 | Assert.Equal("test", cloudTableService.PeekLastAdded("test").First().PartitionKey); 29 | } 30 | 31 | [Fact] 32 | public void MultiplePartitionKeysTest() 33 | { 34 | var logFactory = new LogFactory(); 35 | var logConfig = new Config.LoggingConfiguration(logFactory); 36 | logConfig.Variables["ConnectionString"] = nameof(DataTablesTargetTests); 37 | var cloudTableService = new CloudTableServiceMock(); 38 | var queueStorageTarget = new DataTablesTarget(cloudTableService); 39 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 40 | queueStorageTarget.TableName = "${logger}"; 41 | queueStorageTarget.Layout = "${message}"; 42 | logConfig.AddRuleForAllLevels(queueStorageTarget); 43 | logFactory.Configuration = logConfig; 44 | for (int i = 0; i < 50; ++i) 45 | { 46 | logFactory.GetLogger("Test1").Info("Hello"); 47 | logFactory.GetLogger("Test2").Debug("Goodbye"); 48 | } 49 | logFactory.Flush(); 50 | Assert.Equal(2, cloudTableService.BatchExecuted.Count); // Two partitions 51 | Assert.Equal(50, cloudTableService.PeekLastAdded("Test1").Count()); 52 | Assert.Equal(50, cloudTableService.PeekLastAdded("Test2").Count()); 53 | } 54 | 55 | [Fact] 56 | public void DynamicTableEntityTest() 57 | { 58 | var logFactory = new LogFactory(); 59 | var logConfig = new Config.LoggingConfiguration(logFactory); 60 | logConfig.Variables["ConnectionString"] = nameof(DataTablesTargetTests); 61 | var cloudTableService = new CloudTableServiceMock(); 62 | var queueStorageTarget = new DataTablesTarget(cloudTableService); 63 | queueStorageTarget.ContextProperties.Add(new TargetPropertyWithContext("ThreadId", "${threadid}")); 64 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 65 | queueStorageTarget.TableName = "${logger}"; 66 | queueStorageTarget.Layout = "${message}"; 67 | logConfig.AddRuleForAllLevels(queueStorageTarget); 68 | logFactory.Configuration = logConfig; 69 | logFactory.GetLogger("Test").Info("Hello"); 70 | logFactory.Flush(); 71 | Assert.Single(cloudTableService.BatchExecuted); 72 | var firstEntity = cloudTableService.PeekLastAdded("Test").Cast().First(); 73 | Assert.NotEqual(DateTime.MinValue, firstEntity["LogTimeStamp"]); 74 | Assert.Equal(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), firstEntity["ThreadId"].ToString()); 75 | } 76 | 77 | [Fact] 78 | public void DynamicTableEntityOverrideTimestampTest() 79 | { 80 | var logFactory = new LogFactory(); 81 | var logConfig = new Config.LoggingConfiguration(logFactory); 82 | logConfig.Variables["ConnectionString"] = nameof(DataTablesTargetTests); 83 | var cloudTableService = new CloudTableServiceMock(); 84 | var queueStorageTarget = new DataTablesTarget(cloudTableService); 85 | queueStorageTarget.ContextProperties.Add(new TargetPropertyWithContext("LogTimeStamp", "${shortdate}")); 86 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 87 | queueStorageTarget.TableName = "${logger}"; 88 | queueStorageTarget.Layout = "${message}"; 89 | logConfig.AddRuleForAllLevels(queueStorageTarget); 90 | logFactory.Configuration = logConfig; 91 | logFactory.GetLogger("Test").Info("Hello"); 92 | logFactory.Flush(); 93 | Assert.Single(cloudTableService.BatchExecuted); 94 | var firstEntity = cloudTableService.PeekLastAdded("Test").Cast().First(); 95 | Assert.Equal(DateTime.Now.Date.ToString("yyyy-MM-dd"), firstEntity["LogTimeStamp"]); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureCosmosTable.Tests/TableStorageTargetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.Azure.Cosmos.Table; 4 | using NLog.Targets; 5 | using Xunit; 6 | 7 | namespace NLog.Extensions.AzureTableStorage.Tests 8 | { 9 | public class TableStorageTargetTest 10 | { 11 | [Fact] 12 | public void SingleLogEventTest() 13 | { 14 | var logFactory = new LogFactory(); 15 | var logConfig = new Config.LoggingConfiguration(logFactory); 16 | logConfig.Variables["ConnectionString"] = nameof(TableStorageTargetTest); 17 | var cloudTableService = new CloudTableServiceMock(); 18 | var queueStorageTarget = new TableStorageTarget(cloudTableService); 19 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 20 | queueStorageTarget.TableName = "${logger}"; 21 | queueStorageTarget.Layout = "${message}"; 22 | logConfig.AddRuleForAllLevels(queueStorageTarget); 23 | logFactory.Configuration = logConfig; 24 | logFactory.GetLogger("test").Info("Hello World"); 25 | logFactory.Flush(); 26 | Assert.Equal(nameof(TableStorageTargetTest), cloudTableService.ConnectionString); 27 | Assert.Single(cloudTableService.BatchExecuted); // One queue 28 | Assert.Equal("test", cloudTableService.PeekLastAdded("test").First().PartitionKey); 29 | } 30 | 31 | [Fact] 32 | public void MultiplePartitionKeysTest() 33 | { 34 | var logFactory = new LogFactory(); 35 | var logConfig = new Config.LoggingConfiguration(logFactory); 36 | logConfig.Variables["ConnectionString"] = nameof(TableStorageTargetTest); 37 | var cloudTableService = new CloudTableServiceMock(); 38 | var queueStorageTarget = new TableStorageTarget(cloudTableService); 39 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 40 | queueStorageTarget.TableName = "${logger}"; 41 | queueStorageTarget.Layout = "${message}"; 42 | logConfig.AddRuleForAllLevels(queueStorageTarget); 43 | logFactory.Configuration = logConfig; 44 | for (int i = 0; i < 50; ++i) 45 | { 46 | logFactory.GetLogger("Test1").Info("Hello"); 47 | logFactory.GetLogger("Test2").Debug("Goodbye"); 48 | } 49 | logFactory.Flush(); 50 | Assert.Equal(2, cloudTableService.BatchExecuted.Count); // Two partitions 51 | Assert.Equal(50, cloudTableService.PeekLastAdded("Test1").Count()); 52 | Assert.Equal(50, cloudTableService.PeekLastAdded("Test2").Count()); 53 | } 54 | 55 | [Fact] 56 | public void DynamicTableEntityTest() 57 | { 58 | var logFactory = new LogFactory(); 59 | var logConfig = new Config.LoggingConfiguration(logFactory); 60 | logConfig.Variables["ConnectionString"] = nameof(TableStorageTargetTest); 61 | var cloudTableService = new CloudTableServiceMock(); 62 | var queueStorageTarget = new TableStorageTarget(cloudTableService); 63 | queueStorageTarget.ContextProperties.Add(new TargetPropertyWithContext("ThreadId", "${threadid}")); 64 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 65 | queueStorageTarget.TableName = "${logger}"; 66 | queueStorageTarget.Layout = "${message}"; 67 | logConfig.AddRuleForAllLevels(queueStorageTarget); 68 | logFactory.Configuration = logConfig; 69 | logFactory.GetLogger("Test").Info("Hello"); 70 | logFactory.Flush(); 71 | Assert.Single(cloudTableService.BatchExecuted); 72 | var firstEntity = cloudTableService.PeekLastAdded("Test").Cast().First(); 73 | Assert.NotEqual(DateTime.MinValue, firstEntity["LogTimeStamp"].DateTime); 74 | Assert.Equal(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), firstEntity["ThreadId"].ToString()); 75 | } 76 | 77 | [Fact] 78 | public void DynamicTableEntityOverrideTimestampTest() 79 | { 80 | var logFactory = new LogFactory(); 81 | var logConfig = new Config.LoggingConfiguration(logFactory); 82 | logConfig.Variables["ConnectionString"] = nameof(TableStorageTargetTest); 83 | var cloudTableService = new CloudTableServiceMock(); 84 | var queueStorageTarget = new TableStorageTarget(cloudTableService); 85 | queueStorageTarget.ContextProperties.Add(new TargetPropertyWithContext("LogTimeStamp", "${shortdate}")); 86 | queueStorageTarget.ConnectionString = "${var:ConnectionString}"; 87 | queueStorageTarget.TableName = "${logger}"; 88 | queueStorageTarget.Layout = "${message}"; 89 | logConfig.AddRuleForAllLevels(queueStorageTarget); 90 | logFactory.Configuration = logConfig; 91 | logFactory.GetLogger("Test").Info("Hello"); 92 | logFactory.Flush(); 93 | Assert.Single(cloudTableService.BatchExecuted); 94 | var firstEntity = cloudTableService.PeekLastAdded("Test").Cast().First(); 95 | Assert.Equal(DateTime.Now.Date.ToString("yyyy-MM-dd"), firstEntity["LogTimeStamp"].StringValue); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureAccessToken.Tests/AccessTokenLayoutRendererTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace NLog.Extensions.AzureAccessToken.Tests 7 | { 8 | public class AccessTokenLayoutRendererTests 9 | { 10 | [Fact] 11 | public void SingleLogEventTest() 12 | { 13 | NLog.LogManager.ThrowConfigExceptions = true; 14 | NLog.LayoutRenderers.LayoutRenderer.Register("AzureAccessToken", typeof(AccessTokenLayoutRenderer)); 15 | var logFactory = new LogFactory(); 16 | var logConfig = new Config.LoggingConfiguration(logFactory); 17 | var memTarget = new NLog.Targets.MemoryTarget("Test") { Layout = "${AzureAccessToken:ResourceName=https\\://database.windows.net/}" }; 18 | logConfig.AddRuleForAllLevels(memTarget); 19 | logFactory.Configuration = logConfig; 20 | logFactory.GetLogger("test").Info("Hello World"); 21 | Assert.Single(memTarget.Logs); // One queue 22 | logFactory.Configuration = null; 23 | Assert.True(WaitForTokenTimersStoppedAndGarbageCollected()); 24 | } 25 | 26 | [Fact] 27 | public void SingleRenderRefreshTokenProviderTest() 28 | { 29 | var layout = CreateAccessTokenLayoutRenderer("TestResource", null, TimeSpan.FromMilliseconds(20)); 30 | var uniqueTokens = new HashSet(); 31 | for (int i = 0; i < 5000; ++i) 32 | { 33 | uniqueTokens.Add(layout.Render(LogEventInfo.CreateNullEvent())); 34 | if (uniqueTokens.Count > 1) 35 | break; 36 | System.Threading.Thread.Sleep(1); 37 | } 38 | Assert.NotEmpty(uniqueTokens); 39 | Assert.NotEqual(1, uniqueTokens.Count); 40 | layout.ResetTokenRefresher(); 41 | Assert.True(WaitForTokenTimersStoppedAndGarbageCollected()); 42 | } 43 | 44 | [Fact] 45 | public void TwoEqualRendersReusesSameTokenProvider() 46 | { 47 | AccessTokenLayoutRenderer.AccessTokenProviders.Clear(); 48 | var layout1 = CreateAccessTokenLayoutRenderer(); 49 | var layout2 = CreateAccessTokenLayoutRenderer(); 50 | Assert.False(AccessTokenTimersStoppedAndGarbageCollected()); 51 | Assert.Single(AccessTokenLayoutRenderer.AccessTokenProviders); 52 | 53 | var result1 = layout1.Render(LogEventInfo.CreateNullEvent()); 54 | var result2 = layout2.Render(LogEventInfo.CreateNullEvent()); 55 | Assert.NotEmpty(result1); 56 | Assert.Equal(result1, result2); 57 | 58 | layout1.ResetTokenRefresher(); 59 | Assert.False(AccessTokenTimersStoppedAndGarbageCollected()); 60 | layout2.ResetTokenRefresher(); 61 | Assert.True(WaitForTokenTimersStoppedAndGarbageCollected()); 62 | } 63 | 64 | [Fact] 65 | public void TwoDifferentRendersNotReusingSameTokenProvider() 66 | { 67 | AccessTokenLayoutRenderer.AccessTokenProviders.Clear(); 68 | var layout1 = CreateAccessTokenLayoutRenderer("TestResource1"); 69 | var layout2 = CreateAccessTokenLayoutRenderer("TestResource2"); 70 | Assert.False(AccessTokenTimersStoppedAndGarbageCollected()); 71 | Assert.Equal(2, AccessTokenLayoutRenderer.AccessTokenProviders.Count); 72 | 73 | var result1 = layout1.Render(LogEventInfo.CreateNullEvent()); 74 | var result2 = layout2.Render(LogEventInfo.CreateNullEvent()); 75 | Assert.NotEmpty(result1); 76 | Assert.NotEqual(result1, result2); 77 | 78 | layout1.ResetTokenRefresher(); 79 | Assert.False(AccessTokenTimersStoppedAndGarbageCollected()); 80 | layout2.ResetTokenRefresher(); 81 | Assert.True(WaitForTokenTimersStoppedAndGarbageCollected()); 82 | } 83 | 84 | private static AccessTokenLayoutRenderer CreateAccessTokenLayoutRenderer(string resourceName = "TestResource", string accessToken = null, TimeSpan? refreshInterval = null) 85 | { 86 | NLog.LogManager.ThrowConfigExceptions = true; 87 | var layout = new AccessTokenLayoutRenderer((connectionString, azureAdInstance) => new AzureServiceTokenProviderMock(accessToken, refreshInterval ?? TimeSpan.FromMinutes(10))) { ResourceName = resourceName }; 88 | layout.Render(LogEventInfo.CreateNullEvent()); // Initializes automatically 89 | Assert.False(AccessTokenTimersStoppedAndGarbageCollected()); 90 | return layout; 91 | } 92 | 93 | private static bool WaitForTokenTimersStoppedAndGarbageCollected() 94 | { 95 | for (int i = 0; i < 500; ++i) 96 | { 97 | GC.Collect(2, GCCollectionMode.Forced); 98 | System.Threading.Thread.Sleep(10); 99 | if (AccessTokenTimersStoppedAndGarbageCollected()) 100 | return true; 101 | } 102 | 103 | return false; 104 | } 105 | 106 | private static bool AccessTokenTimersStoppedAndGarbageCollected() 107 | { 108 | return AccessTokenLayoutRenderer.AccessTokenProviders.All(token => !token.Value.TryGetTarget(out var provider)); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /src/nuget.exe 263 | actionlint 264 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureEventHub.Tests/EventHubTargetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NLog; 4 | using NLog.Targets; 5 | using Xunit; 6 | 7 | namespace NLog.Extensions.AzureEventHub.Test 8 | { 9 | public class EventHubTargetTest 10 | { 11 | [Fact] 12 | public void SingleLogEventTest() 13 | { 14 | var logFactory = new LogFactory(); 15 | var logConfig = new Config.LoggingConfiguration(logFactory); 16 | logConfig.Variables["ConnectionString"] = nameof(EventHubTargetTest); 17 | var eventHubService = new EventHubServiceMock(); 18 | var eventHubTarget = new EventHubTarget(eventHubService); 19 | eventHubTarget.ConnectionString = "${var:ConnectionString}"; 20 | eventHubTarget.EventHubName = "${shortdate}"; 21 | eventHubTarget.PartitionKey = "${logger}"; 22 | eventHubTarget.Layout = "${message}"; 23 | logConfig.AddRuleForAllLevels(eventHubTarget); 24 | logFactory.Configuration = logConfig; 25 | logFactory.GetLogger("Test").Info("Hello World"); 26 | logFactory.Flush(); 27 | Assert.Equal(nameof(EventHubTargetTest), eventHubService.ConnectionString); 28 | Assert.Single( eventHubService.EventDataSent); // One partition 29 | Assert.Equal("Hello World", eventHubService.PeekLastSent("Test")); 30 | } 31 | 32 | [Fact] 33 | public void MultiplePartitionKeysTest() 34 | { 35 | var logFactory = new LogFactory(); 36 | var logConfig = new Config.LoggingConfiguration(logFactory); 37 | var eventHubService = new EventHubServiceMock(); 38 | var eventHubTarget = new EventHubTarget(eventHubService); 39 | eventHubTarget.ConnectionString = "LocalEventHub"; 40 | eventHubTarget.PartitionKey = "${logger}"; 41 | eventHubTarget.Layout = "${message}"; 42 | logConfig.AddRuleForAllLevels(eventHubTarget); 43 | logFactory.Configuration = logConfig; 44 | for (int i = 0; i < 50; ++i) 45 | { 46 | logFactory.GetLogger("Test1").Info("Hello"); 47 | logFactory.GetLogger("Test2").Debug("Goodbye"); 48 | } 49 | logFactory.Flush(); 50 | Assert.Equal(2, eventHubService.EventDataSent.Count); // Two partitions 51 | Assert.Equal(50, eventHubService.EventDataSent["Test1"].Count); 52 | Assert.Equal(50, eventHubService.EventDataSent["Test2"].Count); 53 | } 54 | 55 | [Fact] 56 | public void EventDataPropertiesTest() 57 | { 58 | var logFactory = new LogFactory(); 59 | var logConfig = new Config.LoggingConfiguration(logFactory); 60 | var eventHubService = new EventHubServiceMock(); 61 | var eventHubTarget = new EventHubTarget(eventHubService); 62 | eventHubTarget.ConnectionString = "LocalEventHub"; 63 | eventHubTarget.PartitionKey = "${logger}"; 64 | eventHubTarget.Layout = "${message}"; 65 | eventHubTarget.ContextProperties.Add(new Targets.TargetPropertyWithContext("Level", "${level}")); 66 | logConfig.AddRuleForAllLevels(eventHubTarget); 67 | logFactory.Configuration = logConfig; 68 | logFactory.GetLogger("Test").Info("Hello"); 69 | logFactory.Flush(); 70 | Assert.Single(eventHubService.EventDataSent); 71 | Assert.Equal("Hello", eventHubService.PeekLastSent("Test")); 72 | Assert.Single(eventHubService.EventDataSent.First().Value.First().Properties); 73 | Assert.Equal(LogLevel.Info.ToString(), eventHubService.EventDataSent.First().Value.First().Properties["Level"]); 74 | } 75 | 76 | [Fact] 77 | public void EventDataBulkBigBatchSize() 78 | { 79 | var logFactory = new LogFactory(); 80 | var logConfig = new Config.LoggingConfiguration(logFactory); 81 | var eventHubService = new EventHubServiceMock(); 82 | var eventHubTarget = new EventHubTarget(eventHubService); 83 | eventHubTarget.OverflowAction = Targets.Wrappers.AsyncTargetWrapperOverflowAction.Grow; 84 | eventHubTarget.ConnectionString = "LocalEventHub"; 85 | eventHubTarget.PartitionKey = "${logger}"; 86 | eventHubTarget.Layout = "${message}"; 87 | eventHubTarget.TaskDelayMilliseconds = 200; 88 | eventHubTarget.BatchSize = 200; 89 | eventHubTarget.IncludeEventProperties = true; 90 | eventHubTarget.RetryCount = 1; 91 | logConfig.AddRuleForAllLevels(eventHubTarget); 92 | logFactory.Configuration = logConfig; 93 | var logger = logFactory.GetLogger(nameof(EventDataBulkBigBatchSize)); 94 | for (int i = 0; i < 11000; ++i) 95 | { 96 | if (i % 1000 == 0) 97 | { 98 | System.Threading.Thread.Sleep(1); 99 | } 100 | logger.Info("Hello {Counter}", i); 101 | } 102 | logFactory.Flush(); 103 | 104 | Assert.Single(eventHubService.EventDataSent); 105 | Assert.Equal(11000, eventHubService.EventDataSent.First().Value.Count); 106 | var previous = -1; 107 | foreach (var item in eventHubService.EventDataSent.First().Value) 108 | { 109 | Assert.True((int)item.Properties["Counter"] > previous, $"{(int)item.Properties["Counter"]} > {previous}"); 110 | previous = (int)item.Properties["Counter"]; 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureDataTables/NLogEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure; 3 | using Azure.Data.Tables; 4 | 5 | namespace NLog.Extensions.AzureStorage 6 | { 7 | /// 8 | /// Represents a log entry entity for Azure Table Storage. 9 | /// 10 | public class NLogEntity: ITableEntity 11 | { 12 | /// 13 | /// Gets or sets the partition key of the table entity. 14 | /// 15 | public string PartitionKey { get; set; } 16 | /// 17 | /// Gets or sets the row key of the table entity. 18 | /// 19 | public string RowKey { get; set; } 20 | /// 21 | /// Gets or sets the timestamp of the table entity. 22 | /// 23 | public DateTimeOffset? Timestamp { get; set; } 24 | /// 25 | /// Gets or sets the ETag of the table entity. 26 | /// 27 | public ETag ETag { get; set; } 28 | /// 29 | /// Gets or sets the formatted timestamp of the log event. 30 | /// 31 | public string LogTimeStamp { get; set; } 32 | /// 33 | /// Gets or sets the log level of the log event. 34 | /// 35 | public string Level { get; set; } 36 | /// 37 | /// Gets or sets the name of the logger that created the log event. 38 | /// 39 | public string LoggerName { get; set; } 40 | /// 41 | /// Gets or sets the log message. 42 | /// 43 | public string Message { get; set; } 44 | /// 45 | /// Gets or sets the exception information. 46 | /// 47 | public string Exception { get; set; } 48 | /// 49 | /// Gets or sets the inner exception information. 50 | /// 51 | public string InnerException { get; set; } 52 | /// 53 | /// Gets or sets the stack trace information. 54 | /// 55 | public string StackTrace { get; set; } 56 | /// 57 | /// Gets or sets the full formatted log message. 58 | /// 59 | public string FullMessage { get; set; } 60 | /// 61 | /// Gets or sets the name of the machine where the log event occurred. 62 | /// 63 | public string MachineName { get; set; } 64 | 65 | /// 66 | /// Initializes a new instance of the class with log event data. 67 | /// 68 | /// The log event information. 69 | /// The formatted layout message. 70 | /// The machine name. 71 | /// The partition key. 72 | /// The row key. 73 | /// The timestamp format string. 74 | public NLogEntity(LogEventInfo logEvent, string layoutMessage, string machineName, string partitionKey, string rowKey, string logTimeStampFormat) 75 | { 76 | FullMessage = TruncateWhenTooBig(layoutMessage); 77 | Level = logEvent.Level.Name; 78 | LoggerName = logEvent.LoggerName; 79 | Message = TruncateWhenTooBig(logEvent.Message); 80 | LogTimeStamp = logEvent.TimeStamp.ToString(logTimeStampFormat); 81 | MachineName = machineName; 82 | if(logEvent.Exception != null) 83 | { 84 | var exception = logEvent.Exception; 85 | var innerException = exception.InnerException; 86 | if (exception is AggregateException aggregateException) 87 | { 88 | if (aggregateException.InnerExceptions?.Count == 1 && !(aggregateException.InnerExceptions[0] is AggregateException)) 89 | { 90 | exception = aggregateException.InnerExceptions[0]; 91 | innerException = exception.InnerException; 92 | } 93 | else 94 | { 95 | var flatten = aggregateException.Flatten(); 96 | if (flatten.InnerExceptions?.Count == 1) 97 | { 98 | exception = flatten.InnerExceptions[0]; 99 | innerException = exception.InnerException; 100 | } 101 | else 102 | { 103 | innerException = flatten; 104 | } 105 | } 106 | } 107 | 108 | Exception = string.Concat(exception.Message, " - ", exception.GetType().ToString()); 109 | StackTrace = TruncateWhenTooBig(exception.StackTrace); 110 | if (innerException != null) 111 | { 112 | var innerExceptionText = innerException.ToString(); 113 | InnerException = TruncateWhenTooBig(innerExceptionText); 114 | } 115 | } 116 | RowKey = rowKey; 117 | PartitionKey = partitionKey; 118 | } 119 | 120 | private static string TruncateWhenTooBig(string stringValue) 121 | { 122 | return stringValue?.Length >= Targets.DataTablesTarget.ColumnStringValueMaxSize ? 123 | stringValue.Substring(0, Targets.DataTablesTarget.ColumnStringValueMaxSize - 1) : stringValue; 124 | } 125 | 126 | /// 127 | /// Initializes a new instance of the class. 128 | /// 129 | public NLogEntity() { } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureQueueStorage/README.md: -------------------------------------------------------------------------------- 1 | # Azure QueueStorage 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureQueueStorage** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureQueueStorage.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureQueueStorage/) | Azure Queue Storage | 6 | 7 | ## Queue Configuration 8 | 9 | ### Syntax 10 | ```xml 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | ``` 25 | 26 | ### General Options 27 | 28 | _name_ - Name of the target. 29 | 30 | _layout_ - Queue Message Text to be rendered. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 31 | 32 | _queueName_ - QueueName. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 33 | 34 | _connectionString_ - Azure Queue Storage connection string from your storage account. Required unless using `serviceUri`. 35 | 36 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is acquired from DefaultAzureCredential. 37 | 38 | _timeToLiveSeconds_ - Default Time-To-Live (TTL) for Queue messages in seconds (Optional) 39 | 40 | _timeToLiveDays_ - Default Time-To-Live (TTL) for Queue messages in days (Optional) 41 | 42 | ### Authentication Options 43 | 44 | _managedIdentityClientId_ - clientId for `ManagedIdentityClientId` on `DefaultAzureCredentialOptions`. Requires `serviceUri` 45 | 46 | _managedIdentityResourceId_ - resourceId for `ManagedIdentityResourceId` on `DefaultAzureCredentialOptions`, do not use together with `ManagedIdentityClientId`. Requires `serviceUri`. 47 | 48 | _tenantIdentity_ - tenantId for `DefaultAzureCredentialOptions`. Requires `serviceUri`. 49 | 50 | _sharedAccessSignature_ - Access signature for `AzureSasCredential` authentication. Requires `serviceUri`. 51 | 52 | _accountName_ - accountName for `StorageSharedKeyCredential` authentication. Requires `serviceUri` and `accessKey`. 53 | 54 | _accessKey_ - accountKey for `StorageSharedKeyCredential` authentication. Requires `serviceUri` and `accountName`. 55 | 56 | _clientAuthId_ - clientId for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`, `tenantIdentity` and `clientAuthSecret`. 57 | 58 | _clientAuthSecret_ - clientSecret for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`,`tenantIdentity` and `clientAuthId`. 59 | 60 | When using `ServiceUri` (Instead of ConnectionString) with `DefaultAzureCredential`, then Azure Identity can also resolve from environment variables: 61 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 62 | - `AZURE_TENANT_ID` - For TenantId 63 | 64 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 65 | 66 | ### Proxy Options 67 | 68 | _noProxy_ - Bypasses any system proxy and proxy in `ProxyAddress` when set to `true`. 69 | 70 | _proxyAddress_ - Address of the proxy server to use (e.g. http://proxyserver:8080). 71 | 72 | _proxyLogin_ - Login to use for the proxy server. Requires `proxyPassword`. 73 | 74 | _proxyPassword_ - Password to use for the proxy server. Requires `proxyLogin`. 75 | 76 | _useDefaultCredentialsForProxy_ - Uses the default credentials (`System.Net.CredentialCache.DefaultCredentials`) for the proxy server. Take precedence over `proxyLogin` and `proxyPassword` when set to `true`. 77 | 78 | ### Batching Policy 79 | 80 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 81 | 82 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 83 | 84 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 85 | 86 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 87 | 88 | ### Retry Policy 89 | 90 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 91 | 92 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 93 | 94 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 95 | 96 | ## Azure ConnectionString 97 | 98 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 99 | 100 | #### Lookup ConnectionString from appsettings.json 101 | 102 | > `connectionString="${configsetting:ConnectionStrings.AzureQueue}"` 103 | 104 | * Example appsettings.json on .NetCore: 105 | 106 | ```json 107 | { 108 | "ConnectionStrings": { 109 | "AzureQueue": "UseDevelopmentStorage=true;" 110 | } 111 | } 112 | ``` 113 | 114 | #### Lookup ConnectionString from app.config 115 | 116 | > `connectionString="${appsetting:ConnectionStrings.AzureQueue}"` 117 | 118 | * Example app.config on .NetFramework: 119 | 120 | ```xml 121 | 122 | 123 | 124 | 125 | 126 | ``` 127 | 128 | #### Lookup ConnectionString from environment-variable 129 | 130 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 131 | 132 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 133 | 134 | > `connectionString="${gdc:AzureQueueConnectionString}"` 135 | 136 | * Example code for setting GDC-value: 137 | 138 | ```c# 139 | NLog.GlobalDiagnosticsContext.Set("AzureQueueConnectionString", "UseDevelopmentStorage=true;"); 140 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NLog Targets for Azure Storage [![AppVeyor](https://img.shields.io/appveyor/ci/JDetmar/nlog-extensions-azurestorage.svg)](https://ci.appveyor.com/project/JDetmar/nlog-extensions-azurestorage) 2 | 3 | ![logo](logo64.png?raw=true) 4 | 5 | | Package Name | NuGet | Description | Documentation | 6 | | ------------------------------------- | :-------------------: | ----------- | ------------- | 7 | | **NLog.Extensions.AzureBlobStorage** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureBlobStorage.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureBlobStorage/) | Azure Blob Storage | [![](https://img.shields.io/badge/Readme-Docs-blue)](src/NLog.Extensions.AzureBlobStorage/README.md) | 8 | | **NLog.Extensions.AzureDataTables** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureDataTables.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureDataTables/) | Azure Table Storage or Azure CosmosDb Tables | [![](https://img.shields.io/badge/Readme-Docs-blue)](src/NLog.Extensions.AzureDataTables/README.md) | 9 | | **NLog.Extensions.AzureEventHub** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureEventHub.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureEventHub/) | Azure EventHubs | [![](https://img.shields.io/badge/Readme-Docs-blue)](src/NLog.Extensions.AzureEventHub/README.md) | 10 | | **NLog.Extensions.AzureEventGrid** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureEventGrid.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureEventGrid/) | Azure Event Grid | [![](https://img.shields.io/badge/Readme-Docs-blue)](src/NLog.Extensions.AzureEventGrid/README.md) | 11 | | **NLog.Extensions.AzureQueueStorage** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureQueueStorage.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureQueueStorage/) | Azure Queue Storage | [![](https://img.shields.io/badge/Readme-Docs-blue)](src/NLog.Extensions.AzureQueueStorage/README.md) | 12 | | **NLog.Extensions.AzureServiceBus** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureServiceBus.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureServiceBus/) | Azure Service Bus | [![](https://img.shields.io/badge/Readme-Docs-blue)](src/NLog.Extensions.AzureServiceBus/README.md) | 13 | | **NLog.Extensions.AzureAccessToken** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureAccessToken.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureAccessToken/) | Azure App Authentication Access Token for Managed Identity | [![](https://img.shields.io/badge/Readme-Docs-blue)](src/NLog.Extensions.AzureAccessToken/README.md) | 14 | 15 | Initially all NLog targets was bundled into a single nuget-package called [NLog.Extensions.AzureStorage](https://www.nuget.org/packages/NLog.Extensions.AzureStorage/). 16 | But Microsoft decided to discontinue [WindowsAzure.Storage](https://www.nuget.org/packages/WindowsAzure.Storage/) and split into multiple parts. 17 | Later Microsoft also decided to discontinue [Microsoft.Azure.DocumentDB](https://www.nuget.org/packages/Microsoft.Azure.DocumentDB.Core/) 18 | and so [NLog.Extensions.AzureCosmosTable](https://www.nuget.org/packages/NLog.Extensions.AzureCosmosTable/) also became deprecated. 19 | 20 | ## Sample Configuration 21 | 22 | ```xml 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 46 | 51 | 52 | 53 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | ``` 71 | 72 | ## License 73 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FJDetmar%2FNLog.Extensions.AzureStorage.svg?type=small)](https://app.fossa.io/projects/git%2Bgithub.com%2FJDetmar%2FNLog.Extensions.AzureStorage?ref=badge_small) 74 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NuGet 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | package: 7 | description: 'Package to publish' 8 | required: true 9 | type: choice 10 | options: 11 | - All 12 | - NLog.Extensions.AzureBlobStorage 13 | - NLog.Extensions.AzureDataTables 14 | - NLog.Extensions.AzureQueueStorage 15 | - NLog.Extensions.AzureEventGrid 16 | - NLog.Extensions.AzureEventHub 17 | - NLog.Extensions.AzureServiceBus 18 | run_id: 19 | description: 'CI workflow run ID to publish from (leave empty for latest successful run on master)' 20 | required: false 21 | type: string 22 | dry_run: 23 | description: 'Dry run (skip actual publish)' 24 | required: false 25 | type: boolean 26 | default: false 27 | 28 | permissions: 29 | contents: read 30 | actions: read # Required to download artifacts from other workflows 31 | id-token: write # Required for Trusted Publishing OIDC token 32 | 33 | jobs: 34 | publish: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - name: Get CI run ID 39 | id: get-run-id 40 | env: 41 | GH_TOKEN: ${{ github.token }} 42 | run: | 43 | if [ -n "${{ inputs.run_id }}" ]; then 44 | echo "run_id=${{ inputs.run_id }}" >> $GITHUB_OUTPUT 45 | echo "Using provided run ID: ${{ inputs.run_id }}" 46 | else 47 | # Get latest successful CI run on master 48 | RUN_ID=$(gh api repos/${{ github.repository }}/actions/workflows/ci.yml/runs --jq '[.workflow_runs[] | select(.conclusion == "success" and .head_branch == "master")] | .[0].id') 49 | if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then 50 | echo "ERROR: No successful CI runs found on master branch." >&2 51 | exit 1 52 | fi 53 | echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT 54 | echo "Using latest successful CI run: $RUN_ID" 55 | fi 56 | 57 | - name: Download NLog.Extensions.AzureBlobStorage 58 | if: inputs.package == 'All' || inputs.package == 'NLog.Extensions.AzureBlobStorage' 59 | uses: actions/download-artifact@v4 60 | with: 61 | name: package-NLog.Extensions.AzureBlobStorage 62 | path: packages 63 | github-token: ${{ github.token }} 64 | run-id: ${{ steps.get-run-id.outputs.run_id }} 65 | 66 | - name: Download NLog.Extensions.AzureDataTables 67 | if: inputs.package == 'All' || inputs.package == 'NLog.Extensions.AzureDataTables' 68 | uses: actions/download-artifact@v4 69 | with: 70 | name: package-NLog.Extensions.AzureDataTables 71 | path: packages 72 | github-token: ${{ github.token }} 73 | run-id: ${{ steps.get-run-id.outputs.run_id }} 74 | 75 | - name: Download NLog.Extensions.AzureQueueStorage 76 | if: inputs.package == 'All' || inputs.package == 'NLog.Extensions.AzureQueueStorage' 77 | uses: actions/download-artifact@v4 78 | with: 79 | name: package-NLog.Extensions.AzureQueueStorage 80 | path: packages 81 | github-token: ${{ github.token }} 82 | run-id: ${{ steps.get-run-id.outputs.run_id }} 83 | 84 | - name: Download NLog.Extensions.AzureEventGrid 85 | if: inputs.package == 'All' || inputs.package == 'NLog.Extensions.AzureEventGrid' 86 | uses: actions/download-artifact@v4 87 | with: 88 | name: package-NLog.Extensions.AzureEventGrid 89 | path: packages 90 | github-token: ${{ github.token }} 91 | run-id: ${{ steps.get-run-id.outputs.run_id }} 92 | 93 | - name: Download NLog.Extensions.AzureEventHub 94 | if: inputs.package == 'All' || inputs.package == 'NLog.Extensions.AzureEventHub' 95 | uses: actions/download-artifact@v4 96 | with: 97 | name: package-NLog.Extensions.AzureEventHub 98 | path: packages 99 | github-token: ${{ github.token }} 100 | run-id: ${{ steps.get-run-id.outputs.run_id }} 101 | 102 | - name: Download NLog.Extensions.AzureServiceBus 103 | if: inputs.package == 'All' || inputs.package == 'NLog.Extensions.AzureServiceBus' 104 | uses: actions/download-artifact@v4 105 | with: 106 | name: package-NLog.Extensions.AzureServiceBus 107 | path: packages 108 | github-token: ${{ github.token }} 109 | run-id: ${{ steps.get-run-id.outputs.run_id }} 110 | - name: List packages to publish 111 | run: | 112 | echo "Packages to publish:" 113 | if [ ! -d "packages" ]; then 114 | echo "ERROR: packages directory does not exist. Artifact download may have failed." >&2 115 | exit 1 116 | fi 117 | PACKAGES=$(find packages -name "*.nupkg" -type f) 118 | if [ -z "$PACKAGES" ]; then 119 | echo "ERROR: No .nupkg files found in packages directory. Check that artifacts were downloaded correctly." >&2 120 | exit 1 121 | fi 122 | echo "$PACKAGES" | while read f; do echo " - $(basename $f)"; done 123 | 124 | - name: Setup .NET 125 | uses: actions/setup-dotnet@v4 126 | with: 127 | dotnet-version: '8.0.x' 128 | 129 | # Trusted Publishing - no long-lived API key needed! 130 | # Uses OIDC to get a temporary token. Requires one-time setup on nuget.org. 131 | - name: Authenticate with NuGet (Trusted Publishing) 132 | if: inputs.dry_run == false 133 | id: nuget-login 134 | uses: nuget/login@v1 135 | with: 136 | user: ${{ secrets.NUGET_USER }} 137 | 138 | - name: Push to NuGet 139 | if: inputs.dry_run == false 140 | working-directory: packages 141 | run: dotnet nuget push '*.nupkg' --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate 142 | 143 | - name: Dry run summary 144 | if: inputs.dry_run == true 145 | run: | 146 | echo "DRY RUN - Would push the following packages to NuGet.org:" 147 | if [ ! -d "packages" ]; then 148 | echo "ERROR: packages directory does not exist. Artifact download may have failed." >&2 149 | exit 1 150 | fi 151 | PACKAGES=$(find packages -name "*.nupkg" -type f) 152 | if [ -z "$PACKAGES" ]; then 153 | echo "ERROR: No .nupkg files found in packages directory. Check that artifacts were downloaded correctly." >&2 154 | exit 1 155 | fi 156 | echo "$PACKAGES" | while read f; do echo " - $(basename $f)"; done 157 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureBlobStorage/README.md: -------------------------------------------------------------------------------- 1 | # Azure BlobStorage 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureBlobStorage** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureBlobStorage.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureBlobStorage/) | Azure Blob Storage | 6 | 7 | ## Blob Configuration 8 | 9 | ### Syntax 10 | ```xml 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | ### General Options 29 | 30 | _name_ - Name of the target. 31 | 32 | _layout_ - Text to be rendered. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 33 | 34 | _blobName_ - BlobName. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 35 | 36 | _container_ - Azure blob container name. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 37 | 38 | _contentType_ - Azure blob ContentType (Default = text/plain) 39 | 40 | _connectionString_ - Azure Blob Storage connection string from your storage account. Required unless using `ServiceUri`. 41 | 42 | _serviceUri_ - Uri to reference the blob service (e.g. https://{account_name}.blob.core.windows.net). Alternative to ConnectionString, where Managed Identiy is applied from DefaultAzureCredential. 43 | 44 | ### Authentication Options 45 | 46 | _managedIdentityClientId_ - clientId for `ManagedIdentityClientId` on `DefaultAzureCredentialOptions`. Requires `serviceUri`. 47 | 48 | _managedIdentityResourceId_ - resourceId for `ManagedIdentityResourceId` on `DefaultAzureCredentialOptions`, do not use together with `ManagedIdentityClientId`. Requires `serviceUri`. 49 | 50 | _tenantIdentity_ - tenantId for `DefaultAzureCredentialOptions` and `ClientSecretCredential`. Requires `serviceUri`. 51 | 52 | _sharedAccessSignature_ - Access signature for `AzureSasCredential` authentication. Requires `serviceUri`. 53 | 54 | _accountName_ - accountName for `StorageSharedKeyCredential` authentication. Requires `serviceUri` and `accessKey`. 55 | 56 | _accessKey_ - accountKey for `StorageSharedKeyCredential` authentication. Requires `serviceUri` and `accountName`. 57 | 58 | _clientAuthId_ - clientId for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`, `tenantIdentity` and `clientAuthSecret`. 59 | 60 | _clientAuthSecret_ - clientSecret for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`,`tenantIdentity` and `clientAuthId`. 61 | 62 | When using `ServiceUri` (Instead of ConnectionString) with `DefaultAzureCredential`, then Azure Identity can also resolve from environment variables: 63 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 64 | - `AZURE_TENANT_ID` - For TenantId 65 | 66 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 67 | 68 | ### Proxy Options 69 | 70 | _noProxy_ - Bypasses any system proxy and proxy in `ProxyAddress` when set to `true`. 71 | 72 | _proxyAddress_ - Address of the proxy server to use (e.g. http://proxyserver:8080). 73 | 74 | _proxyLogin_ - Login to use for the proxy server. Requires `proxyPassword`. 75 | 76 | _proxyPassword_ - Password to use for the proxy server. Requires `proxyLogin`. 77 | 78 | _useDefaultCredentialsForProxy_ - Uses the default credentials (`System.Net.CredentialCache.DefaultCredentials`) for the proxy server. Take precedence over `proxyLogin` and `proxyPassword` when set to `true`. 79 | 80 | ### Batching Policy 81 | 82 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 83 | 84 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 85 | 86 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 87 | 88 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 89 | 90 | ### Retry Policy 91 | 92 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 93 | 94 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 95 | 96 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 97 | 98 | ## Azure Blob Storage Emulator 99 | The AzureBlobStorage-target uses Append blob operations, which is [not support by Azure Storage Emulator](https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator#differences-for-blob-storage) from Microsoft. 100 | 101 | It will fail with the following error: 102 | ``` 103 | Azure.RequestFailedException: This feature is not currently supported by the Storage Emulator 104 | ``` 105 | 106 | Instead one can try an alternative Azure Storage Emulator like [Azurite](https://github.com/azure/azurite) 107 | 108 | ## Azure ConnectionString 109 | 110 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 111 | 112 | #### Lookup ConnectionString from appsettings.json 113 | > `connectionString="${configsetting:ConnectionStrings.AzureBlob}"` 114 | 115 | * Example appsettings.json on .NetCore: 116 | 117 | ```json 118 | { 119 | "ConnectionStrings": { 120 | "AzureBlob": "UseDevelopmentStorage=true;" 121 | } 122 | } 123 | ``` 124 | 125 | #### Lookup ConnectionString from app.config 126 | 127 | > `connectionString="${appsetting:ConnectionStrings.AzureBlob}"` 128 | 129 | * Example app.config on .NetFramework: 130 | 131 | ```xml 132 | 133 | 134 | 135 | 136 | 137 | ``` 138 | 139 | #### Lookup ConnectionString from environment-variable 140 | 141 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 142 | 143 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 144 | 145 | > `connectionString="${gdc:AzureBlobConnectionString}"` 146 | 147 | * Example code for setting GDC-value: 148 | 149 | ```c# 150 | NLog.GlobalDiagnosticsContext.Set("AzureBlobConnectionString", "UseDevelopmentStorage=true;"); 151 | ``` -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureStorage/AzureStorageNameCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace NLog.Extensions.AzureStorage 6 | { 7 | internal sealed class AzureStorageNameCache 8 | { 9 | private readonly Dictionary _storageNameCache = new Dictionary(); 10 | 11 | public string LookupStorageName(string requestedName, Func checkAndRepairName) 12 | { 13 | if (_storageNameCache.TryGetValue(requestedName, out var validName)) 14 | return validName; 15 | 16 | if (_storageNameCache.Count > 1000) 17 | _storageNameCache.Clear(); 18 | 19 | validName = checkAndRepairName(requestedName); 20 | _storageNameCache[requestedName] = validName; 21 | return validName; 22 | } 23 | 24 | private static string EnsureValidName(string name, bool ensureToLower = false) 25 | { 26 | if (name?.Length > 0) 27 | { 28 | if (char.IsWhiteSpace(name[0]) || char.IsWhiteSpace(name[name.Length - 1])) 29 | name = name.Trim(); 30 | 31 | for (int i = 0; i < name.Length; ++i) 32 | { 33 | char chr = name[i]; 34 | if (chr >= 'A' && chr <= 'Z') 35 | { 36 | if (ensureToLower) 37 | name = name.ToLowerInvariant(); 38 | continue; 39 | } 40 | if (chr >= 'a' && chr <= 'z') 41 | continue; 42 | if (i != 0 && chr >= '0' && chr <= '9') 43 | continue; 44 | 45 | return null; 46 | } 47 | 48 | return name; 49 | } 50 | 51 | return null; 52 | } 53 | 54 | /// 55 | /// Checks the and repairs container name acording to the Azure naming rules. 56 | /// 57 | /// Name of the requested container. 58 | /// 59 | public static string CheckAndRepairContainerNamingRules(string requestedContainerName) 60 | { 61 | /* https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/naming-and-referencing-containers--blobs--and-metadata 62 | Container Names 63 | A container name must be a valid DNS name, conforming to the following naming rules: 64 | Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character. 65 | Every dash (-) character must be immediately preceded and followed by a letter or number, 66 | consecutive dashes are not permitted in container names. 67 | All letters in a container name must be lowercase. 68 | Container names must be from 3 through 63 characters long. 69 | */ 70 | var simpleValidName = requestedContainerName?.Length <= 63 ? EnsureValidName(requestedContainerName, ensureToLower: true) : null; 71 | if (simpleValidName?.Length >= 3) 72 | return simpleValidName; 73 | 74 | requestedContainerName = requestedContainerName?.Trim() ?? string.Empty; 75 | const string validContainerPattern = "^[a-z0-9](?!.*--)[a-z0-9-]{1,61}[a-z0-9]$"; 76 | var loweredRequestedContainerName = requestedContainerName.ToLower(); 77 | if (Regex.Match(loweredRequestedContainerName, validContainerPattern, RegexOptions.ExplicitCapture).Success) 78 | { 79 | //valid name okay to lower and use 80 | return loweredRequestedContainerName; 81 | } 82 | 83 | const string trimLeadingPattern = "^.*?(?=[a-zA-Z0-9])"; 84 | const string trimTrailingPattern = "(?<=[a-zA-Z0-9]).*?"; 85 | const string trimFobiddenCharactersPattern = "[^a-zA-Z0-9-]"; 86 | const string trimExtraHyphensPattern = "-+"; 87 | 88 | requestedContainerName = requestedContainerName.Replace('.', '-').Replace('_', '-').Replace('\\', '-').Replace('/', '-').Replace(' ', '-').Replace("--", "-").Replace("--", "-").Trim(new[] { '-' }); 89 | var pass1 = Regex.Replace(requestedContainerName, trimFobiddenCharactersPattern, String.Empty, RegexOptions.ExplicitCapture); 90 | var pass2 = Regex.Replace(pass1, trimTrailingPattern, String.Empty, RegexOptions.RightToLeft | RegexOptions.ExplicitCapture); 91 | var pass3 = Regex.Replace(pass2, trimLeadingPattern, String.Empty, RegexOptions.ExplicitCapture); 92 | var pass4 = Regex.Replace(pass3, trimExtraHyphensPattern, "-", RegexOptions.ExplicitCapture); 93 | var loweredCleanedContainerName = pass4.ToLower(); 94 | if (Regex.Match(loweredCleanedContainerName, validContainerPattern, RegexOptions.ExplicitCapture).Success) 95 | { 96 | return loweredCleanedContainerName; 97 | } 98 | return "defaultlog"; 99 | } 100 | 101 | /// 102 | /// Checks the and repairs table name acording to the Azure naming rules. 103 | /// 104 | /// Name of the table. 105 | /// 106 | public static string CheckAndRepairTableNamingRules(string tableName) 107 | { 108 | /* http://msdn.microsoft.com/en-us/library/windowsazure/dd179338.aspx 109 | Table Names: 110 | Table names must be unique within an account. 111 | Table names may contain only alphanumeric characters. 112 | Table names cannot begin with a numeric character. 113 | Table names are case-insensitive. 114 | Table names must be from 3 to 63 characters long. 115 | Some table names are reserved, including "tables". Attempting to create a table with a reserved table name returns error code 404 (Bad Request). 116 | */ 117 | var simpleValidName = tableName?.Length <= 63 ? EnsureValidName(tableName) : null; 118 | if (simpleValidName?.Length >= 3) 119 | return simpleValidName; 120 | 121 | const string trimLeadingPattern = "^.*?(?=[a-zA-Z])"; 122 | const string trimFobiddenCharactersPattern = "[^a-zA-Z0-9-]"; 123 | 124 | var pass1 = Regex.Replace(tableName, trimFobiddenCharactersPattern, String.Empty, RegexOptions.ExplicitCapture); 125 | var cleanedTableName = Regex.Replace(pass1, trimLeadingPattern, String.Empty, RegexOptions.ExplicitCapture); 126 | if (String.IsNullOrWhiteSpace(cleanedTableName) || cleanedTableName.Length > 63 || cleanedTableName.Length < 3) 127 | { 128 | var tableDefault = "Logs"; 129 | return tableDefault; 130 | } 131 | return tableName; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventHub/README.md: -------------------------------------------------------------------------------- 1 | # Azure EventHubs 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureEventHub** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureEventHub.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureEventHub/) | Azure EventHubs | 6 | 7 | ## EventHub Configuration 8 | 9 | ### Syntax 10 | ```xml 11 | 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | ### General Options 38 | 39 | _name_ - Name of the target. 40 | 41 | _connectionString_ - Azure storage connection string. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 42 | 43 | _eventHubName_ - Overrides the EntityPath in the ConnectionString. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 44 | 45 | _partitionKey_ - Partition-Key which EventHub uses to generate PartitionId-hash. [Layout](https://github.com/NLog/NLog/wiki/Layouts) (Default='0') 46 | 47 | _layout_ - EventData Body Text to be rendered and encoded as UTF8. [Layout](https://github.com/NLog/NLog/wiki/Layouts). 48 | 49 | _contentType_ - EventData ContentType. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Ex. application/json 50 | 51 | _messageId_ - EventData MessageId. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 52 | 53 | _correlationId_ - EventData Correlationid. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 54 | 55 | _useWebSockets_ - Enable AmqpWebSockets. Ex. true/false (optional) 56 | 57 | _customEndpointAddress_ - Custom endpoint address that can be used when establishing the connection (optional) 58 | 59 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is applied from DefaultAzureCredential. 60 | 61 | _eventProducerIdentifier_ - A unique name used to identify the event producer. If null or empty, a GUID will be used as the identifier (optional) 62 | 63 | ### Authentication Options 64 | 65 | _managedIdentityClientId_ - clientId for `ManagedIdentityClientId` on `DefaultAzureCredentialOptions`. Requires `serviceUri` 66 | 67 | _managedIdentityResourceId_ - resourceId for `ManagedIdentityResourceId` on `DefaultAzureCredentialOptions`, do not use together with `ManagedIdentityClientId`. Requires `serviceUri`. 68 | 69 | _tenantIdentity_ - tenantId for `DefaultAzureCredentialOptions`. Requires `serviceUri`. 70 | 71 | _sharedAccessSignature_ - Access signature for `AzureSasCredential` authentication. Requires `serviceUri`. 72 | 73 | _accountName_ - accountName for `AzureNamedKeyCredential` authentication. Requires `serviceUri` and `accessKey`. 74 | 75 | _accessKey_ - accountKey for `AzureNamedKeyCredential` authentication. Requires `serviceUri` and `accountName`. 76 | 77 | _clientAuthId_ - clientId for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`, `tenantIdentity` and `clientAuthSecret`. 78 | 79 | _clientAuthSecret_ - clientSecret for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`,`tenantIdentity` and `clientAuthId`. 80 | 81 | When using `ServiceUri` (Instead of ConnectionString) with `DefaultAzureCredential`, then Azure Identity can also resolve from environment variables: 82 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 83 | - `AZURE_TENANT_ID` - For TenantId 84 | 85 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 86 | 87 | ### Proxy Options 88 | 89 | _noProxy_ - Bypasses any system proxy and proxy in `ProxyAddress` when set to `true`. Requires `useWebSockets = true`. 90 | 91 | _proxyAddress_ - Address of the proxy server to use (e.g. http://proxyserver:8080). Requires `useWebSockets = true`. 92 | 93 | _proxyLogin_ - Login to use for the proxy server. Requires `proxyPassword`. Requires `useWebSockets = true`. 94 | 95 | _proxyPassword_ - Password to use for the proxy server. Requires `proxyLogin`. Requires `useWebSockets = true`. 96 | 97 | _useDefaultCredentialsForProxy_ - Uses the default credentials (`System.Net.CredentialCache.DefaultCredentials`) for the proxy server. Take precedence over `proxyLogin` and `proxyPassword` when set to `true`. Requires `useWebSockets = true`. 98 | 99 | ### Batching Policy 100 | 101 | _maxBatchSizeBytes_ - Max size of a single batch in bytes [Integer](https://github.com/NLog/NLog/wiki/Data-types) (Default=1024*1024) 102 | 103 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 104 | 105 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 106 | 107 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 108 | 109 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 110 | 111 | ### Retry Policy 112 | 113 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 114 | 115 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 116 | 117 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 118 | 119 | ## Azure ConnectionString 120 | 121 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 122 | 123 | #### Lookup ConnectionString from appsettings.json 124 | 125 | > `connectionString="${configsetting:ConnectionStrings.AzureEventHub}"` 126 | 127 | * Example appsettings.json on .NetCore: 128 | 129 | ```json 130 | { 131 | "ConnectionStrings": { 132 | "AzureEventHub": "UseDevelopmentStorage=true;" 133 | } 134 | } 135 | ``` 136 | 137 | #### Lookup ConnectionString from app.config 138 | 139 | > `connectionString="${appsetting:ConnectionStrings.AzureEventHub}"` 140 | 141 | * Example app.config on .NetFramework: 142 | 143 | ```xml 144 | 145 | 146 | 147 | 148 | 149 | ``` 150 | 151 | #### Lookup ConnectionString from environment-variable 152 | 153 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 154 | 155 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 156 | 157 | > `connectionString="${gdc:AzureEventHubConnectionString}"` 158 | 159 | * Example code for setting GDC-value: 160 | 161 | ```c# 162 | NLog.GlobalDiagnosticsContext.Set("AzureEventHubConnectionString", "UseDevelopmentStorage=true;"); 163 | ``` -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureDataTables/README.md: -------------------------------------------------------------------------------- 1 | # Azure Table Storage and Cosmos Table 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureDataTables** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureDataTables.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureDataTables/) | Azure Table Storage or Azure CosmosDb Tables | 6 | 7 | ## Table Configuration 8 | Supports both Azure Storage Tables and CosmosDB Tables. 9 | 10 | ### Syntax 11 | ```xml 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | ``` 26 | ### General Options 27 | 28 | _name_ - Name of the target. 29 | 30 | _layout_ - Text to be rendered. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 31 | 32 | _connectionString_ - Azure storage connection string. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Required unless using `serviceUri`. 33 | 34 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is acquired from DefaultAzureCredential. 35 | 36 | _tableName_ - Azure table name. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 37 | 38 | _rowKey_ - Azure Table RowKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = "InverseTicks_${guid}" 39 | 40 | _partitionKey_ - Azure PartitionKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = `${logger}` 41 | 42 | _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. 43 | 44 | ## Authentication Options 45 | 46 | _managedIdentityClientId_ - clientId for `ManagedIdentityClientId` on `DefaultAzureCredentialOptions`. Requires `serviceUri` 47 | 48 | _managedIdentityResourceId_ - resourceId for `ManagedIdentityResourceId` on `DefaultAzureCredentialOptions`, do not use together with `ManagedIdentityClientId`. Requires `serviceUri`. 49 | 50 | _tenantIdentity_ - tenantId for `DefaultAzureCredentialOptions`. Requires `serviceUri`. 51 | 52 | _sharedAccessSignature_ - Access signature for `AzureSasCredential` authentication. Requires `serviceUri`. 53 | 54 | _accountName_ - accountName for `TableSharedKeyCredential` authentication. Requires `serviceUri` and `accessKey`. 55 | 56 | _accessKey_ - accountKey for `TableSharedKeyCredential` authentication. Requires `serviceUri` and `accountName`. 57 | 58 | _clientAuthId_ - clientId for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`, `tenantIdentity` and `clientAuthSecret`. 59 | 60 | _clientAuthSecret_ - clientSecret for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`,`tenantIdentity` and `clientAuthId`. 61 | 62 | When using `ServiceUri` (Instead of ConnectionString) with `DefaultAzureCredential`, then Azure Identity can also resolve from environment variables: 63 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 64 | - `AZURE_TENANT_ID` - For TenantId 65 | 66 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 67 | 68 | ## Proxy Options 69 | 70 | _noProxy_ - Bypasses any system proxy and proxy in `ProxyAddress` when set to `true`. 71 | 72 | _proxyAddress_ - Address of the proxy server to use (e.g. http://proxyserver:8080). 73 | 74 | _proxyLogin_ - Login to use for the proxy server. Requires `proxyPassword`. 75 | 76 | _proxyPassword_ - Password to use for the proxy server. Requires `proxyLogin`. 77 | 78 | _useDefaultCredentialsForProxy_ - Uses the default credentials (`System.Net.CredentialCache.DefaultCredentials`) for the proxy server. Take precedence over `proxyLogin` and `proxyPassword` when set to `true`. 79 | 80 | ### Dynamic TableEntity 81 | Instead of using the predefined NLogEntity-properties, then one can specify wanted properties: 82 | 83 | ```xml 84 | 85 | 86 | 87 | 88 | 89 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ``` 102 | 103 | It will by default include the hardcoded property `LogTimeStamp` of type DateTime that contains `LogEventInfo.TimeStamp.ToUniversalTime()`. 104 | - This can be overriden by having `` as the first property, where empty property-value means leave out. 105 | 106 | ### Batching Policy 107 | 108 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 109 | 110 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 111 | 112 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 113 | 114 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 115 | 116 | ### Retry Policy 117 | 118 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 119 | 120 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 121 | 122 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 123 | 124 | ## Azure Table Service Size Limits 125 | 126 | There are restrictions for how big column values can be: 127 | 128 | - PartitionKey has max limit of 1024 characters 129 | - RowKey has max limit of 1024 characters 130 | - Column string-Values has max limit of 32.768 characters 131 | 132 | When breaking these limits, then [Azure Table Service](https://learn.microsoft.com/en-us/rest/api/storageservices/understanding-the-table-service-data-model) will discard the data, so NLog AzureDataTables will automatically truncate if needed. 133 | 134 | ## Azure ConnectionString 135 | 136 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 137 | 138 | #### Lookup ConnectionString from appsettings.json 139 | 140 | > `connectionString="${configsetting:ConnectionStrings.AzureTable}"` 141 | 142 | * Example appsettings.json on .NetCore: 143 | 144 | ```json 145 | { 146 | "ConnectionStrings": { 147 | "AzureTable": "Server=tcp:server.database.windows.net;" 148 | } 149 | } 150 | ``` 151 | 152 | #### Lookup ConnectionString from app.config 153 | 154 | > `connectionString="${appsetting:ConnectionStrings.AzureTable}"` 155 | 156 | * Example app.config on .NetFramework: 157 | 158 | ```xml 159 | 160 | 161 | 162 | 163 | 164 | ``` 165 | 166 | #### Lookup ConnectionString from environment-variable 167 | 168 | > `connectionString="${environment:AZURESQLCONNSTR_CONNECTION_STRING}"` 169 | 170 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 171 | 172 | > `connectionString="${gdc:AzureTableConnectionString}"` 173 | 174 | * Example code for setting GDC-value: 175 | 176 | ```c# 177 | NLog.GlobalDiagnosticsContext.Set("AzureTableConnectionString", "Server=tcp:server.database.windows.net;"); 178 | ``` -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureServiceBus/README.md: -------------------------------------------------------------------------------- 1 | # Azure ServiceBus 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureServiceBus** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureServiceBus.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureServiceBus/) | Azure Service Bus | 6 | 7 | ## ServiceBus Configuration 8 | 9 | ### Syntax 10 | ```xml 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ``` 39 | 40 | ### General Options 41 | 42 | _name_ - Name of the target. 43 | 44 | _connectionString_ - Azure storage connection string. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required unless using `ServiceUri`. 45 | 46 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is applied from DefaultAzureCredential. 47 | 48 | _queueName_ - QueueName for multiple producers single consumer. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 49 | 50 | _topicName_ - TopicName for multiple producers and multiple subscribers. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 51 | 52 | _sessionId_ - SessionId-Key which Service Bus uses to generate PartitionId-hash. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 53 | 54 | _partitionKey_ - Partition-Key which Service Bus uses to generate PartitionId-hash. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 55 | 56 | _subject_ - Service Bus Message Subject to be used as label for the message. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 57 | 58 | _layout_ - Service Bus Message Body to be rendered and encoded as UTF8. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 59 | 60 | _contentType_ - Service Bus Message Body ContentType. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Ex. application/json 61 | 62 | _messageId_ - Service Bus Message MessageId. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 63 | 64 | _correlationId_ - Service Bus Message Correlationid. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 65 | 66 | _timeToLiveSeconds_ - Default Time-To-Live (TTL) for ServiceBus messages in seconds (Optional) 67 | 68 | _timeToLiveDays_ - Default Time-To-Live (TTL) for ServiceBus messages in days (Optional) 69 | 70 | _useWebSockets_ - Enable AmqpWebSockets. Ex. true/false (optional) 71 | 72 | _customEndpointAddress_ - Custom endpoint address that can be used when establishing the connection (optional) 73 | 74 | _eventProducerIdentifier_ - A unique name used to identify the event producer. If null or empty, a GUID will be used as the identifier (optional) 75 | 76 | ### Authentication Options 77 | 78 | _managedIdentityClientId_ - clientId for `ManagedIdentityClientId` on `DefaultAzureCredentialOptions`. Requires `serviceUri`. 79 | 80 | _managedIdentityResourceId_ - resourceId for `ManagedIdentityResourceId` on `DefaultAzureCredentialOptions`, do not use together with `ManagedIdentityClientId`. Requires `serviceUri`. 81 | 82 | _tenantIdentity_ - tenantId for `DefaultAzureCredentialOptions`. Requires `serviceUri`. 83 | 84 | _sharedAccessSignature_ - Access signature for `AzureSasCredential` authentication. Requires `serviceUri`. 85 | 86 | _accountName_ - accountName for `AzureNamedKeyCredential` authentication. Requires `serviceUri` and `accessKey`. 87 | 88 | _accessKey_ - accountKey for `AzureNamedKeyCredential` authentication. Requires `serviceUri` and `accountName`. 89 | 90 | _clientAuthId_ - clientId for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`, `tenantIdentity` and `clientAuthSecret`. 91 | 92 | _clientAuthSecret_ - clientSecret for `ClientSecretCredential` OAuth2 authentication. Requires `serviceUri`,`tenantIdentity` and `clientAuthId`. 93 | 94 | When using `ServiceUri` (Instead of ConnectionString) with `DefaultAzureCredential`, then Azure Identity can also resolve from environment variables: 95 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 96 | - `AZURE_TENANT_ID` - For TenantId 97 | 98 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 99 | 100 | ### Proxy Options 101 | 102 | _noProxy_ - Bypasses any system proxy and proxy in `ProxyAddress` when set to `true`. Requires `useWebSockets = true`. 103 | 104 | _proxyAddress_ - Address of the proxy server to use (e.g. http://proxyserver:8080). Requires `useWebSockets = true`. 105 | 106 | _proxyLogin_ - Login to use for the proxy server. Requires `proxyPassword`. Requires `useWebSockets = true`. 107 | 108 | _proxyPassword_ - Password to use for the proxy server. Requires `proxyLogin`. Requires `useWebSockets = true`. 109 | 110 | _useDefaultCredentialsForProxy_ - Uses the default credentials (`System.Net.CredentialCache.DefaultCredentials`) for the proxy server. Take precedence over `proxyLogin` and `proxyPassword` when set to `true`. Requires `useWebSockets = true`. 111 | 112 | 113 | ### Batching Policy 114 | 115 | _maxBatchSizeBytes_ - Max size of a single batch in bytes [Integer](https://github.com/NLog/NLog/wiki/Data-types) (Default=256*1024) 116 | 117 | _batchSize_ - Number of LogEvents to send in a single batch (Default=100) 118 | 119 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 120 | 121 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 122 | 123 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 124 | 125 | ### Retry Policy 126 | 127 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 128 | 129 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 130 | 131 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 132 | 133 | ## Azure ConnectionString 134 | 135 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 136 | 137 | #### Lookup ConnectionString from appsettings.json 138 | 139 | > `connectionString="${configsetting:ConnectionStrings.AzureBus}"` 140 | 141 | * Example appsettings.json on .NetCore: 142 | ```json 143 | { 144 | "ConnectionStrings": { 145 | "AzureBus": "UseDevelopmentStorage=true;" 146 | } 147 | } 148 | ``` 149 | 150 | #### Lookup ConnectionString from app.config 151 | 152 | > `connectionString="${appsetting:ConnectionStrings.AzureBus}"` 153 | 154 | * Example app.config on .NetFramework: 155 | ```xml 156 | 157 | 158 | 159 | 160 | 161 | ``` 162 | 163 | #### Lookup ConnectionString from environment-variable 164 | 165 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 166 | 167 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 168 | 169 | > `connectionString="${gdc:AzureBusConnectionString}"` 170 | 171 | * Example code for setting GDC-value: 172 | ```c# 173 | NLog.GlobalDiagnosticsContext.Set("AzureBusConnectionString", "UseDevelopmentStorage=true;"); 174 | ``` -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureStorage.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32804.467 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EACADB54-1A72-4CEE-AF3A-5AEDC3CB553A}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\appveyor.yml = ..\appveyor.yml 9 | ..\README.md = ..\README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureEventHub", "NLog.Extensions.AzureEventHub\NLog.Extensions.AzureEventHub.csproj", "{CF5E1262-D006-4C3A-9A62-98C31A3AD5F0}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{246DBA78-87B8-4982-912F-4BBFFF005AD6}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureEventHub.Tests", "..\test\NLog.Extensions.AzureEventHub.Tests\NLog.Extensions.AzureEventHub.Tests.csproj", "{EEAF8AEA-27AE-464A-9EAB-EE373FEF3EE2}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Extensions.AzureStorage.IntegrationTest", "..\test\NLog.Extensions.AzureStorage.IntegrationTest\NLog.Extensions.AzureStorage.IntegrationTest.csproj", "{BD4E0EB2-99DB-41CB-A46B-DC573A7573CA}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureBlobStorage", "NLog.Extensions.AzureBlobStorage\NLog.Extensions.AzureBlobStorage.csproj", "{21B2270D-1F45-4BE0-8CCE-DEC8FF293704}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureQueueStorage", "NLog.Extensions.AzureQueueStorage\NLog.Extensions.AzureQueueStorage.csproj", "{3E2EF52D-C7A3-48D4-9AB0-8D3E9EFD3518}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureBlobStorage.Tests", "..\test\NLog.Extensions.AzureBlobStorage.Tests\NLog.Extensions.AzureBlobStorage.Tests.csproj", "{18DBD852-28D9-43C8-9A1A-F13C13B5CA13}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureQueueStorage.Tests", "..\test\NLog.Extensions.AzureQueueStorage.Tests\NLog.Extensions.AzureQueueStorage.Tests.csproj", "{A56BD3E3-C388-41B9-A021-8B67C43CF225}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureServiceBus", "NLog.Extensions.AzureServiceBus\NLog.Extensions.AzureServiceBus.csproj", "{DFD8118B-2AFE-4DDF-BFD6-F68D6EAB3498}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureServiceBus.Tests", "..\test\NLog.Extensions.AzureServiceBus.Tests\NLog.Extensions.AzureServiceBus.Tests.csproj", "{DDE1C98C-7DE2-4F38-B6CD-63FF976680FD}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureDataTables", "NLog.Extensions.AzureDataTables\NLog.Extensions.AzureDataTables.csproj", "{0969C9D5-F3F1-4AB0-8887-A62F877EE68A}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureDataTables.Tests", "..\test\NLog.Extensions.AzureDataTables.Tests\NLog.Extensions.AzureDataTables.Tests.csproj", "{972C36EF-406F-4276-A8F4-EC859B188B9C}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureEventGrid", "NLog.Extensions.AzureEventGrid\NLog.Extensions.AzureEventGrid.csproj", "{111D9918-5708-4C62-A52F-95F400370B0E}" 37 | EndProject 38 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.AzureEventGrid.Tests", "..\test\NLog.Extensions.AzureEventGrid.Tests\NLog.Extensions.AzureEventGrid.Tests.csproj", "{822D7B8F-1B2A-4E87-9FA2-2A50367E99E7}" 39 | EndProject 40 | Global 41 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 42 | Debug|Any CPU = Debug|Any CPU 43 | Release|Any CPU = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 46 | {CF5E1262-D006-4C3A-9A62-98C31A3AD5F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {CF5E1262-D006-4C3A-9A62-98C31A3AD5F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {CF5E1262-D006-4C3A-9A62-98C31A3AD5F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {CF5E1262-D006-4C3A-9A62-98C31A3AD5F0}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {EEAF8AEA-27AE-464A-9EAB-EE373FEF3EE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {EEAF8AEA-27AE-464A-9EAB-EE373FEF3EE2}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {EEAF8AEA-27AE-464A-9EAB-EE373FEF3EE2}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {EEAF8AEA-27AE-464A-9EAB-EE373FEF3EE2}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {BD4E0EB2-99DB-41CB-A46B-DC573A7573CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {BD4E0EB2-99DB-41CB-A46B-DC573A7573CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {BD4E0EB2-99DB-41CB-A46B-DC573A7573CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {BD4E0EB2-99DB-41CB-A46B-DC573A7573CA}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {21B2270D-1F45-4BE0-8CCE-DEC8FF293704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {21B2270D-1F45-4BE0-8CCE-DEC8FF293704}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {21B2270D-1F45-4BE0-8CCE-DEC8FF293704}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {21B2270D-1F45-4BE0-8CCE-DEC8FF293704}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {3E2EF52D-C7A3-48D4-9AB0-8D3E9EFD3518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {3E2EF52D-C7A3-48D4-9AB0-8D3E9EFD3518}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {3E2EF52D-C7A3-48D4-9AB0-8D3E9EFD3518}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {3E2EF52D-C7A3-48D4-9AB0-8D3E9EFD3518}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {18DBD852-28D9-43C8-9A1A-F13C13B5CA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {18DBD852-28D9-43C8-9A1A-F13C13B5CA13}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {18DBD852-28D9-43C8-9A1A-F13C13B5CA13}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {18DBD852-28D9-43C8-9A1A-F13C13B5CA13}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {A56BD3E3-C388-41B9-A021-8B67C43CF225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {A56BD3E3-C388-41B9-A021-8B67C43CF225}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {A56BD3E3-C388-41B9-A021-8B67C43CF225}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {A56BD3E3-C388-41B9-A021-8B67C43CF225}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {DFD8118B-2AFE-4DDF-BFD6-F68D6EAB3498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {DFD8118B-2AFE-4DDF-BFD6-F68D6EAB3498}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {DFD8118B-2AFE-4DDF-BFD6-F68D6EAB3498}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {DFD8118B-2AFE-4DDF-BFD6-F68D6EAB3498}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {DDE1C98C-7DE2-4F38-B6CD-63FF976680FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {DDE1C98C-7DE2-4F38-B6CD-63FF976680FD}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {DDE1C98C-7DE2-4F38-B6CD-63FF976680FD}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {DDE1C98C-7DE2-4F38-B6CD-63FF976680FD}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {0969C9D5-F3F1-4AB0-8887-A62F877EE68A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 83 | {0969C9D5-F3F1-4AB0-8887-A62F877EE68A}.Debug|Any CPU.Build.0 = Debug|Any CPU 84 | {0969C9D5-F3F1-4AB0-8887-A62F877EE68A}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {0969C9D5-F3F1-4AB0-8887-A62F877EE68A}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {972C36EF-406F-4276-A8F4-EC859B188B9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {972C36EF-406F-4276-A8F4-EC859B188B9C}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {972C36EF-406F-4276-A8F4-EC859B188B9C}.Release|Any CPU.ActiveCfg = Release|Any CPU 89 | {972C36EF-406F-4276-A8F4-EC859B188B9C}.Release|Any CPU.Build.0 = Release|Any CPU 90 | {111D9918-5708-4C62-A52F-95F400370B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 91 | {111D9918-5708-4C62-A52F-95F400370B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU 92 | {111D9918-5708-4C62-A52F-95F400370B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU 93 | {111D9918-5708-4C62-A52F-95F400370B0E}.Release|Any CPU.Build.0 = Release|Any CPU 94 | {822D7B8F-1B2A-4E87-9FA2-2A50367E99E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 95 | {822D7B8F-1B2A-4E87-9FA2-2A50367E99E7}.Debug|Any CPU.Build.0 = Debug|Any CPU 96 | {822D7B8F-1B2A-4E87-9FA2-2A50367E99E7}.Release|Any CPU.ActiveCfg = Release|Any CPU 97 | {822D7B8F-1B2A-4E87-9FA2-2A50367E99E7}.Release|Any CPU.Build.0 = Release|Any CPU 98 | EndGlobalSection 99 | GlobalSection(SolutionProperties) = preSolution 100 | HideSolutionNode = FALSE 101 | EndGlobalSection 102 | GlobalSection(NestedProjects) = preSolution 103 | {EEAF8AEA-27AE-464A-9EAB-EE373FEF3EE2} = {246DBA78-87B8-4982-912F-4BBFFF005AD6} 104 | {BD4E0EB2-99DB-41CB-A46B-DC573A7573CA} = {246DBA78-87B8-4982-912F-4BBFFF005AD6} 105 | {18DBD852-28D9-43C8-9A1A-F13C13B5CA13} = {246DBA78-87B8-4982-912F-4BBFFF005AD6} 106 | {A56BD3E3-C388-41B9-A021-8B67C43CF225} = {246DBA78-87B8-4982-912F-4BBFFF005AD6} 107 | {DDE1C98C-7DE2-4F38-B6CD-63FF976680FD} = {246DBA78-87B8-4982-912F-4BBFFF005AD6} 108 | {972C36EF-406F-4276-A8F4-EC859B188B9C} = {246DBA78-87B8-4982-912F-4BBFFF005AD6} 109 | {822D7B8F-1B2A-4E87-9FA2-2A50367E99E7} = {246DBA78-87B8-4982-912F-4BBFFF005AD6} 110 | EndGlobalSection 111 | GlobalSection(ExtensibilityGlobals) = postSolution 112 | SolutionGuid = {57F963D5-962F-4205-B49E-B4CCE3888D73} 113 | EndGlobalSection 114 | EndGlobal 115 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureAccessToken/AccessTokenLayoutRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Threading; 7 | using Microsoft.Azure.Services.AppAuthentication; 8 | using NLog.Common; 9 | using NLog.Config; 10 | using NLog.Layouts; 11 | using NLog.LayoutRenderers; 12 | 13 | namespace NLog.Extensions.AzureAccessToken 14 | { 15 | [LayoutRenderer("AzureAccessToken")] 16 | [ThreadAgnostic] 17 | [ThreadSafe] 18 | public class AccessTokenLayoutRenderer : LayoutRenderer 19 | { 20 | internal static readonly ConcurrentDictionary> AccessTokenProviders = new ConcurrentDictionary>(); 21 | private readonly Func _tokenProviderFactory; 22 | private AccessTokenRefresher _tokenRefresher; 23 | 24 | /// 25 | /// Ex. https://management.azure.com or https://database.windows.net/ or https://storage.azure.com/ 26 | /// 27 | [RequiredParameter] 28 | public Layout ResourceName { get; set; } 29 | 30 | /// 31 | /// TenantId (or directory Id) of your Azure Active Directory. Ex hosttenant.onmicrosoft.com 32 | /// 33 | public Layout TenantId { get; set; } 34 | 35 | public Layout ConnectionString { get; set; } 36 | 37 | public Layout AzureAdInstance { get; set; } 38 | 39 | public AccessTokenLayoutRenderer() 40 | :this((connectionString,azureAdInstance) => new AzureServiceTokenProviderService(connectionString, azureAdInstance)) 41 | { 42 | } 43 | 44 | internal AccessTokenLayoutRenderer(Func tokenProviderFactory) 45 | { 46 | _tokenProviderFactory = tokenProviderFactory; 47 | } 48 | 49 | protected override void InitializeLayoutRenderer() 50 | { 51 | ResetTokenRefresher(); 52 | LookupAccessTokenRefresher(); 53 | base.InitializeLayoutRenderer(); 54 | } 55 | 56 | protected override void CloseLayoutRenderer() 57 | { 58 | ResetTokenRefresher(); 59 | base.CloseLayoutRenderer(); 60 | } 61 | 62 | internal void ResetTokenRefresher() 63 | { 64 | var tokenRefresher = Interlocked.Exchange(ref _tokenRefresher, null); 65 | if (tokenRefresher != null) 66 | tokenRefresher.AccessTokenRefreshed -= AccessTokenRefreshed; 67 | } 68 | 69 | private void AccessTokenRefreshed(object sender, EventArgs eventArgs) 70 | { 71 | InternalLogger.Debug("AccessToken LayoutRenderer - AccessToken Refreshed"); 72 | } 73 | 74 | protected override void Append(StringBuilder builder, LogEventInfo logEvent) 75 | { 76 | var tokenProvider = LookupAccessTokenRefresher(); 77 | builder.Append(tokenProvider?.AccessToken); 78 | } 79 | 80 | AccessTokenRefresher LookupAccessTokenRefresher() 81 | { 82 | if (_tokenRefresher != null) 83 | return _tokenRefresher; 84 | 85 | var resourceName = ResourceName?.Render(LogEventInfo.CreateNullEvent()); 86 | if (string.IsNullOrEmpty(resourceName)) 87 | { 88 | InternalLogger.Warn("AccessToken LayoutRenderer - Missing ResourceName"); 89 | return null; 90 | } 91 | 92 | var tenantId = TenantId?.Render(LogEventInfo.CreateNullEvent()); 93 | if (string.IsNullOrWhiteSpace(tenantId)) 94 | tenantId = null; 95 | var connectionString = ConnectionString?.Render(LogEventInfo.CreateNullEvent()); 96 | var azureAdInstance = AzureAdInstance?.Render(LogEventInfo.CreateNullEvent()); 97 | 98 | var tokenProviderKey = new TokenProviderKey(resourceName, tenantId, connectionString, azureAdInstance); 99 | AccessTokenRefresher tokenRefresher = null; 100 | if (!AccessTokenProviders.TryGetValue(tokenProviderKey, out var tokenProvider) || !tokenProvider.TryGetTarget(out tokenRefresher)) 101 | { 102 | var serviceProvider = _tokenProviderFactory(connectionString, azureAdInstance); 103 | lock (AccessTokenProviders) 104 | { 105 | if (_tokenRefresher == null) 106 | { 107 | if (!AccessTokenProviders.TryGetValue(tokenProviderKey, out tokenProvider) || !tokenProvider.TryGetTarget(out tokenRefresher)) 108 | { 109 | tokenRefresher = new AccessTokenRefresher(serviceProvider, resourceName, tenantId); 110 | AccessTokenProviders[tokenProviderKey] = new WeakReference(tokenRefresher); 111 | } 112 | } 113 | } 114 | } 115 | 116 | if (Interlocked.CompareExchange(ref _tokenRefresher, tokenRefresher, null) == null) 117 | { 118 | _tokenRefresher.AccessTokenRefreshed += AccessTokenRefreshed; 119 | } 120 | 121 | return _tokenRefresher; 122 | } 123 | 124 | internal struct TokenProviderKey : IEquatable 125 | { 126 | public readonly string ResourceName; 127 | public readonly string TenantId; 128 | public readonly string ConnectionString; 129 | public readonly string AzureAdInstance; 130 | 131 | public TokenProviderKey(string resourceName, string tenantId, string connectionString, string azureAdInstance) 132 | { 133 | ResourceName = resourceName ?? string.Empty; 134 | TenantId = tenantId ?? string.Empty; 135 | ConnectionString = connectionString ?? string.Empty; 136 | AzureAdInstance = azureAdInstance ?? string.Empty; 137 | } 138 | 139 | public bool Equals(TokenProviderKey other) 140 | { 141 | return string.Equals(ResourceName, other.ResourceName) 142 | && string.Equals(TenantId, other.TenantId) 143 | && string.Equals(ConnectionString, other.ConnectionString) 144 | && string.Equals(AzureAdInstance, other.AzureAdInstance); 145 | } 146 | 147 | public override bool Equals(object obj) 148 | { 149 | return obj is TokenProviderKey other && Equals(other); 150 | } 151 | 152 | public override int GetHashCode() 153 | { 154 | int hash = 17; 155 | hash = hash * 23 + ResourceName.GetHashCode(); 156 | hash = hash * 23 + TenantId.GetHashCode(); 157 | hash = hash * 23 + ConnectionString.GetHashCode(); 158 | hash = hash * 23 + AzureAdInstance.GetHashCode(); 159 | return hash; 160 | } 161 | } 162 | 163 | internal sealed class AccessTokenRefresher : IDisposable 164 | { 165 | private readonly object _lockObject = new object(); 166 | private readonly IAzureServiceTokenProviderService _tokenProvider; 167 | private CancellationTokenSource _cancellationTokenSource; 168 | private readonly Timer _refreshTimer; 169 | private readonly string _resource; 170 | private readonly string _tenantId; 171 | 172 | public AccessTokenRefresher(IAzureServiceTokenProviderService tokenProvider, string resource, string tenantId) 173 | { 174 | _tokenProvider = tokenProvider; 175 | _cancellationTokenSource = new CancellationTokenSource(); 176 | _resource = resource; 177 | _tenantId = tenantId; 178 | _refreshTimer = new Timer(TimerRefresh, this, Timeout.Infinite, Timeout.Infinite); 179 | } 180 | 181 | public string AccessToken => WaitForToken(); 182 | private string _accessToken; 183 | 184 | private static void TimerRefresh(object state) 185 | { 186 | _ = ((AccessTokenRefresher)state).RefreshToken(); 187 | } 188 | 189 | private string WaitForToken() 190 | { 191 | if (_accessToken == null) 192 | { 193 | for (int i = 0; i < 100; ++i) 194 | { 195 | Task.Delay(1).GetAwaiter().GetResult(); 196 | var accessToken = Interlocked.CompareExchange(ref _accessToken, null, null); 197 | if (accessToken != null) 198 | return accessToken; 199 | } 200 | Interlocked.CompareExchange(ref _accessToken, string.Empty, null); 201 | 202 | if (string.IsNullOrEmpty(_accessToken)) 203 | InternalLogger.Warn("AccessToken LayoutRenderer - No AccessToken received from AzureServiceTokenProvider"); 204 | } 205 | else 206 | { 207 | if (string.IsNullOrEmpty(_accessToken)) 208 | InternalLogger.Debug("AccessToken LayoutRenderer - Missing AccessToken from AzureServiceTokenProvider"); 209 | } 210 | 211 | return _accessToken; 212 | } 213 | 214 | private async Task RefreshToken() 215 | { 216 | TimeSpan nextRefresh = TimeSpan.Zero; 217 | 218 | try 219 | { 220 | var authResult = await _tokenProvider.GetAuthenticationResultAsync(_resource, _tenantId, _cancellationTokenSource.Token).ConfigureAwait(false); 221 | if (string.IsNullOrEmpty(_accessToken)) 222 | InternalLogger.Debug("AccessToken LayoutRenderer - Acquired {0} AccessToken from AzureServiceTokenProvider", string.IsNullOrEmpty(authResult.Key) ? "Empty" : "Valid"); 223 | 224 | Interlocked.Exchange(ref _accessToken, authResult.Key); 225 | 226 | if (_accessTokenRefreshed != null) 227 | { 228 | lock (_lockObject) 229 | _accessTokenRefreshed?.Invoke(this, EventArgs.Empty); 230 | } 231 | 232 | // Renew the token 5 minutes before it expires. 233 | nextRefresh = (authResult.Value - DateTimeOffset.UtcNow) - TimeSpan.FromMinutes(5); 234 | } 235 | catch (Exception ex) 236 | { 237 | InternalLogger.Error(ex, "AccessToken LayoutRenderer - Failed getting AccessToken from AzureServiceTokenProvider"); 238 | } 239 | finally 240 | { 241 | if (!_cancellationTokenSource.IsCancellationRequested) 242 | { 243 | if (nextRefresh < TimeSpan.FromMilliseconds(500)) 244 | nextRefresh = TimeSpan.FromMilliseconds(500); 245 | _refreshTimer.Change((int)nextRefresh.TotalMilliseconds, Timeout.Infinite); 246 | } 247 | } 248 | } 249 | 250 | public void Dispose() 251 | { 252 | _refreshTimer.Change(Timeout.Infinite, Timeout.Infinite); 253 | _cancellationTokenSource.Cancel(); 254 | _refreshTimer.Dispose(); 255 | _cancellationTokenSource.Dispose(); 256 | _accessTokenRefreshed = null; 257 | } 258 | 259 | public event EventHandler AccessTokenRefreshed 260 | { 261 | add 262 | { 263 | lock (_lockObject) 264 | { 265 | var wasCancelled = _accessTokenRefreshed == null; 266 | _accessTokenRefreshed += value; 267 | if (wasCancelled) 268 | { 269 | _cancellationTokenSource = new CancellationTokenSource(); 270 | _refreshTimer.Change(1, Timeout.Infinite); 271 | } 272 | } 273 | } 274 | remove 275 | { 276 | lock (_lockObject) 277 | { 278 | _accessTokenRefreshed -= value; 279 | if (_accessTokenRefreshed == null) 280 | { 281 | _cancellationTokenSource.Cancel(); 282 | _refreshTimer.Change(Timeout.Infinite, Timeout.Infinite); 283 | } 284 | } 285 | } 286 | } 287 | 288 | private event EventHandler _accessTokenRefreshed; 289 | } 290 | 291 | internal interface IAzureServiceTokenProviderService 292 | { 293 | Task> GetAuthenticationResultAsync(string resource, string tenantId, CancellationToken cancellationToken); 294 | } 295 | 296 | private sealed class AzureServiceTokenProviderService : IAzureServiceTokenProviderService 297 | { 298 | private readonly AzureServiceTokenProvider _tokenProvider; 299 | 300 | public AzureServiceTokenProviderService(string connectionString, string azureAdInstance) 301 | { 302 | _tokenProvider = string.IsNullOrEmpty(azureAdInstance) ? new AzureServiceTokenProvider(connectionString) : new AzureServiceTokenProvider(connectionString, azureAdInstance); 303 | } 304 | 305 | public async Task> GetAuthenticationResultAsync(string resource, string tenantId, CancellationToken cancellationToken) 306 | { 307 | var result = await _tokenProvider.GetAuthenticationResultAsync(resource, tenantId, cancellationToken).ConfigureAwait(false); 308 | return new KeyValuePair(result?.AccessToken, result?.ExpiresOn ?? default(DateTimeOffset)); 309 | } 310 | } 311 | } 312 | } 313 | --------------------------------------------------------------------------------