diff --git a/src/AWS.Logger.AspNetCore/AWS.Logger.AspNetCore.csproj b/src/AWS.Logger.AspNetCore/AWS.Logger.AspNetCore.csproj index d888d3d..628fc83 100644 --- a/src/AWS.Logger.AspNetCore/AWS.Logger.AspNetCore.csproj +++ b/src/AWS.Logger.AspNetCore/AWS.Logger.AspNetCore.csproj @@ -22,7 +22,7 @@ True ..\..\public.snk - 3.5.2 + 3.5.3 diff --git a/src/AWS.Logger.Core/AWS.Logger.Core.csproj b/src/AWS.Logger.Core/AWS.Logger.Core.csproj index 0fa8cba..73cb9f7 100644 --- a/src/AWS.Logger.Core/AWS.Logger.Core.csproj +++ b/src/AWS.Logger.Core/AWS.Logger.Core.csproj @@ -23,7 +23,7 @@ True False ..\..\public.snk - 3.3.2 + 3.3.3 diff --git a/src/AWS.Logger.Core/Core/AWSLoggerCore.cs b/src/AWS.Logger.Core/Core/AWSLoggerCore.cs index 8c01f6a..e3e00ff 100644 --- a/src/AWS.Logger.Core/Core/AWSLoggerCore.cs +++ b/src/AWS.Logger.Core/Core/AWSLoggerCore.cs @@ -30,10 +30,25 @@ public class AWSLoggerCore : IAWSLoggerCore private SemaphoreSlim _flushTriggerEvent; private ManualResetEventSlim _flushCompletedEvent; private AWSLoggerConfig _config; - private IAmazonCloudWatchLogs _client; private DateTime _maxBufferTimeStamp = new DateTime(); private string _logType; + /// + /// Internal CloudWatch Logs client + /// + /// + /// We defer the initialization of the client until it is first accessed. This avoids a deadlock for log4net: + /// 1. The thread creating the logger (which contains the CWL client) gets an internal lock in log4net, then tries to + /// access SDK configuration via the static FallbackInternalConfigurationFactory. + /// 2. The timer thread the SDK uses to load EC2 IMDS credentials requests SDK configuration via + /// FallbackInternalConfigurationFactory, which attempts to create additional loggers for logging the configuration loading. + /// There's an implicit lock around FallbackInternalConfigurationFactory's static constructor, so these two threads deadlock. + /// + /// By delaying initializing the internal client, we delay starting thread 2 until thread 1 has finished, that way we're + /// not creating additional log4net loggers in FallbackInternalConfigurationFactory while another thread is holding the log4net lock. + /// + private Lazy _client; + private static readonly string _assemblyVersion = typeof(AWSLoggerCore).GetTypeInfo().Assembly.GetName().Version?.ToString() ?? string.Empty; private static readonly string _baseUserAgentString = $"lib/aws-logger-core#{_assemblyVersion}"; @@ -102,12 +117,16 @@ public AWSLoggerCore(AWSLoggerConfig config, string logType) awsConfig.AuthenticationRegion = _config.AuthenticationRegion; } - var credentials = DetermineCredentials(config); - _client = new AmazonCloudWatchLogsClient(credentials, awsConfig); + _client = new Lazy(() => + { + var credentials = DetermineCredentials(config); + var client = new AmazonCloudWatchLogsClient(credentials, awsConfig); + client.BeforeRequestEvent += ServiceClientBeforeRequestEvent; + client.ExceptionEvent += ServiceClienExceptionEvent; - ((AmazonCloudWatchLogsClient)this._client).BeforeRequestEvent += ServiceClientBeforeRequestEvent; - ((AmazonCloudWatchLogsClient)this._client).ExceptionEvent += ServiceClienExceptionEvent; + return client; + }); StartMonitor(); RegisterShutdownHook(); @@ -215,9 +234,9 @@ private string GetServiceUrl() { try { - _client.Config.Validate(); + _client.Value.Config.Validate(); #pragma warning disable CS0618 // Type or member is obsolete - return _client.Config.DetermineServiceURL() ?? "Undetermined ServiceURL"; + return _client.Value.Config.DetermineServiceURL() ?? "Undetermined ServiceURL"; #pragma warning restore CS0618 // Type or member is obsolete } catch (Exception ex) @@ -331,7 +350,7 @@ private async Task Monitor(CancellationToken token) LogLibraryServiceError(ex); if (token.IsCancellationRequested) { - _client.Dispose(); + _client.Value.Dispose(); return; } } @@ -380,7 +399,7 @@ private async Task Monitor(CancellationToken token) { if (!token.IsCancellationRequested || !_repo.IsEmpty || !_pendingMessageQueue.IsEmpty) LogLibraryServiceError(ex); - _client.Dispose(); + _client.Value.Dispose(); return; } catch (Exception ex) @@ -403,7 +422,7 @@ private async Task SendMessages(CancellationToken token) { //Make sure the log events are in the right order. _repo._request.LogEvents.Sort((ev1, ev2) => ev1.Timestamp.CompareTo(ev2.Timestamp)); - var response = await _client.PutLogEventsAsync(_repo._request, token).ConfigureAwait(false); + var response = await _client.Value.PutLogEventsAsync(_repo._request, token).ConfigureAwait(false); _repo.Reset(); } catch (ResourceNotFoundException ex) @@ -425,7 +444,7 @@ private async Task LogEventTransmissionSetup(CancellationToken token) if (!_config.DisableLogGroupCreation) { - var logGroupResponse = await _client.DescribeLogGroupsAsync(new DescribeLogGroupsRequest + var logGroupResponse = await _client.Value.DescribeLogGroupsAsync(new DescribeLogGroupsRequest { LogGroupNamePrefix = _config.LogGroup }, token).ConfigureAwait(false); @@ -436,7 +455,7 @@ private async Task LogEventTransmissionSetup(CancellationToken token) if (logGroupResponse.LogGroups.FirstOrDefault(x => string.Equals(x.LogGroupName, _config.LogGroup, StringComparison.Ordinal)) == null) { - var createGroupResponse = await _client.CreateLogGroupAsync(new CreateLogGroupRequest { LogGroupName = _config.LogGroup }, token).ConfigureAwait(false); + var createGroupResponse = await _client.Value.CreateLogGroupAsync(new CreateLogGroupRequest { LogGroupName = _config.LogGroup }, token).ConfigureAwait(false); if (!IsSuccessStatusCode(createGroupResponse)) { LogLibraryServiceError(new System.Net.WebException($"Create LogGroup {_config.LogGroup} returned status: {createGroupResponse.HttpStatusCode}"), serviceURL); @@ -454,7 +473,7 @@ private async Task LogEventTransmissionSetup(CancellationToken token) try { - var streamResponse = await _client.CreateLogStreamAsync(new CreateLogStreamRequest + var streamResponse = await _client.Value.CreateLogStreamAsync(new CreateLogStreamRequest { LogGroupName = _config.LogGroup, LogStreamName = currentStreamName @@ -486,7 +505,7 @@ private void PutRetentionPolicy(int logGroupRetentionInDays, string logGroup, st { try { - var putPolicyResponse = await _client.PutRetentionPolicyAsync(new PutRetentionPolicyRequest(logGroup, logGroupRetentionInDays), token).ConfigureAwait(false); + var putPolicyResponse = await _client.Value.PutRetentionPolicyAsync(new PutRetentionPolicyRequest(logGroup, logGroupRetentionInDays), token).ConfigureAwait(false); if (!IsSuccessStatusCode(putPolicyResponse)) { LogLibraryServiceError(new System.Net.WebException($"Put retention policy {logGroupRetentionInDays} for LogGroup {logGroup} returned status: {putPolicyResponse.HttpStatusCode}"), serviceURL); diff --git a/src/AWS.Logger.Core/IAWSLoggerConfig.cs b/src/AWS.Logger.Core/IAWSLoggerConfig.cs index 746718b..ddbf29e 100644 --- a/src/AWS.Logger.Core/IAWSLoggerConfig.cs +++ b/src/AWS.Logger.Core/IAWSLoggerConfig.cs @@ -142,7 +142,7 @@ public interface IAWSLoggerConfig bool LibraryLogErrors { get; set; } /// - /// Gets and sets the LibraryLogFileName property. This is the name of the file into which errors from the AWS.Logger.Core library will be written into. + /// Gets and sets the LibraryLogFileName property. This is the name (and optional path) of the file into which errors from the AWS.Logger.Core library will be written into. /// /// The default is going to "aws-logger-errors.txt". /// diff --git a/src/AWS.Logger.Log4net/AWS.Logger.Log4net.csproj b/src/AWS.Logger.Log4net/AWS.Logger.Log4net.csproj index 31d83ac..a612fab 100644 --- a/src/AWS.Logger.Log4net/AWS.Logger.Log4net.csproj +++ b/src/AWS.Logger.Log4net/AWS.Logger.Log4net.csproj @@ -22,7 +22,7 @@ True ..\..\public.snk - 3.5.2 + 3.5.3 diff --git a/src/AWS.Logger.Log4net/AWSAppender.cs b/src/AWS.Logger.Log4net/AWSAppender.cs index 82df402..b8d1bec 100644 --- a/src/AWS.Logger.Log4net/AWSAppender.cs +++ b/src/AWS.Logger.Log4net/AWSAppender.cs @@ -233,7 +233,7 @@ public TimeSpan FlushTimeout } /// - /// Gets and sets the LibraryLogFileName property. This is the name of the file into which errors from the AWS.Logger.Core library will be written into. + /// Gets and sets the LibraryLogFileName property. This is the name (and optional path) of the file into which errors from the AWS.Logger.Core library will be written into. /// /// The default is going to "aws-logger-errors.txt". /// diff --git a/src/AWS.Logger.SeriLog/AWS.Logger.SeriLog.csproj b/src/AWS.Logger.SeriLog/AWS.Logger.SeriLog.csproj index 42d900d..c25e41b 100644 --- a/src/AWS.Logger.SeriLog/AWS.Logger.SeriLog.csproj +++ b/src/AWS.Logger.SeriLog/AWS.Logger.SeriLog.csproj @@ -22,7 +22,7 @@ True ..\..\public.snk - 3.4.2 + 3.4.3 diff --git a/src/NLog.AWS.Logger/AWSTarget.cs b/src/NLog.AWS.Logger/AWSTarget.cs index 824fb45..aea632f 100644 --- a/src/NLog.AWS.Logger/AWSTarget.cs +++ b/src/NLog.AWS.Logger/AWSTarget.cs @@ -223,7 +223,7 @@ public bool LibraryLogErrors } /// - /// Gets and sets the LibraryLogFileName property. This is the name of the file into which errors from the AWS.Logger.Core library will be written into. + /// Gets and sets the LibraryLogFileName property. This is the name (and optional path) of the file into which errors from the AWS.Logger.Core library will be written into. /// /// The default is "aws-logger-errors.txt". /// diff --git a/src/NLog.AWS.Logger/NLog.AWS.Logger.csproj b/src/NLog.AWS.Logger/NLog.AWS.Logger.csproj index 5a4875b..7197bef 100644 --- a/src/NLog.AWS.Logger/NLog.AWS.Logger.csproj +++ b/src/NLog.AWS.Logger/NLog.AWS.Logger.csproj @@ -23,7 +23,7 @@ True ..\..\public.snk - 3.3.3 + 3.3.4