├── .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 [](https://ci.appveyor.com/project/JDetmar/nlog-extensions-azurestorage)
2 |
3 | 
4 |
5 | | Package Name | NuGet | Description | Documentation |
6 | | ------------------------------------- | :-------------------: | ----------- | ------------- |
7 | | **NLog.Extensions.AzureBlobStorage** | [](https://www.nuget.org/packages/NLog.Extensions.AzureBlobStorage/) | Azure Blob Storage | [](src/NLog.Extensions.AzureBlobStorage/README.md) |
8 | | **NLog.Extensions.AzureDataTables** | [](https://www.nuget.org/packages/NLog.Extensions.AzureDataTables/) | Azure Table Storage or Azure CosmosDb Tables | [](src/NLog.Extensions.AzureDataTables/README.md) |
9 | | **NLog.Extensions.AzureEventHub** | [](https://www.nuget.org/packages/NLog.Extensions.AzureEventHub/) | Azure EventHubs | [](src/NLog.Extensions.AzureEventHub/README.md) |
10 | | **NLog.Extensions.AzureEventGrid** | [](https://www.nuget.org/packages/NLog.Extensions.AzureEventGrid/) | Azure Event Grid | [](src/NLog.Extensions.AzureEventGrid/README.md) |
11 | | **NLog.Extensions.AzureQueueStorage** | [](https://www.nuget.org/packages/NLog.Extensions.AzureQueueStorage/) | Azure Queue Storage | [](src/NLog.Extensions.AzureQueueStorage/README.md) |
12 | | **NLog.Extensions.AzureServiceBus** | [](https://www.nuget.org/packages/NLog.Extensions.AzureServiceBus/) | Azure Service Bus | [](src/NLog.Extensions.AzureServiceBus/README.md) |
13 | | **NLog.Extensions.AzureAccessToken** | [](https://www.nuget.org/packages/NLog.Extensions.AzureAccessToken/) | Azure App Authentication Access Token for Managed Identity | [](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 | [](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** | [](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** | [](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** | [](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** | [](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** | [](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** | [](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** | [](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** | [](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 |
--------------------------------------------------------------------------------