├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── appveyor.yml ├── logo64.png ├── src ├── NLog.Extensions.AzureAccessToken │ ├── AccessTokenLayoutRenderer.cs │ ├── NLog.Extensions.AzureAccessToken.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── README.md ├── NLog.Extensions.AzureBlobStorage │ ├── BlobStorageTarget.cs │ ├── ICloudBlobService.cs │ ├── NLog.Extensions.AzureBlobStorage.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── README.md ├── NLog.Extensions.AzureCosmosTable │ ├── ICloudTableService.cs │ ├── NLog.Extensions.AzureCosmosTable.csproj │ ├── NLogEntity.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── README.md │ └── TableStorageTarget.cs ├── NLog.Extensions.AzureDataTables │ ├── DataTablesTarget.cs │ ├── ICloudTableService.cs │ ├── NLog.Extensions.AzureDataTables.csproj │ ├── NLogEntity.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── README.md ├── NLog.Extensions.AzureEventGrid │ ├── EventGridTarget.cs │ ├── IEventGridService.cs │ ├── NLog.Extensions.AzureEventGrid.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── README.md ├── NLog.Extensions.AzureEventHub │ ├── EventHubTarget.cs │ ├── IEventHubService.cs │ ├── NLog.Extensions.AzureEventHub.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── README.md ├── NLog.Extensions.AzureQueueStorage │ ├── ICloudQueueService.cs │ ├── NLog.Extensions.AzureQueueStorage.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── QueueStorageTarget.cs │ └── README.md ├── NLog.Extensions.AzureServiceBus │ ├── ICloudServiceBus.cs │ ├── NLog.Extensions.AzureServiceBus.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── README.md │ └── ServiceBusTarget.cs ├── NLog.Extensions.AzureStorage.sln └── NLog.Extensions.AzureStorage │ ├── AzureCredentialHelper.cs │ ├── AzureStorageNameCache.cs │ └── SortHelpers.cs └── test ├── NLog.Extensions.AzureAccessToken.Tests ├── AccessTokenLayoutRendererTests.cs ├── AzureServiceTokenProviderMock.cs └── NLog.Extensions.AzureAccessToken.Tests.csproj ├── NLog.Extensions.AzureBlobStorage.Tests ├── BlobStorageTargetTest.cs ├── CloudBlobServiceMock.cs └── NLog.Extensions.AzureBlobStorage.Tests.csproj ├── NLog.Extensions.AzureCosmosTable.Tests ├── CloudTableServiceMock.cs ├── NLog.Extensions.AzureCosmosTable.Tests.csproj └── TableStorageTargetTest.cs ├── NLog.Extensions.AzureDataTables.Tests ├── CloudTableServiceMock.cs ├── DataTablesTargetTests.cs └── NLog.Extensions.AzureDataTables.Tests.csproj ├── NLog.Extensions.AzureEventGrid.Tests ├── EventGridServiceMock.cs ├── EventGridTargetTest.cs └── NLog.Extensions.AzureEventGrid.Tests.csproj ├── NLog.Extensions.AzureEventHub.Tests ├── EventHubServiceMock.cs ├── EventHubTargetTest.cs └── NLog.Extensions.AzureEventHub.Tests.csproj ├── NLog.Extensions.AzureQueueStorage.Tests ├── CloudQueueServiceMock.cs ├── NLog.Extensions.AzureQueueStorage.Tests.csproj └── QueueStorageTargetTest.cs ├── NLog.Extensions.AzureServiceBus.Tests ├── NLog.Extensions.AzureServiceBus.Tests.csproj ├── ServiceBusMock.cs └── ServiceBusTargetTest.cs └── NLog.Extensions.AzureStorage.IntegrationTest ├── App.config ├── NLog.Extensions.AzureStorage.IntegrationTest.csproj ├── Program.cs ├── Properties └── AssemblyInfo.cs └── packages.config /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | image: Visual Studio 2022 3 | configuration: Release 4 | 5 | build_script: 6 | - 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 7 | - 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 8 | - 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 9 | - 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 10 | - 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 11 | - 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 12 | - 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 13 | 14 | test_script: 15 | - cmd: dotnet test test\NLog.Extensions.AzureBlobStorage.Tests /p:Configuration=Release --verbosity minimal 16 | - cmd: dotnet test test\NLog.Extensions.AzureDataTables.Tests /p:Configuration=Release --verbosity minimal 17 | - cmd: dotnet test test\NLog.Extensions.AzureQueueStorage.Tests /p:Configuration=Release --verbosity minimal 18 | - cmd: dotnet test test\NLog.Extensions.AzureEventGrid.Tests /p:Configuration=Release --verbosity minimal 19 | - cmd: dotnet test test\NLog.Extensions.AzureEventHub.Tests /p:Configuration=Release --verbosity minimal 20 | - cmd: dotnet test test\NLog.Extensions.AzureServiceBus.Tests /p:Configuration=Release --verbosity minimal 21 | 22 | artifacts: 23 | - path: '**\NLog.Extensions.Azure*.nupkg' 24 | - path: '**\NLog.Extensions.Azure*.snupkg' -------------------------------------------------------------------------------- /logo64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JDetmar/NLog.Extensions.AzureStorage/93e7b6e3ef405676888299ae51d2238fac706c92/logo64.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureAccessToken/NLog.Extensions.AzureAccessToken.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard1.4;net461;net452 5 | true 6 | true 7 | 8 | 2.3.0 9 | 10 | NLog AccessTokenLayoutRenderer for application authentication and acquire AccessToken for resource. 11 | jdetmar 12 | $([System.DateTime]::Now.ToString(yyyy)) 13 | Copyright (c) $(CurrentYear) - jdetmar 14 | 15 | NLog;Azure;AccessToken;Authentication;AppAuthentication 16 | logo64.png 17 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 18 | git 19 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 20 | MIT 21 | 22 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureAccessToken/README.md 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /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.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. -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureBlobStorage/ICloudBlobService.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 | 8 | namespace NLog.Extensions.AzureStorage 9 | { 10 | interface ICloudBlobService 11 | { 12 | void Connect(string connectionString, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientId, string clientSecret, IDictionary blobMetadata, IDictionary blobTags); 13 | Task AppendFromByteArrayAsync(string containerName, string blobName, string contentType, byte[] buffer, CancellationToken cancellationToken); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureBlobStorage/NLog.Extensions.AzureBlobStorage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 4.4.2 8 | 9 | NLog BlobStorageTarget for writing to Azure Cloud Blob Storage 10 | jdetmar 11 | $([System.DateTime]::Now.ToString(yyyy)) 12 | Copyright (c) $(CurrentYear) - jdetmar 13 | 14 | NLog;azure;CloudBlob;blob;storage;log;logging 15 | logo64.png 16 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 17 | git 18 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 19 | MIT 20 | 21 | - Added support for authentication with a service principal using a secret. by @ssteiner 22 | 23 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureBlobStorage/README.md 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /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.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 | ### Parameters 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 storage connection string. Ex. `UseDevelopmentStorage=true;` 41 | 42 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is acquired from DefaultAzureCredential. 43 | 44 | _clientIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityClientId. 45 | 46 | _resourceIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityResourceId. 47 | 48 | _tenantIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential / ClientSecretCredential. 49 | 50 | _sharedAccessSignature_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureSasCredential 51 | 52 | _accountName_ - Alternative to ConnectionString. Used together with ServiceUri. Input for StorageSharedKeyCredential-AccountName 53 | 54 | _accessKey_ - Alternative to ConnectionString. Used together with ServiceUri. Input for StorageSharedKeyCredential-AccessKey 55 | 56 | _clientId_ - Alternative to ConnectionString. Instantiates the `BlobServiceClient` using a `ClientSecretCredential` for authentication. Requires `TenantIdentity` and `ClientSecret`. 57 | 58 | _clientSecret_ - Secret when using ClientId. Instantiates the `BlobServiceClient` using a `ClientSecretCredential` for authentication. Requires `TenantIdentity` and `ClientId`. 59 | 60 | ### Batching Policy 61 | 62 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 63 | 64 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 65 | 66 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 67 | 68 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 69 | 70 | ### Retry Policy 71 | 72 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 73 | 74 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 75 | 76 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 77 | 78 | ## Azure Blob Storage Emulator 79 | 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. 80 | 81 | It will fail with the following error: 82 | ``` 83 | Azure.RequestFailedException: This feature is not currently supported by the Storage Emulator 84 | ``` 85 | 86 | Instead one can try an alternative Azure Storage Emulator like [Azurite](https://github.com/azure/azurite) 87 | 88 | ## Azure Identity Environment 89 | When using `ServiceUri` (Instead of ConnectionString), then `DefaultAzureCredential` is used for Azure Identity which supports environment variables: 90 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 91 | - `AZURE_TENANT_ID` - For TenantId 92 | 93 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 94 | 95 | ## Azure ConnectionString 96 | 97 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 98 | 99 | #### Lookup ConnectionString from appsettings.json 100 | > `connectionString="${configsetting:ConnectionStrings.AzureBlob}"` 101 | 102 | * Example appsettings.json on .NetCore: 103 | 104 | ```json 105 | { 106 | "ConnectionStrings": { 107 | "AzureBlob": "UseDevelopmentStorage=true;" 108 | } 109 | } 110 | ``` 111 | 112 | #### Lookup ConnectionString from app.config 113 | 114 | > `connectionString="${appsetting:ConnectionStrings.AzureBlob}"` 115 | 116 | * Example app.config on .NetFramework: 117 | 118 | ```xml 119 | 120 | 121 | 122 | 123 | 124 | ``` 125 | 126 | #### Lookup ConnectionString from environment-variable 127 | 128 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 129 | 130 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 131 | 132 | > `connectionString="${gdc:AzureBlobConnectionString}"` 133 | 134 | * Example code for setting GDC-value: 135 | 136 | ```c# 137 | NLog.GlobalDiagnosticsContext.Set("AzureBlobConnectionString", "UseDevelopmentStorage=true;"); 138 | ``` -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureCosmosTable/ICloudTableService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | #if NETSTANDARD2_0 || NET461 4 | using Microsoft.Azure.Cosmos.Table; 5 | #else 6 | using Microsoft.WindowsAzure.Storage; 7 | using Microsoft.WindowsAzure.Storage.Table; 8 | #endif 9 | 10 | namespace NLog.Extensions.AzureStorage 11 | { 12 | interface ICloudTableService 13 | { 14 | void Connect(string connectionString, int? defaultTimeToLiveSeconds); 15 | Task ExecuteBatchAsync(string tableName, TableBatchOperation tableOperation, CancellationToken cancellationToken); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureCosmosTable/NLog.Extensions.AzureCosmosTable.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net45;net461;netstandard1.3;netstandard2.0 5 | true 6 | true 7 | 8 | 2.8.0 9 | 10 | NLog TableStorageTarget for writing to Azure Table Storage OR Azure CosmosDB Tables 11 | jdetmar 12 | $([System.DateTime]::Now.ToString(yyyy)) 13 | Copyright (c) $(CurrentYear) - jdetmar 14 | 15 | NLog;azure;CloudTable;cosmos;cosmosdb;documentdb;table;storage;log;logging 16 | logo64.png 17 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 18 | git 19 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 20 | MIT 21 | 22 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureCosmosTable/README.md 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureCosmosTable/NLogEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if NETSTANDARD2_0 || NET461 3 | using Microsoft.Azure.Cosmos.Table; 4 | #else 5 | using Microsoft.WindowsAzure.Storage; 6 | using Microsoft.WindowsAzure.Storage.Table; 7 | #endif 8 | using NLog.Layouts; 9 | 10 | namespace NLog.Extensions.AzureStorage 11 | { 12 | public class NLogEntity: TableEntity 13 | { 14 | public string LogTimeStamp { get; set; } 15 | public string Level { get; set; } 16 | public string LoggerName { get; set; } 17 | public string Message { get; set; } 18 | public string Exception { get; set; } 19 | public string InnerException { get; set; } 20 | public string StackTrace { get; set; } 21 | public string FullMessage { get; set; } 22 | public string MachineName { get; set; } 23 | 24 | public NLogEntity(LogEventInfo logEvent, string layoutMessage, string machineName, string partitionKey, string rowKey, string logTimeStampFormat) 25 | { 26 | FullMessage = layoutMessage; 27 | Level = logEvent.Level.Name; 28 | LoggerName = logEvent.LoggerName; 29 | Message = logEvent.Message; 30 | LogTimeStamp = logEvent.TimeStamp.ToString(logTimeStampFormat); 31 | MachineName = machineName; 32 | if(logEvent.Exception != null) 33 | { 34 | var exception = logEvent.Exception; 35 | var innerException = exception.InnerException; 36 | if (exception is AggregateException aggregateException) 37 | { 38 | var innerExceptions = aggregateException.Flatten(); 39 | if (innerExceptions.InnerExceptions?.Count == 1) 40 | { 41 | exception = innerExceptions.InnerExceptions[0]; 42 | innerException = null; 43 | } 44 | else 45 | { 46 | innerException = innerExceptions; 47 | } 48 | } 49 | 50 | Exception = string.Concat(exception.Message, " - ", exception.GetType().ToString()); 51 | StackTrace = exception.StackTrace; 52 | if (innerException != null) 53 | { 54 | InnerException = innerException.ToString(); 55 | } 56 | } 57 | RowKey = rowKey; 58 | PartitionKey = partitionKey; 59 | } 60 | public NLogEntity() { } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureCosmosTable/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.AzureCosmosTable.Tests")] 8 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureCosmosTable/README.md: -------------------------------------------------------------------------------- 1 | # Azure Table Storage and Cosmos Table 2 | 3 | | Package Name | NuGet | Description | 4 | | ------------------------------------- | :-------------------: | ----------- | 5 | | **NLog.Extensions.AzureCosmosTable** | [![NuGet](https://img.shields.io/nuget/v/NLog.Extensions.AzureCosmosTable.svg)](https://www.nuget.org/packages/NLog.Extensions.AzureCosmosTable/) | Azure Table Storage or Azure CosmosDb Tables | 6 | 7 | ## Table Configuration 8 | Supports both Azure Storage Tables and CosmosDB Tables. 9 | 10 | ### Syntax 11 | ```xml 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | ``` 27 | ### Parameters 28 | 29 | _name_ - Name of the target. 30 | 31 | _layout_ - Text to be rendered. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 32 | 33 | _connectionString_ - Azure storage connection string. Must provide either _connectionString_ or _connectionStringKey_. 34 | 35 | _connectionStringKey_ - App key name of Azure storage connection string. Must provide either _connectionString_ or _connectionStringKey_. 36 | 37 | _tableName_ - Azure table name. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 38 | 39 | _rowKey_ - Azure Table RowKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = "InverseTicks_${guid}" 40 | 41 | _partitionKey_ - Azure PartitionKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = `${logger}` 42 | 43 | _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. 44 | 45 | _timeToLiveSeconds_ - Default [Time-to-live](https://docs.microsoft.com/en-us/azure/cosmos-db/time-to-live) (TTL) for CosmosDb rows in seconds. Default = 0 (Off - Forever) 46 | 47 | _timeToLiveDays_ - Default [Time-to-live](https://docs.microsoft.com/en-us/azure/cosmos-db/time-to-live) (TTL) for CosmosDb rows in days. Default = 0 (Off - Forever) 48 | 49 | ### DynamicTableEntity 50 | Instead of using the predefined NLogEntity-properties, then one can specify wanted properties: 51 | 52 | ```xml 53 | 54 | 55 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | ``` 72 | 73 | It will by default include the hardcoded property `LogTimeStamp` of type DateTime that contains `LogEventInfo.TimeStamp.ToUniversalTime()`. 74 | - This can be overriden by having `` as the first property, where empty property-value means leave out. 75 | 76 | ### Batching Policy 77 | 78 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 79 | 80 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 81 | 82 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 83 | 84 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 85 | 86 | ### Retry Policy 87 | 88 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 89 | 90 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 91 | 92 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 93 | 94 | ## Azure ConnectionString 95 | 96 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 97 | 98 | #### Lookup ConnectionString from appsettings.json 99 | 100 | > `connectionString="${configsetting:ConnectionStrings.AzureTable}"` 101 | 102 | * Example appsettings.json on .NetCore: 103 | 104 | ```json 105 | { 106 | "ConnectionStrings": { 107 | "AzureTable": "Server=tcp:server.database.windows.net;" 108 | } 109 | } 110 | ``` 111 | 112 | #### Lookup ConnectionString from app.config 113 | 114 | > `connectionString="${appsetting:ConnectionStrings.AzureTable}"` 115 | 116 | * Example app.config on .NetFramework: 117 | 118 | ```xml 119 | 120 | 121 | 122 | 123 | 124 | ``` 125 | 126 | #### Lookup ConnectionString from environment-variable 127 | 128 | > `connectionString="${environment:AZURESQLCONNSTR_CONNECTION_STRING}"` 129 | 130 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 131 | 132 | > `connectionString="${gdc:AzureTableConnectionString}"` 133 | 134 | * Example code for setting GDC-value: 135 | 136 | ```c# 137 | NLog.GlobalDiagnosticsContext.Set("AzureTableConnectionString", "Server=tcp:server.database.windows.net;"); 138 | ``` -------------------------------------------------------------------------------- /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 | 6 | namespace NLog.Extensions.AzureStorage 7 | { 8 | interface ICloudTableService 9 | { 10 | void Connect(string connectionString, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey); 11 | Task SubmitTransactionAsync(string tableName, IEnumerable tableTransaction, CancellationToken cancellationToken); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureDataTables/NLog.Extensions.AzureDataTables.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 4.4.1 8 | 9 | NLog DataTablesTarget for writing to Azure Tables in Azure storage accounts or Azure Cosmos DB table API 10 | jdetmar 11 | $([System.DateTime]::Now.ToString(yyyy)) 12 | Copyright (c) $(CurrentYear) - jdetmar 13 | 14 | NLog;azure;CloudTable;cosmos;cosmosdb;documentdb;table;tables;storage;log;logging 15 | logo64.png 16 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 17 | git 18 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 19 | MIT 20 | 21 | - Assign WorkloadIdentityClientId from ClientIdentity 22 | - Changed Layout default-value to ${message} since LogEvent is split into columns 23 | - Added automatic truncate when column-values has string-length above 32K 24 | - Skips adding column values when empty string and configured IncludeEmptyValue = false 25 | 26 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureDataTables/README.md 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /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 | public class NLogEntity: ITableEntity 8 | { 9 | public string PartitionKey { get; set; } 10 | public string RowKey { get; set; } 11 | public DateTimeOffset? Timestamp { get; set; } 12 | public ETag ETag { get; set; } 13 | public string LogTimeStamp { get; set; } 14 | public string Level { get; set; } 15 | public string LoggerName { get; set; } 16 | public string Message { get; set; } 17 | public string Exception { get; set; } 18 | public string InnerException { get; set; } 19 | public string StackTrace { get; set; } 20 | public string FullMessage { get; set; } 21 | public string MachineName { get; set; } 22 | 23 | public NLogEntity(LogEventInfo logEvent, string layoutMessage, string machineName, string partitionKey, string rowKey, string logTimeStampFormat) 24 | { 25 | FullMessage = TruncateWhenTooBig(layoutMessage); 26 | Level = logEvent.Level.Name; 27 | LoggerName = logEvent.LoggerName; 28 | Message = TruncateWhenTooBig(logEvent.Message); 29 | LogTimeStamp = logEvent.TimeStamp.ToString(logTimeStampFormat); 30 | MachineName = machineName; 31 | if(logEvent.Exception != null) 32 | { 33 | var exception = logEvent.Exception; 34 | var innerException = exception.InnerException; 35 | if (exception is AggregateException aggregateException) 36 | { 37 | if (aggregateException.InnerExceptions?.Count == 1 && !(aggregateException.InnerExceptions[0] is AggregateException)) 38 | { 39 | exception = aggregateException.InnerExceptions[0]; 40 | innerException = exception.InnerException; 41 | } 42 | else 43 | { 44 | var flatten = aggregateException.Flatten(); 45 | if (flatten.InnerExceptions?.Count == 1) 46 | { 47 | exception = flatten.InnerExceptions[0]; 48 | innerException = exception.InnerException; 49 | } 50 | else 51 | { 52 | innerException = flatten; 53 | } 54 | } 55 | } 56 | 57 | Exception = string.Concat(exception.Message, " - ", exception.GetType().ToString()); 58 | StackTrace = TruncateWhenTooBig(exception.StackTrace); 59 | if (innerException != null) 60 | { 61 | var innerExceptionText = innerException.ToString(); 62 | InnerException = TruncateWhenTooBig(innerExceptionText); 63 | } 64 | } 65 | RowKey = rowKey; 66 | PartitionKey = partitionKey; 67 | } 68 | 69 | private static string TruncateWhenTooBig(string stringValue) 70 | { 71 | return stringValue?.Length >= Targets.DataTablesTarget.ColumnStringValueMaxSize ? 72 | stringValue.Substring(0, Targets.DataTablesTarget.ColumnStringValueMaxSize - 1) : stringValue; 73 | } 74 | 75 | public NLogEntity() { } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /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.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 | 23 | 24 | ``` 25 | ### Parameters 26 | 27 | _name_ - Name of the target. 28 | 29 | _layout_ - Text to be rendered. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 30 | 31 | _connectionString_ - Azure storage connection string. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 32 | 33 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is acquired from DefaultAzureCredential. 34 | 35 | _clientIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityClientId. 36 | 37 | _resourceIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityResourceId. 38 | 39 | _tenantIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential. 40 | 41 | _sharedAccessSignature_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureSasCredential 42 | 43 | _accountName_ - Alternative to ConnectionString. Used together with ServiceUri. Input for TableSharedKeyCredential storage account-name. 44 | 45 | _accessKey_ - Alternative to ConnectionString. Used together with ServiceUri. Input for TableSharedKeyCredential account-access-key. 46 | 47 | _tableName_ - Azure table name. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 48 | 49 | _rowKey_ - Azure Table RowKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = "InverseTicks_${guid}" 50 | 51 | _partitionKey_ - Azure PartitionKey. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Default = `${logger}` 52 | 53 | _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. 54 | 55 | ### Dynamic TableEntity 56 | Instead of using the predefined NLogEntity-properties, then one can specify wanted properties: 57 | 58 | ```xml 59 | 60 | 61 | 62 | 63 | 64 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ``` 77 | 78 | It will by default include the hardcoded property `LogTimeStamp` of type DateTime that contains `LogEventInfo.TimeStamp.ToUniversalTime()`. 79 | - This can be overriden by having `` as the first property, where empty property-value means leave out. 80 | 81 | ### Batching Policy 82 | 83 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 84 | 85 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 86 | 87 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 88 | 89 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 90 | 91 | ### Retry Policy 92 | 93 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 94 | 95 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 96 | 97 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 98 | 99 | ## Azure Identity Environment 100 | When using `ServiceUri` (Instead of ConnectionString), then `DefaultAzureCredential` is used for Azure Identity which supports environment variables: 101 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 102 | - `AZURE_TENANT_ID` - For TenantId 103 | 104 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 105 | 106 | ## Azure Table Service Size Limits 107 | 108 | There are restrictions for how big column values can be: 109 | 110 | - PartitionKey has max limit of 1024 characters 111 | - RowKey has max limit of 1024 characters 112 | - Column string-Values has max limit of 32.768 characters 113 | 114 | 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. 115 | 116 | ## Azure ConnectionString 117 | 118 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 119 | 120 | #### Lookup ConnectionString from appsettings.json 121 | 122 | > `connectionString="${configsetting:ConnectionStrings.AzureTable}"` 123 | 124 | * Example appsettings.json on .NetCore: 125 | 126 | ```json 127 | { 128 | "ConnectionStrings": { 129 | "AzureTable": "Server=tcp:server.database.windows.net;" 130 | } 131 | } 132 | ``` 133 | 134 | #### Lookup ConnectionString from app.config 135 | 136 | > `connectionString="${appsetting:ConnectionStrings.AzureTable}"` 137 | 138 | * Example app.config on .NetFramework: 139 | 140 | ```xml 141 | 142 | 143 | 144 | 145 | 146 | ``` 147 | 148 | #### Lookup ConnectionString from environment-variable 149 | 150 | > `connectionString="${environment:AZURESQLCONNSTR_CONNECTION_STRING}"` 151 | 152 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 153 | 154 | > `connectionString="${gdc:AzureTableConnectionString}"` 155 | 156 | * Example code for setting GDC-value: 157 | 158 | ```c# 159 | NLog.GlobalDiagnosticsContext.Set("AzureTableConnectionString", "Server=tcp:server.database.windows.net;"); 160 | ``` -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventGrid/EventGridTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using System.Threading; 6 | using Azure; 7 | using Azure.Messaging; 8 | using Azure.Messaging.EventGrid; 9 | using NLog.Common; 10 | using NLog.Config; 11 | using NLog.Layouts; 12 | using NLog.Extensions.AzureStorage; 13 | 14 | namespace NLog.Targets 15 | { 16 | /// 17 | /// Azure Event Grid NLog Target 18 | /// 19 | [Target("AzureEventGrid")] 20 | public class EventGridTarget : AsyncTaskTarget 21 | { 22 | private readonly IEventGridService _eventGridService; 23 | private readonly char[] _reusableEncodingBuffer = new char[32 * 1024]; // Avoid large-object-heap 24 | 25 | /// 26 | /// The topic endpoint. For example, "https://TOPIC-NAME.REGION-NAME-1.eventgrid.azure.net/api/events" 27 | /// 28 | public Layout Topic { get; set; } 29 | 30 | /// 31 | /// A resource path relative to the topic path. 32 | /// 33 | public Layout GridEventSubject { get; set; } 34 | 35 | /// 36 | /// Identifies the context in which an event happened. The combination of id and source must be unique for each distinct event. 37 | /// 38 | public Layout CloudEventSource { get; set; } 39 | 40 | /// 41 | /// The type of the event that occurred. For example, "Contoso.Items.ItemReceived". 42 | /// 43 | public Layout EventType { get; set; } 44 | 45 | /// 46 | /// Content type of the payload. A content type different from "application/json" should be specified if payload is not JSON. 47 | /// 48 | public Layout ContentType 49 | { 50 | get => _contentType; 51 | set 52 | { 53 | _contentType = value; 54 | if (!_dataFormatJson.HasValue && value?.ToString()?.IndexOf("json", StringComparison.OrdinalIgnoreCase) >= 0) 55 | { 56 | _dataFormatJson = true; 57 | } 58 | } 59 | } 60 | private Layout _contentType; 61 | 62 | /// 63 | /// The serialize format of the data object. Json / Binary 64 | /// 65 | public string DataFormat 66 | { 67 | get => _dataFormatJson == true ? "Json" : "Binary"; 68 | set => _dataFormatJson = value?.IndexOf("Json", StringComparison.OrdinalIgnoreCase) >= 0; 69 | } 70 | private bool? _dataFormatJson; 71 | 72 | /// 73 | /// The schema version of the data object. 74 | /// 75 | public Layout DataSchema { get; set; } 76 | 77 | /// 78 | /// Input for 79 | /// 80 | public Layout TenantIdentity { get; set; } 81 | 82 | /// 83 | /// Input for 84 | /// 85 | public Layout ResourceIdentity { get; set; } 86 | 87 | /// 88 | /// Input for with ManagedIdentityClientId / WorkloadIdentityClientId 89 | /// 90 | public Layout ClientIdentity { get; set; } 91 | 92 | /// 93 | /// Alternative to DefaultAzureCredential. Input for AzureKeyCredential for EventGridPublisherClient constructor 94 | /// 95 | public Layout AccessKey { get; set; } 96 | 97 | /// 98 | /// Alternative to DefaultAzureCredential. Input for AzureSasCredential for EventGridPublisherClient constructor 99 | /// 100 | public Layout SharedAccessSignature { get; set; } 101 | 102 | /// 103 | /// Gets a list of message properties aka. custom CloudEvent Extension Attributes 104 | /// 105 | [ArrayParameter(typeof(TargetPropertyWithContext), "messageproperty")] 106 | public IList MessageProperties { get => ContextProperties; } 107 | 108 | public EventGridTarget() 109 | : this(new EventGridService()) 110 | { 111 | RetryDelayMilliseconds = 100; 112 | } 113 | 114 | internal EventGridTarget(IEventGridService eventGridService) 115 | { 116 | _eventGridService = eventGridService; 117 | } 118 | 119 | /// 120 | protected override void InitializeTarget() 121 | { 122 | base.InitializeTarget(); 123 | 124 | for (int i = 0; i < MessageProperties.Count; ++i) 125 | { 126 | var messageProperty = MessageProperties[i]; 127 | if (!IsValidExtensionAttribue(messageProperty.Name)) 128 | { 129 | var messagePropertyName = (messageProperty.Name ?? string.Empty).Trim().ToLowerInvariant(); 130 | if (!IsValidExtensionAttribue(messagePropertyName)) 131 | { 132 | messagePropertyName = string.Join(string.Empty, System.Linq.Enumerable.Where(messagePropertyName, chr => !IsInvalidExtensionAttribueChar(chr))); 133 | } 134 | 135 | InternalLogger.Debug("AzureEventGridTarget(Name={0}): Fixing MessageProperty-Name from '{1}' to '{2}'", Name, messageProperty.Name, messagePropertyName); 136 | messageProperty.Name = messagePropertyName; 137 | } 138 | } 139 | 140 | string topic = string.Empty; 141 | string tenantIdentity = string.Empty; 142 | string resourceIdentity = string.Empty; 143 | string clientIdentity = string.Empty; 144 | string sharedAccessSignature = string.Empty; 145 | string accessKey = string.Empty; 146 | 147 | var defaultLogEvent = LogEventInfo.CreateNullEvent(); 148 | 149 | try 150 | { 151 | topic = Topic?.Render(defaultLogEvent); 152 | tenantIdentity = TenantIdentity?.Render(defaultLogEvent); 153 | resourceIdentity = ResourceIdentity?.Render(defaultLogEvent); 154 | clientIdentity = ClientIdentity?.Render(defaultLogEvent); 155 | sharedAccessSignature = SharedAccessSignature?.Render(defaultLogEvent); 156 | accessKey = AccessKey?.Render(defaultLogEvent); 157 | 158 | _eventGridService.Connect(topic, tenantIdentity, resourceIdentity, clientIdentity, sharedAccessSignature, accessKey); 159 | InternalLogger.Debug("AzureEventGridTarget(Name={0}): Initialized", Name); 160 | } 161 | catch (Exception ex) 162 | { 163 | InternalLogger.Error(ex, "AzureEventGridTarget(Name={0}): Failed to create EventGridPublisherClient with Topic={1}.", Name, topic); 164 | throw; 165 | } 166 | } 167 | 168 | protected override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken) 169 | { 170 | try 171 | { 172 | if (CloudEventSource is null) 173 | { 174 | var gridEvent = CreateGridEvent(logEvent); 175 | return _eventGridService.SendEventAsync(gridEvent, cancellationToken); 176 | } 177 | else 178 | { 179 | var cloudEvent = CreateCloudEvent(logEvent); 180 | return _eventGridService.SendEventAsync(cloudEvent, cancellationToken); 181 | } 182 | } 183 | catch (Exception ex) 184 | { 185 | InternalLogger.Error(ex, "AzureEventGridTarget(Name={0}): Failed sending logevent to Topic={1}", Name, _eventGridService?.Topic); 186 | throw; 187 | } 188 | } 189 | 190 | private sealed class EventGridService : IEventGridService 191 | { 192 | EventGridPublisherClient _client; 193 | 194 | public string Topic { get; private set; } 195 | 196 | public void Connect(string topic, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string accessKey) 197 | { 198 | Topic = topic; 199 | 200 | if (!string.IsNullOrWhiteSpace(sharedAccessSignature)) 201 | { 202 | _client = new EventGridPublisherClient(new Uri(topic), new AzureSasCredential(sharedAccessSignature)); 203 | } 204 | else if (!string.IsNullOrWhiteSpace(accessKey)) 205 | { 206 | _client = new EventGridPublisherClient(new Uri(topic), new AzureKeyCredential(accessKey)); 207 | } 208 | else 209 | { 210 | var tokenCredentials = AzureCredentialHelpers.CreateTokenCredentials(clientIdentity, tenantIdentity, resourceIdentifier); 211 | _client = new EventGridPublisherClient(new Uri(topic), tokenCredentials); 212 | } 213 | } 214 | 215 | public Task SendEventAsync(EventGridEvent gridEvent, CancellationToken cancellationToken) 216 | { 217 | return _client.SendEventAsync(gridEvent, cancellationToken); 218 | } 219 | 220 | public Task SendEventAsync(CloudEvent cloudEvent, CancellationToken cancellationToken) 221 | { 222 | return _client.SendEventAsync(cloudEvent, cancellationToken); 223 | } 224 | } 225 | 226 | private CloudEvent CreateCloudEvent(LogEventInfo logEvent) 227 | { 228 | var eventDataBody = RenderLogEvent(Layout, logEvent) ?? string.Empty; 229 | var eventSource = RenderLogEvent(CloudEventSource, logEvent) ?? string.Empty; 230 | var eventType = RenderLogEvent(EventType, logEvent) ?? string.Empty; 231 | var eventDataSchema = RenderLogEvent(DataSchema, logEvent) ?? string.Empty; 232 | var eventContentType = RenderLogEvent(ContentType, logEvent) ?? string.Empty; 233 | var cloudEventFormat = _dataFormatJson == true ? CloudEventDataFormat.Json : CloudEventDataFormat.Binary; 234 | var cloudEvent = new CloudEvent(eventSource, eventType, new BinaryData(EncodeToUTF8(eventDataBody)), eventContentType, cloudEventFormat); 235 | cloudEvent.Time = logEvent.TimeStamp.ToUniversalTime(); 236 | 237 | if (!string.IsNullOrEmpty(eventDataSchema)) 238 | { 239 | cloudEvent.DataSchema = eventDataSchema; 240 | } 241 | 242 | for (int i = 0; i < MessageProperties.Count; ++i) 243 | { 244 | var messageProperty = MessageProperties[i]; 245 | if (string.IsNullOrEmpty(messageProperty.Name)) 246 | continue; 247 | 248 | var propertyValue = RenderLogEvent(messageProperty.Layout, logEvent); 249 | if (string.IsNullOrEmpty(propertyValue) && !messageProperty.IncludeEmptyValue) 250 | continue; 251 | 252 | cloudEvent.ExtensionAttributes.Add(messageProperty.Name, propertyValue); 253 | } 254 | 255 | return cloudEvent; 256 | } 257 | 258 | private EventGridEvent CreateGridEvent(LogEventInfo logEvent) 259 | { 260 | var eventDataBody = RenderLogEvent(Layout, logEvent) ?? string.Empty; 261 | var eventSubject = RenderLogEvent(GridEventSubject, logEvent) ?? string.Empty; 262 | var eventType = RenderLogEvent(EventType, logEvent) ?? string.Empty; 263 | var eventDataSchema = RenderLogEvent(DataSchema, logEvent) ?? string.Empty; 264 | var gridEvent = new EventGridEvent(eventSubject, eventType, eventDataSchema, new BinaryData(EncodeToUTF8(eventDataBody))); 265 | gridEvent.EventTime = logEvent.TimeStamp.ToUniversalTime(); 266 | return gridEvent; 267 | } 268 | 269 | private byte[] EncodeToUTF8(string eventDataBody) 270 | { 271 | if (eventDataBody.Length < _reusableEncodingBuffer.Length) 272 | { 273 | lock (_reusableEncodingBuffer) 274 | { 275 | eventDataBody.CopyTo(0, _reusableEncodingBuffer, 0, eventDataBody.Length); 276 | return Encoding.UTF8.GetBytes(_reusableEncodingBuffer, 0, eventDataBody.Length); 277 | } 278 | } 279 | else 280 | { 281 | return Encoding.UTF8.GetBytes(eventDataBody); // Calls string.ToCharArray() 282 | } 283 | } 284 | 285 | private static bool IsValidExtensionAttribue(string propertyValue) 286 | { 287 | for (int i = 0; i < propertyValue.Length; ++i) 288 | { 289 | char chr = propertyValue[i]; 290 | if (IsInvalidExtensionAttribueChar(chr)) 291 | { 292 | return false; 293 | } 294 | } 295 | return true; 296 | } 297 | 298 | private static bool IsInvalidExtensionAttribueChar(char chr) 299 | { 300 | return (chr < 'a' || chr > 'z') && (chr < '0' || chr > '9'); 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventGrid/IEventGridService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Azure.Messaging.EventGrid; 5 | using System.Threading.Tasks; 6 | using Azure.Messaging; 7 | using System.Threading; 8 | 9 | namespace NLog.Extensions.AzureStorage 10 | { 11 | internal interface IEventGridService 12 | { 13 | string Topic { get; } 14 | 15 | void Connect(string topic, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string accessKey); 16 | 17 | Task SendEventAsync(EventGridEvent gridEvent, CancellationToken cancellationToken); 18 | 19 | Task SendEventAsync(CloudEvent cloudEvent, CancellationToken cancellationToken); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventGrid/NLog.Extensions.AzureEventGrid.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 4.4.1 8 | 9 | NLog EventGridTarget for writing to Azure Event Grid 10 | jdetmar 11 | $([System.DateTime]::Now.ToString(yyyy)) 12 | Copyright (c) $(CurrentYear) - jdetmar 13 | 14 | NLog;azure;EventGrid;Event;Grid;CloudEvents;log;logging 15 | logo64.png 16 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 17 | git 18 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 19 | MIT 20 | 21 | - Assign WorkloadIdentityClientId from ClientIdentity 22 | - Updated to Azure.Messaging.EventGrid ver. 4.24.1 23 | 24 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureEventGrid/README.md 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /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.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 | 26 | 27 | 28 | 29 | ``` 30 | 31 | ### Parameters 32 | 33 | _name_ - Name of the target. 34 | 35 | _layout_ - Event data-payload. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 36 | 37 | _topic_ - Topic EndPoint Uri. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 38 | 39 | _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) 40 | 41 | _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) 42 | 43 | _eventType_ - Type of the event. Ex. "Contoso.Items.ItemReceived". [Layout](https://github.com/NLog/NLog/wiki/Layouts) 44 | 45 | _contentType_ - Content type of the data-payload. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 46 | 47 | _dataFormat_ - Format of the data-payload (Binary / Json). Default Binary. `String` 48 | 49 | _dataSchema_ - Schema version of the data-payload. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 50 | 51 | _clientIdentity_ - Input for DefaultAzureCredential as ManagedIdentityClientId. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 52 | 53 | _resourceIdentity_ - Input for DefaultAzureCredential as ManagedIdentityResourceId. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 54 | 55 | _tenantIdentity_ - Input for DefaultAzureCredential. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 56 | 57 | _accessKey_ - Alternative to DefaultAzureCredential. Input for AzureKeyCredential. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 58 | 59 | _sharedAccessSignature_ - Alternative to DefaultAzureCredential. Input for AzureSasCredential. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 60 | 61 | ### Retry Policy 62 | 63 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 64 | 65 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 66 | 67 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 68 | 69 | ## Azure Identity Environment 70 | When `DefaultAzureCredential` is used for Azure Identity, then it will recognize these environment variables: 71 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 72 | - `AZURE_TENANT_ID` - For TenantId 73 | 74 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) -------------------------------------------------------------------------------- /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 | 6 | namespace NLog.Extensions.AzureStorage 7 | { 8 | internal interface IEventHubService 9 | { 10 | string EventHubName { get; } 11 | void Connect(string connectionString, string eventHubName, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, bool useWebSockets, string webSocketsProxyAddress, string endPointAddress); 12 | Task CloseAsync(); 13 | Task SendAsync(IEnumerable eventDataBatch, string partitionKey, CancellationToken cancellationToken); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureEventHub/NLog.Extensions.AzureEventHub.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 4.4.2 8 | 9 | NLog EventHubTarget for writing to Azure Event Hubs datastreams 10 | jdetmar 11 | $([System.DateTime]::Now.ToString(yyyy)) 12 | Copyright (c) $(CurrentYear) - jdetmar 13 | 14 | NLog;azure;eventhubs;AMQP;log;logging 15 | logo64.png 16 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 17 | git 18 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 19 | MIT 20 | 21 | - Assign WorkloadIdentityClientId from ClientIdentity 22 | 23 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureEventHub/README.md 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /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.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 | ### Parameters 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 | _webSocketProxyAddress_ - Custom WebProxy address for WebSockets (optional) 58 | 59 | _customEndpointAddress_ - Custom endpoint address that can be used when establishing the connection (optional) 60 | 61 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is applied from DefaultAzureCredential. 62 | 63 | _clientIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityClientId. 64 | 65 | _resourceIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityResourceId. 66 | 67 | _tenantIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential. 68 | 69 | _sharedAccessSignature_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureSasCredential 70 | 71 | _accountName_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureNamedKeyCredential-AccountName 72 | 73 | _accessKey_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureNamedKeyCredential-AccessKey 74 | 75 | ### Batching Policy 76 | 77 | _maxBatchSizeBytes_ - Max size of a single batch in bytes [Integer](https://github.com/NLog/NLog/wiki/Data-types) (Default=1024*1024) 78 | 79 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 80 | 81 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 82 | 83 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 84 | 85 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 86 | 87 | ### Retry Policy 88 | 89 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 90 | 91 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 92 | 93 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 94 | 95 | ## Azure Identity Environment 96 | When using `ServiceUri` (Instead of ConnectionString), then `DefaultAzureCredential` is used for Azure Identity which supports environment variables: 97 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 98 | - `AZURE_TENANT_ID` - For TenantId 99 | 100 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 101 | 102 | ## Azure ConnectionString 103 | 104 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 105 | 106 | #### Lookup ConnectionString from appsettings.json 107 | 108 | > `connectionString="${configsetting:ConnectionStrings.AzureEventHub}"` 109 | 110 | * Example appsettings.json on .NetCore: 111 | 112 | ```json 113 | { 114 | "ConnectionStrings": { 115 | "AzureEventHub": "UseDevelopmentStorage=true;" 116 | } 117 | } 118 | ``` 119 | 120 | #### Lookup ConnectionString from app.config 121 | 122 | > `connectionString="${appsetting:ConnectionStrings.AzureEventHub}"` 123 | 124 | * Example app.config on .NetFramework: 125 | 126 | ```xml 127 | 128 | 129 | 130 | 131 | 132 | ``` 133 | 134 | #### Lookup ConnectionString from environment-variable 135 | 136 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 137 | 138 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 139 | 140 | > `connectionString="${gdc:AzureEventHubConnectionString}"` 141 | 142 | * Example code for setting GDC-value: 143 | 144 | ```c# 145 | NLog.GlobalDiagnosticsContext.Set("AzureEventHubConnectionString", "UseDevelopmentStorage=true;"); 146 | ``` -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureQueueStorage/ICloudQueueService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace NLog.Extensions.AzureStorage 7 | { 8 | internal interface ICloudQueueService 9 | { 10 | void Connect(string connectionString, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, TimeSpan? timeToLive, IDictionary queueMetadata); 11 | Task AddMessageAsync(string queueName, string queueMessage, CancellationToken cancellationToken); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureQueueStorage/NLog.Extensions.AzureQueueStorage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 4.4.1 8 | 9 | NLog QueueStorageTarget for writing to Azure Cloud Queue Storage 10 | jdetmar 11 | $([System.DateTime]::Now.ToString(yyyy)) 12 | Copyright (c) $(CurrentYear) - jdetmar 13 | 14 | NLog;azure;cloudqueue;queue;storage;log;logging 15 | logo64.png 16 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 17 | git 18 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 19 | MIT 20 | 21 | - Assign WorkloadIdentityClientId from ClientIdentity 22 | 23 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureQueueStorage/README.md 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /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.AzureQueueStorage/QueueStorageTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Azure.Storage.Queues; 6 | using NLog.Common; 7 | using NLog.Config; 8 | using NLog.Layouts; 9 | using NLog.Extensions.AzureStorage; 10 | 11 | namespace NLog.Targets 12 | { 13 | /// 14 | /// Azure Queue Storage NLog Target 15 | /// 16 | [Target("AzureQueueStorage")] 17 | public sealed class QueueStorageTarget : AsyncTaskTarget 18 | { 19 | private readonly ICloudQueueService _cloudQueueService; 20 | private readonly AzureStorageNameCache _containerNameCache = new AzureStorageNameCache(); 21 | private readonly Func _checkAndRepairQueueNameDelegate; 22 | 23 | public Layout ConnectionString { get; set; } 24 | 25 | [RequiredParameter] 26 | public Layout QueueName { get; set; } 27 | 28 | /// 29 | /// Alternative to ConnectionString 30 | /// 31 | public Layout ServiceUri { get; set; } 32 | 33 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 34 | [Obsolete("Instead use ServiceUri")] 35 | public Layout ServiceUrl { get => ServiceUri; set => ServiceUri = value; } 36 | 37 | /// 38 | /// Alternative to ConnectionString, when using 39 | /// 40 | public Layout TenantIdentity { get; set; } 41 | 42 | /// 43 | /// Alternative to ConnectionString, when using 44 | /// 45 | public Layout ResourceIdentity { get; set; } 46 | 47 | /// 48 | /// Alternative to ConnectionString, when using with ManagedIdentityClientId / WorkloadIdentityClientId 49 | /// 50 | public Layout ClientIdentity { get; set; } 51 | 52 | /// 53 | /// Alternative to ConnectionString, when using with AzureSasCredential 54 | /// 55 | public Layout SharedAccessSignature { get; set; } 56 | 57 | /// 58 | /// Alternative to ConnectionString, when using with storage account name. 59 | /// 60 | public Layout AccountName { get; set; } 61 | 62 | /// 63 | /// Alternative to ConnectionString, when using with storage account access-key. 64 | /// 65 | public Layout AccessKey { get; set; } 66 | 67 | [ArrayParameter(typeof(TargetPropertyWithContext), "metadata")] 68 | public IList QueueMetadata { get; private set; } 69 | 70 | /// 71 | /// The default time to live value for the message. 72 | /// 73 | /// 74 | /// Messages older than their TimeToLive value will expire and no longer be retained 75 | /// in the message store. Subscribers will be unable to receive expired messages. 76 | /// 77 | public Layout TimeToLiveSeconds { get; set; } 78 | 79 | /// 80 | /// The default time to live value for the message. 81 | /// 82 | /// 83 | /// Messages older than their TimeToLive value will expire and no longer be retained 84 | /// in the message store. Subscribers will be unable to receive expired messages. 85 | /// 86 | public Layout TimeToLiveDays { get; set; } 87 | 88 | public QueueStorageTarget() 89 | :this(new CloudQueueService()) 90 | { 91 | } 92 | 93 | internal QueueStorageTarget(ICloudQueueService cloudQueueService) 94 | { 95 | RetryDelayMilliseconds = 100; 96 | 97 | QueueMetadata = new List(); 98 | _cloudQueueService = cloudQueueService; 99 | _checkAndRepairQueueNameDelegate = CheckAndRepairQueueNamingRules; 100 | } 101 | 102 | /// 103 | /// Initializes the target. Can be used by inheriting classes 104 | /// to initialize logging. 105 | /// 106 | protected override void InitializeTarget() 107 | { 108 | base.InitializeTarget(); 109 | 110 | string connectionString = string.Empty; 111 | string serviceUri = string.Empty; 112 | string tenantIdentity = string.Empty; 113 | string resourceIdentifier = string.Empty; 114 | string clientIdentity = string.Empty; 115 | string sharedAccessSignature = string.Empty; 116 | string storageAccountName = string.Empty; 117 | string storageAccountAccessKey = string.Empty; 118 | 119 | Dictionary queueMetadata = null; 120 | 121 | var defaultLogEvent = LogEventInfo.CreateNullEvent(); 122 | 123 | try 124 | { 125 | connectionString = ConnectionString?.Render(defaultLogEvent); 126 | if (string.IsNullOrEmpty(connectionString)) 127 | { 128 | serviceUri = ServiceUri?.Render(defaultLogEvent); 129 | tenantIdentity = TenantIdentity?.Render(defaultLogEvent); 130 | resourceIdentifier = ResourceIdentity?.Render(defaultLogEvent); 131 | clientIdentity = ClientIdentity?.Render(defaultLogEvent); 132 | sharedAccessSignature = SharedAccessSignature?.Render(defaultLogEvent); 133 | storageAccountName = AccountName?.Render(defaultLogEvent); 134 | storageAccountAccessKey = AccessKey?.Render(defaultLogEvent); 135 | } 136 | 137 | if (QueueMetadata?.Count > 0) 138 | { 139 | queueMetadata = new Dictionary(); 140 | foreach (var metadata in QueueMetadata) 141 | { 142 | if (string.IsNullOrWhiteSpace(metadata.Name)) 143 | continue; 144 | 145 | var metadataValue = metadata.Layout?.Render(defaultLogEvent); 146 | if (string.IsNullOrEmpty(metadataValue)) 147 | continue; 148 | 149 | queueMetadata[metadata.Name.Trim()] = metadataValue; 150 | } 151 | } 152 | 153 | var timeToLive = RenderDefaultTimeToLive(); 154 | if (timeToLive <= TimeSpan.Zero) 155 | { 156 | timeToLive = default(TimeSpan?); 157 | } 158 | 159 | _cloudQueueService.Connect(connectionString, serviceUri, tenantIdentity, resourceIdentifier, clientIdentity, sharedAccessSignature, storageAccountName, storageAccountAccessKey, timeToLive, queueMetadata); 160 | InternalLogger.Debug("AzureQueueStorageTarget(Name={0}): Initialized", Name); 161 | } 162 | catch (Exception ex) 163 | { 164 | if (!string.IsNullOrEmpty(serviceUri)) 165 | InternalLogger.Error(ex, "AzureQueueStorageTarget(Name={0}): Failed to create QueueClient with ServiceUri={1}.", Name, serviceUri); 166 | else 167 | InternalLogger.Error(ex, "AzureQueueStorageTarget(Name={0}): Failed to create QueueClient with connectionString={1}.", Name, connectionString); 168 | throw; 169 | } 170 | } 171 | 172 | private TimeSpan? RenderDefaultTimeToLive() 173 | { 174 | string timeToLiveSeconds = null; 175 | string timeToLiveDays = null; 176 | 177 | try 178 | { 179 | timeToLiveSeconds = TimeToLiveSeconds?.Render(LogEventInfo.CreateNullEvent()); 180 | if (!string.IsNullOrEmpty(timeToLiveSeconds)) 181 | { 182 | if (int.TryParse(timeToLiveSeconds, out var resultSeconds)) 183 | { 184 | return TimeSpan.FromSeconds(resultSeconds); 185 | } 186 | else 187 | { 188 | InternalLogger.Error("AzureQueueStorageTarget(Name={0}): Failed to parse TimeToLiveSeconds={1}", Name, timeToLiveSeconds); 189 | } 190 | } 191 | else 192 | { 193 | timeToLiveDays = TimeToLiveDays?.Render(LogEventInfo.CreateNullEvent()); 194 | if (!string.IsNullOrEmpty(timeToLiveDays)) 195 | { 196 | if (int.TryParse(timeToLiveDays, out var resultDays)) 197 | { 198 | return TimeSpan.FromDays(resultDays); 199 | } 200 | else 201 | { 202 | InternalLogger.Error("AzureQueueStorageTarget(Name={0}): Failed to parse TimeToLiveDays={1}", Name, timeToLiveDays); 203 | } 204 | } 205 | } 206 | } 207 | catch (Exception ex) 208 | { 209 | InternalLogger.Error(ex, "AzureQueueStorageTarget(Name={0}): Failed to parse TimeToLive value. Seconds={1}, Days={2}", Name, timeToLiveSeconds, timeToLiveDays); 210 | } 211 | 212 | return default(TimeSpan?); 213 | } 214 | 215 | /// 216 | protected override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken) 217 | { 218 | var queueName = RenderLogEvent(QueueName, logEvent); 219 | var layoutMessage = RenderLogEvent(Layout, logEvent); 220 | 221 | try 222 | { 223 | queueName = _containerNameCache.LookupStorageName(queueName, _checkAndRepairQueueNameDelegate); 224 | 225 | return _cloudQueueService.AddMessageAsync(queueName, layoutMessage, cancellationToken); 226 | } 227 | catch (Exception ex) 228 | { 229 | InternalLogger.Error(ex, "AzureQueueStorageTarget(Name={0}): failed writing to queue: {1}", Name, queueName); 230 | throw; 231 | } 232 | } 233 | 234 | private string CheckAndRepairQueueNamingRules(string queueName) 235 | { 236 | InternalLogger.Trace("AzureQueueStorageTarget(Name={0}): Requested Queue Name: {1}", Name, queueName); 237 | string validQueueName = AzureStorageNameCache.CheckAndRepairContainerNamingRules(queueName); 238 | if (validQueueName == queueName.ToLowerInvariant()) 239 | { 240 | InternalLogger.Trace("AzureQueueStorageTarget(Name={0}): Using Queue Name: {1}", Name, validQueueName); 241 | } 242 | else 243 | { 244 | InternalLogger.Trace("AzureQueueStorageTarget(Name={0}): Using Cleaned Queue name: {1}", Name, validQueueName); 245 | } 246 | return validQueueName; 247 | } 248 | 249 | private sealed class CloudQueueService : ICloudQueueService 250 | { 251 | private QueueServiceClient _client; 252 | private QueueClient _queue; 253 | private IDictionary _queueMetadata; 254 | private TimeSpan? _timeToLive; 255 | 256 | public void Connect(string connectionString, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, TimeSpan? timeToLive, IDictionary queueMetadata) 257 | { 258 | _timeToLive = timeToLive; 259 | _queueMetadata = queueMetadata; 260 | 261 | if (string.IsNullOrWhiteSpace(serviceUri)) 262 | { 263 | _client = new QueueServiceClient(connectionString); 264 | } 265 | else if (!string.IsNullOrWhiteSpace(sharedAccessSignature)) 266 | { 267 | _client = new QueueServiceClient(new Uri(serviceUri), new Azure.AzureSasCredential(sharedAccessSignature)); 268 | } 269 | else if (!string.IsNullOrWhiteSpace(storageAccountName)) 270 | { 271 | _client = new QueueServiceClient(new Uri(serviceUri), new Azure.Storage.StorageSharedKeyCredential(storageAccountName, storageAccountAccessKey)); 272 | } 273 | else 274 | { 275 | Azure.Core.TokenCredential tokenCredentials = AzureCredentialHelpers.CreateTokenCredentials(clientIdentity, tenantIdentity, resourceIdentifier); 276 | _client = new QueueServiceClient(new Uri(serviceUri), tokenCredentials); 277 | } 278 | } 279 | 280 | public Task AddMessageAsync(string queueName, string queueMessage, CancellationToken cancellationToken) 281 | { 282 | var queue = _queue; 283 | if (string.IsNullOrEmpty(queueName) || queue?.Name != queueName) 284 | { 285 | return InitializeAndCacheQueueAsync(queueName, cancellationToken).ContinueWith(async (t, m) => await t.Result.SendMessageAsync((string)m, null, _timeToLive, cancellationToken).ConfigureAwait(false), queueMessage, cancellationToken); 286 | } 287 | else 288 | { 289 | return queue.SendMessageAsync(queueMessage, null, _timeToLive, cancellationToken); 290 | } 291 | } 292 | 293 | private async Task InitializeAndCacheQueueAsync(string queueName, CancellationToken cancellationToken) 294 | { 295 | try 296 | { 297 | if (_client == null) 298 | throw new InvalidOperationException("QueueServiceClient has not been initialized"); 299 | 300 | InternalLogger.Debug("AzureQueueStorageTarget: Initializing queue: {0}", queueName); 301 | var queue = _client.GetQueueClient(queueName); 302 | bool queueExists = await queue.ExistsAsync(cancellationToken).ConfigureAwait(false); 303 | if (!queueExists) 304 | { 305 | InternalLogger.Debug("AzureQueueStorageTarget: Creating new queue: {0}", queueName); 306 | await queue.CreateIfNotExistsAsync(_queueMetadata, cancellationToken).ConfigureAwait(false); 307 | } 308 | _queue = queue; 309 | return queue; 310 | } 311 | catch (Exception exception) 312 | { 313 | InternalLogger.Error(exception, "AzureQueueStorageTarget: Failed to initialize queue {0}", queueName); 314 | throw; 315 | } 316 | } 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /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 | ### Parameters 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 storage connection string. Ex. `UseDevelopmentStorage=true;` 35 | 36 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is acquired from DefaultAzureCredential. 37 | 38 | _clientIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityClientId. 39 | 40 | _resourceIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityResourceId. 41 | 42 | _tenantIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential. 43 | 44 | _sharedAccessSignature_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureSasCredential 45 | 46 | _accountName_ - Alternative to ConnectionString. Used together with ServiceUri. Input for StorageSharedKeyCredential-AccountName 47 | 48 | _accessKey_ - Alternative to ConnectionString. Used together with ServiceUri. Input for StorageSharedKeyCredential-AccessKey 49 | 50 | _timeToLiveSeconds_ - Default Time-To-Live (TTL) for Queue messages in seconds (Optional) 51 | 52 | _timeToLiveDays_ - Default Time-To-Live (TTL) for Queue messages in days (Optional) 53 | 54 | ### Batching Policy 55 | 56 | _batchSize_ - Number of EventData items to send in a single batch (Default=100) 57 | 58 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 59 | 60 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 61 | 62 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 63 | 64 | ### Retry Policy 65 | 66 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 67 | 68 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 69 | 70 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 71 | 72 | ## Azure Identity Environment 73 | When using `ServiceUri` (Instead of ConnectionString), then `DefaultAzureCredential` is used for Azure Identity which supports environment variables: 74 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 75 | - `AZURE_TENANT_ID` - For TenantId 76 | 77 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 78 | 79 | ## Azure ConnectionString 80 | 81 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 82 | 83 | #### Lookup ConnectionString from appsettings.json 84 | 85 | > `connectionString="${configsetting:ConnectionStrings.AzureQueue}"` 86 | 87 | * Example appsettings.json on .NetCore: 88 | 89 | ```json 90 | { 91 | "ConnectionStrings": { 92 | "AzureQueue": "UseDevelopmentStorage=true;" 93 | } 94 | } 95 | ``` 96 | 97 | #### Lookup ConnectionString from app.config 98 | 99 | > `connectionString="${appsetting:ConnectionStrings.AzureQueue}"` 100 | 101 | * Example app.config on .NetFramework: 102 | 103 | ```xml 104 | 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | #### Lookup ConnectionString from environment-variable 112 | 113 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 114 | 115 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 116 | 117 | > `connectionString="${gdc:AzureQueueConnectionString}"` 118 | 119 | * Example code for setting GDC-value: 120 | 121 | ```c# 122 | NLog.GlobalDiagnosticsContext.Set("AzureQueueConnectionString", "UseDevelopmentStorage=true;"); 123 | ``` -------------------------------------------------------------------------------- /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 | 7 | namespace NLog.Extensions.AzureStorage 8 | { 9 | internal interface ICloudServiceBus 10 | { 11 | string EntityPath { get; } 12 | TimeSpan? DefaultTimeToLive { get; } 13 | void Connect(string connectionString, string queueOrTopicName, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, bool useWebSockets, string webSocketProxyAddress, string endPointAddress, TimeSpan? timeToLive); 14 | Task SendAsync(IEnumerable messages, CancellationToken cancellationToken); 15 | Task CloseAsync(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/NLog.Extensions.AzureServiceBus/NLog.Extensions.AzureServiceBus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 4.4.2 8 | 9 | NLog ServiceBusTarget for writing to Azure Service Bus Topic or Queue 10 | jdetmar 11 | $([System.DateTime]::Now.ToString(yyyy)) 12 | Copyright (c) $(CurrentYear) - jdetmar 13 | 14 | NLog;azure;servicebus;AMQP;bus;queue;topic;log;logging 15 | logo64.png 16 | https://github.com/JDetmar/NLog.Extensions.AzureStorage 17 | git 18 | https://github.com/JDetmar/NLog.Extensions.AzureStorage.git 19 | MIT 20 | 21 | - Assign WorkloadIdentityClientId from ClientIdentity 22 | 23 | Docs: https://github.com/JDetmar/NLog.Extensions.AzureStorage/blob/master/src/NLog.Extensions.AzureServiceBus/README.md 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /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.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 | ### Parameters 41 | 42 | _name_ - Name of the target. 43 | 44 | _connectionString_ - Azure storage connection string. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 45 | 46 | _queueName_ - QueueName for multiple producers single consumer. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 47 | 48 | _topicName_ - TopicName for multiple producers and multiple subscribers. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 49 | 50 | _sessionId_ - SessionId-Key which Service Bus uses to generate PartitionId-hash. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 51 | 52 | _partitionKey_ - Partition-Key which Service Bus uses to generate PartitionId-hash. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 53 | 54 | _subject_ - Service Bus Message Subject to be used as label for the message. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 55 | 56 | _layout_ - Service Bus Message Body to be rendered and encoded as UTF8. [Layout](https://github.com/NLog/NLog/wiki/Layouts) Required. 57 | 58 | _contentType_ - Service Bus Message Body ContentType. [Layout](https://github.com/NLog/NLog/wiki/Layouts). Ex. application/json 59 | 60 | _messageId_ - Service Bus Message MessageId. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 61 | 62 | _correlationId_ - Service Bus Message Correlationid. [Layout](https://github.com/NLog/NLog/wiki/Layouts) 63 | 64 | _timeToLiveSeconds_ - Default Time-To-Live (TTL) for ServiceBus messages in seconds (Optional) 65 | 66 | _timeToLiveDays_ - Default Time-To-Live (TTL) for ServiceBus messages in days (Optional) 67 | 68 | _useWebSockets_ - Enable AmqpWebSockets. Ex. true/false (optional) 69 | 70 | _webSocketProxyAddress_ - Custom WebProxy address for WebSockets (optional) 71 | 72 | _customEndpointAddress_ - Custom endpoint address that can be used when establishing the connection (optional) 73 | 74 | _serviceUri_ - Alternative to ConnectionString, where Managed Identiy is applied from DefaultAzureCredential. 75 | 76 | _clientIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityClientId. 77 | 78 | _resourceIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential as ManagedIdentityResourceId. 79 | 80 | _tenantIdentity_ - Alternative to ConnectionString. Used together with ServiceUri. Input for DefaultAzureCredential. 81 | 82 | _sharedAccessSignature_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureSasCredential 83 | 84 | _accountName_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureNamedKeyCredential-AccountName 85 | 86 | _accessKey_ - Alternative to ConnectionString. Used together with ServiceUri. Input for AzureNamedKeyCredential-AccessKey 87 | 88 | ### Batching Policy 89 | 90 | _maxBatchSizeBytes_ - Max size of a single batch in bytes [Integer](https://github.com/NLog/NLog/wiki/Data-types) (Default=256*1024) 91 | 92 | _batchSize_ - Number of LogEvents to send in a single batch (Default=100) 93 | 94 | _taskDelayMilliseconds_ - Artificial delay before sending to optimize for batching (Default=200 ms) 95 | 96 | _queueLimit_ - Number of pending LogEvents to have in memory queue, that are waiting to be sent (Default=10000) 97 | 98 | _overflowAction_ - Action to take when reaching limit of in memory queue (Default=Discard) 99 | 100 | ### Retry Policy 101 | 102 | _taskTimeoutSeconds_ - How many seconds a Task is allowed to run before it is cancelled (Default 150 secs) 103 | 104 | _retryDelayMilliseconds_ - How many milliseconds to wait before next retry (Default 500ms, and will be doubled on each retry). 105 | 106 | _retryCount_ - How many attempts to retry the same Task, before it is aborted (Default 0) 107 | 108 | ## Azure Identity Environment 109 | When using `ServiceUri` (Instead of ConnectionString), then `DefaultAzureCredential` is used for Azure Identity which supports environment variables: 110 | - `AZURE_CLIENT_ID` - For ManagedIdentityClientId / WorkloadIdentityClientId 111 | - `AZURE_TENANT_ID` - For TenantId 112 | 113 | See also: [Set up Your Environment for Authentication](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication) 114 | 115 | ## Azure ConnectionString 116 | 117 | NLog Layout makes it possible to retrieve settings from [many locations](https://nlog-project.org/config/?tab=layout-renderers). 118 | 119 | #### Lookup ConnectionString from appsettings.json 120 | 121 | > `connectionString="${configsetting:ConnectionStrings.AzureBus}"` 122 | 123 | * Example appsettings.json on .NetCore: 124 | ```json 125 | { 126 | "ConnectionStrings": { 127 | "AzureBus": "UseDevelopmentStorage=true;" 128 | } 129 | } 130 | ``` 131 | 132 | #### Lookup ConnectionString from app.config 133 | 134 | > `connectionString="${appsetting:ConnectionStrings.AzureBus}"` 135 | 136 | * Example app.config on .NetFramework: 137 | ```xml 138 | 139 | 140 | 141 | 142 | 143 | ``` 144 | 145 | #### Lookup ConnectionString from environment-variable 146 | 147 | > `connectionString="${environment:AZURE_STORAGE_CONNECTION_STRING}"` 148 | 149 | #### Lookup ConnectionString from NLog GlobalDiagnosticsContext (GDC) 150 | 151 | > `connectionString="${gdc:AzureBusConnectionString}"` 152 | 153 | * Example code for setting GDC-value: 154 | ```c# 155 | NLog.GlobalDiagnosticsContext.Set("AzureBusConnectionString", "UseDevelopmentStorage=true;"); 156 | ``` -------------------------------------------------------------------------------- /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.AzureStorage/AzureCredentialHelper.cs: -------------------------------------------------------------------------------- 1 | namespace NLog.Extensions.AzureStorage 2 | { 3 | internal static class AzureCredentialHelpers 4 | { 5 | internal static Azure.Identity.DefaultAzureCredential CreateTokenCredentials(string clientIdentity, string tenantIdentity, string resourceIdentifier) 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(clientIdentity)) 15 | { 16 | options.ManagedIdentityClientId = clientIdentity; 17 | options.WorkloadIdentityClientId = clientIdentity; 18 | } 19 | else 20 | { 21 | if (!string.IsNullOrWhiteSpace(resourceIdentifier)) 22 | { 23 | options.ManagedIdentityResourceId = new Azure.Core.ResourceIdentifier(resourceIdentifier); 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 | } -------------------------------------------------------------------------------- /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).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(' ', '-').Trim(new[] { '-' }); 89 | var pass1 = Regex.Replace(requestedContainerName, trimFobiddenCharactersPattern, String.Empty, RegexOptions.None); 90 | var pass2 = Regex.Replace(pass1, trimTrailingPattern, String.Empty, RegexOptions.RightToLeft); 91 | var pass3 = Regex.Replace(pass2, trimLeadingPattern, String.Empty, RegexOptions.None); 92 | var pass4 = Regex.Replace(pass3, trimExtraHyphensPattern, "-", RegexOptions.None); 93 | var loweredCleanedContainerName = pass4.ToLower(); 94 | if (Regex.Match(loweredCleanedContainerName, validContainerPattern).Success) 95 | { 96 | return loweredCleanedContainerName; 97 | } 98 | return "defaultlog"; 99 | } 100 | 101 | //TODO: update rules 102 | /// 103 | /// Checks the and repairs table name acording to the Azure naming rules. 104 | /// 105 | /// Name of the table. 106 | /// 107 | public static string CheckAndRepairTableNamingRules(string tableName) 108 | { 109 | /* http://msdn.microsoft.com/en-us/library/windowsazure/dd179338.aspx 110 | Table Names: 111 | Table names must be unique within an account. 112 | Table names may contain only alphanumeric characters. 113 | Table names cannot begin with a numeric character. 114 | Table names are case-insensitive. 115 | Table names must be from 3 to 63 characters long. 116 | Some table names are reserved, including "tables". Attempting to create a table with a reserved table name returns error code 404 (Bad Request). 117 | */ 118 | var simpleValidName = tableName?.Length <= 63 ? EnsureValidName(tableName) : null; 119 | if (simpleValidName?.Length >= 3) 120 | return simpleValidName; 121 | 122 | const string trimLeadingPattern = "^.*?(?=[a-zA-Z])"; 123 | const string trimFobiddenCharactersPattern = "[^a-zA-Z0-9-]"; 124 | 125 | var pass1 = Regex.Replace(tableName, trimFobiddenCharactersPattern, String.Empty, RegexOptions.None); 126 | var cleanedTableName = Regex.Replace(pass1, trimLeadingPattern, String.Empty, RegexOptions.None); 127 | if (String.IsNullOrWhiteSpace(cleanedTableName) || cleanedTableName.Length > 63 || cleanedTableName.Length < 3) 128 | { 129 | var tableDefault = "Logs"; 130 | return tableDefault; 131 | } 132 | return tableName; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /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 Dictionary> BucketSort(IList inputs, KeySelector keySelector) where TKey : IEquatable 26 | { 27 | var retVal = new Dictionary>(); 28 | 29 | for (int i = 0; i < inputs.Count; ++i) 30 | { 31 | var input = inputs[i]; 32 | var keyValue = keySelector(input); 33 | if (!retVal.TryGetValue(keyValue, out var eventsInBucket)) 34 | { 35 | eventsInBucket = new List(inputs.Count - i); 36 | retVal.Add(keyValue, eventsInBucket); 37 | } 38 | 39 | eventsInBucket.Add(input); 40 | } 41 | 42 | return retVal; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, string clientId, string clientSecret, IDictionary blobMetadata, IDictionary blobTags) 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 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureBlobStorage.Tests/NLog.Extensions.AzureBlobStorage.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.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.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.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.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.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, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey) 17 | { 18 | ConnectionString = connectionString; 19 | } 20 | 21 | public Task SubmitTransactionAsync(string tableName, IEnumerable tableTransaction, 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] = tableTransaction; 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.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.AzureDataTables.Tests/NLog.Extensions.AzureDataTables.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.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.AzureStorage; 10 | 11 | namespace NLog.Extensions.AzureEventGrid.Tests 12 | { 13 | public class EventGridServiceMock : IEventGridService 14 | { 15 | public string Topic { get; set; } 16 | 17 | public List GridEvents { get; } = new List(); 18 | 19 | public List CloudEvents { get; } = new List(); 20 | 21 | public void Connect(string topic, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string accessKey) 22 | { 23 | Topic = topic; 24 | } 25 | 26 | public Task SendEventAsync(EventGridEvent gridEvent, CancellationToken cancellationToken) 27 | { 28 | if (string.IsNullOrEmpty(Topic)) 29 | throw new InvalidOperationException("TopicUri not connected"); 30 | 31 | return Task.Delay(10, cancellationToken).ContinueWith(t => 32 | { 33 | lock (GridEvents) 34 | GridEvents.Add(gridEvent); 35 | }); 36 | } 37 | 38 | public Task SendEventAsync(CloudEvent cloudEvent, CancellationToken cancellationToken) 39 | { 40 | if (string.IsNullOrEmpty(Topic)) 41 | throw new InvalidOperationException("TopicUri not connected"); 42 | 43 | return Task.Delay(10, cancellationToken).ContinueWith(t => 44 | { 45 | lock (CloudEvents) 46 | CloudEvents.Add(cloudEvent); 47 | }); 48 | } 49 | 50 | public string PeekLastGridEvent() 51 | { 52 | lock (GridEvents) 53 | { 54 | var gridEvent = GridEvents.LastOrDefault(); 55 | if (gridEvent != null) 56 | return Encoding.UTF8.GetString(gridEvent.Data.ToArray()); 57 | } 58 | 59 | return null; 60 | } 61 | 62 | public string PeekLastCloudEvent() 63 | { 64 | lock (CloudEvents) 65 | { 66 | var cloudEvent = CloudEvents.LastOrDefault(); 67 | if (cloudEvent != null) 68 | return Encoding.UTF8.GetString(cloudEvent.Data.ToArray()); 69 | } 70 | 71 | return null; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /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.AzureEventGrid.Tests/NLog.Extensions.AzureEventGrid.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.AzureEventHub.Tests/EventHubServiceMock.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.EventHubs; 2 | using NLog.Extensions.AzureStorage; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace NLog.Extensions.AzureEventHub.Test 10 | { 11 | class EventHubServiceMock : IEventHubService 12 | { 13 | private readonly Random _random = new Random(); 14 | 15 | public Dictionary> EventDataSent { get; } = new Dictionary>(); 16 | public string ConnectionString { get; private set; } 17 | public string EventHubName { get; private set; } 18 | 19 | public async Task CloseAsync() 20 | { 21 | await Task.Delay(1).ConfigureAwait(false); 22 | lock (EventDataSent) 23 | EventDataSent.Clear(); 24 | } 25 | 26 | public void Connect(string connectionString, string eventHubName, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, bool useWebSockets, string webSocketsProxyAddress, string endPointAddress) 27 | { 28 | ConnectionString = connectionString; 29 | EventHubName = eventHubName; 30 | } 31 | 32 | public Task SendAsync(IEnumerable eventDataBatch, string partitionKey, CancellationToken cancellationToken) 33 | { 34 | if (string.IsNullOrEmpty(ConnectionString)) 35 | throw new InvalidOperationException("EventHubService not connected"); 36 | 37 | return Task.Delay(_random.Next(5, 10), cancellationToken).ContinueWith(t => 38 | { 39 | lock (EventDataSent) 40 | { 41 | if (EventDataSent.TryGetValue(partitionKey, out var existingBatch)) 42 | existingBatch.AddRange(eventDataBatch); 43 | else 44 | EventDataSent[partitionKey] = new List(eventDataBatch); 45 | } 46 | }, cancellationToken); 47 | } 48 | 49 | public string PeekLastSent(string partitionKey) 50 | { 51 | lock (EventDataSent) 52 | { 53 | if (EventDataSent.TryGetValue(partitionKey, out var eventData)) 54 | { 55 | if (eventData.Count > 0) 56 | { 57 | return Encoding.UTF8.GetString(eventData[eventData.Count - 1].Body.ToArray()); 58 | } 59 | } 60 | } 61 | 62 | return null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureEventHub.Tests/NLog.Extensions.AzureEventHub.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.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.AzureStorage; 6 | 7 | namespace NLog.Extensions.AzureQueueStorage.Tests 8 | { 9 | class CloudQueueServiceMock : ICloudQueueService 10 | { 11 | public Dictionary MessagesAdded { get; } = new Dictionary(); 12 | 13 | public string ConnectionString { get; private set; } 14 | 15 | public TimeSpan? TimeToLive { get; private set; } 16 | 17 | public IDictionary QueueMetadata { get; private set; } 18 | 19 | public Task AddMessageAsync(string queueName, string queueMessage, CancellationToken cancellationToken) 20 | { 21 | if (string.IsNullOrEmpty(ConnectionString)) 22 | throw new InvalidOperationException("CloudQueueService not connected"); 23 | 24 | return Task.Delay(10, cancellationToken).ContinueWith(t => 25 | { 26 | lock (MessagesAdded) 27 | MessagesAdded[queueName] = queueMessage; 28 | }); 29 | } 30 | 31 | public void Connect(string connectionString, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, TimeSpan? timeToLive, IDictionary queueMetadata) 32 | { 33 | ConnectionString = connectionString; 34 | QueueMetadata = queueMetadata; 35 | TimeToLive = timeToLive; 36 | } 37 | 38 | public string PeekLastAdded(string queueName) 39 | { 40 | lock (MessagesAdded) 41 | { 42 | if (MessagesAdded.TryGetValue(queueName, out var queueMessage)) 43 | { 44 | return queueMessage; 45 | } 46 | } 47 | 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureQueueStorage.Tests/NLog.Extensions.AzureQueueStorage.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.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 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureServiceBus.Tests/NLog.Extensions.AzureServiceBus.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.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.AzureStorage; 7 | 8 | namespace NLog.Extensions.AzureServiceBus.Test 9 | { 10 | class ServiceBusMock : ICloudServiceBus 11 | { 12 | public List> MessageDataSent { get; } = new List>(); 13 | public string ConnectionString { get; private set; } 14 | public string EntityPath { get; private set; } 15 | public TimeSpan? DefaultTimeToLive { get; private set; } 16 | 17 | public string PeekLastMessageBody() 18 | { 19 | lock (MessageDataSent) 20 | { 21 | if (MessageDataSent.Count > 0) 22 | { 23 | var messages = MessageDataSent[MessageDataSent.Count - 1]; 24 | if (messages.Count > 0) 25 | { 26 | return Encoding.UTF8.GetString(messages[messages.Count - 1].Body); 27 | } 28 | } 29 | } 30 | 31 | return null; 32 | } 33 | 34 | public void Connect(string connectionString, string queueOrTopicName, string serviceUri, string tenantIdentity, string resourceIdentifier, string clientIdentity, string sharedAccessSignature, string storageAccountName, string storageAccountAccessKey, bool useWebSockets, string webProxy, string endPointAddress, TimeSpan? timeToLive) 35 | { 36 | ConnectionString = connectionString; 37 | EntityPath = queueOrTopicName; 38 | DefaultTimeToLive = timeToLive; 39 | } 40 | 41 | public Task SendAsync(IEnumerable messages, System.Threading.CancellationToken cancellationToken) 42 | { 43 | if (string.IsNullOrEmpty(ConnectionString)) 44 | throw new InvalidOperationException("ServiceBusService not connected"); 45 | 46 | return Task.Delay(10, cancellationToken).ContinueWith(t => 47 | { 48 | lock (MessageDataSent) 49 | MessageDataSent.Add(new List(messages)); 50 | }, cancellationToken); 51 | } 52 | 53 | public async Task CloseAsync() 54 | { 55 | await Task.Delay(1).ConfigureAwait(false); 56 | lock (MessageDataSent) 57 | MessageDataSent.Clear(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureStorage.IntegrationTest/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 24 | 29 | 33 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 59 | 60 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureStorage.IntegrationTest/NLog.Extensions.AzureStorage.IntegrationTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BD4E0EB2-99DB-41CB-A46B-DC573A7573CA} 8 | Exe 9 | NLog.Extensions.AzureStorage.IntegrationTest 10 | NLog.Extensions.AzureStorage.IntegrationTest 11 | v4.7.2 12 | 512 13 | true 14 | PackageReference 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\..\src\packages\NLog.4.7.0\lib\net45\NLog.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Designer 60 | 61 | 62 | 63 | 64 | 65 | {21b2270d-1f45-4be0-8cce-dec8ff293704} 66 | NLog.Extensions.AzureBlobStorage 67 | 68 | 69 | {0969c9d5-f3f1-4ab0-8887-a62f877ee68a} 70 | NLog.Extensions.AzureDataTables 71 | 72 | 73 | {cf5e1262-d006-4c3a-9a62-98c31a3ad5f0} 74 | NLog.Extensions.AzureEventHub 75 | 76 | 77 | {3e2ef52d-c7a3-48d4-9ab0-8d3e9efd3518} 78 | NLog.Extensions.AzureQueueStorage 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureStorage.IntegrationTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("NLog.Extensions.AzureStorage.IntegrationTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NLog.Extensions.AzureStorage.IntegrationTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("bd4e0eb2-99db-41cb-a46b-dc573a7573ca")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /test/NLog.Extensions.AzureStorage.IntegrationTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------