Skip to content

Instantly share code, notes, and snippets.

@tnayanam
Created November 22, 2019 04:53
Show Gist options
  • Save tnayanam/a30301e35774d4a6c9f869a0a7f7a33a to your computer and use it in GitHub Desktop.
Save tnayanam/a30301e35774d4a6c9f869a0a7f7a33a to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SM.BrokerDomain.DTO.Configuration;
using SM.BrokerDomain.DTO.Federation;
using SM.BrokerDomain.DTO.System;
using SM.BrokerDomain.Repo.SystemModels;
using SM.Common.BLL;
using SM.Common.BLL.AppServices;
using SM.Common.DTO.Configuration;
using SM.Common.DTO.Exceptions;
using SM.Common.Repo;
namespace SM.BrokerDomain.BLL.System
{
public class SystemLogger : AppServiceThread, ISystemLogger
{
private int _logBatchSize = 1000;
private TimeSpan _sleepTime = TimeSpan.FromMilliseconds(Config.TryGet<int>(ConfigurationKeys.LoggingProcessingSleep, 1000));
private ConcurrentLogQueue<SystemLog> _logs;
public override TimeSpan SleepTime => _sleepTime;
public override TimeSpan StopTimeout => TimeSpan.FromMilliseconds(5000); //time to wait for logs to finish writing after stop request has been received
public override AppServiceStartupMode ServiceStartupMode => Config.TryGet(ConfigurationKeys.LoggingSystemLogsEnabled, true) ? AppServiceStartupMode.Automatic : AppServiceStartupMode.Manual;
public static readonly SystemLogger Instance = new SystemLogger();
public static string systemStreamName { get; set; } = Guid.NewGuid().ToString();
public static string systemSequenceToken = null;
public static int systemRetryCount = 2;
private SystemLogger(): base("System Logger")
{
_logs = new ConcurrentLogQueue<SystemLog>(_logBatchSize);
//If service is set to automatically start, allow early-enqueuing of system logs, before the service thread has started.
if (ServiceStartupMode == AppServiceStartupMode.Automatic)
_logs.Enabled = true;
}
public void CancelAutomaticStartup()
{
//Stop allowing early enqueueing, and dump any logs that were enqueued early (before the service was started).
_logs.Enabled = false;
_logs.Dump();
}
public void DumpOnNextFailure()
{
_logs.DumpOnNextFailure();
}
protected override Task OnStartAsync(CancellationToken cancellationToken)
{
//Start accepting new logs into the queue
_logs.Enabled = true;
//Start the base AppServiceThread
return base.OnStartAsync(cancellationToken);
}
protected override async Task OnStopAsync(CancellationToken cancellationToken, bool appDomainIsStopping)
{
//Stop accepting new logs into the queue
_logs.Enabled = appDomainIsStopping; //allow collection of final log set if performing final stop of the domain. This allows domain shutdown system logs to be captured and logged with the final log flush performed by the BrokeredApplication.
//Stop the base AppServiceThread since the service is stopping
await base.OnStopAsync(cancellationToken, appDomainIsStopping).ConfigureAwait(false);
//Flush any logs still left in the queue, and dump them if an error occurs
DumpOnNextFailure();
await FlushAsync().ConfigureAwait(false);
}
public Task FlushAsync(CancellationToken writeCancellationToken = default(CancellationToken))
{
if (_logs.IsEmpty)
return Task.CompletedTask;
return _logs.FlushAsync(writeCancellationToken, (dequeuedLogs, cancellationToken) => BulkLogWriter.LogAsync(dequeuedLogs, cancellationToken, _logBatchSize));
}
protected override void Process(CancellationToken shutdownCancellationToken)
{
Task writeTask = null;
try
{
writeTask = FlushAsync(CancellationToken.None); //Perform the write without a cancellation token
writeTask.Wait(shutdownCancellationToken); //If application is shutting down, interrupt the wait, and handle the exception by waiting for some fixed, but limited additional time for the write to complete.
}
catch (OperationCanceledException ex)
{
//If a shutdown signal was received, Wait a bit more time for the write to complete before returning
if (writeTask != null && !writeTask.IsCompleted && StopTimeout.Ticks > 0)
writeTask.Wait(StopTimeout);
}
}
/// <summary>
/// Logging callback implementation. The base class in SM.Common cannot depend on Broker's logging infrastructure, so this callback allows for a custom logging implementation.
/// Service logs for system logger just logs to itself.
/// </summary>
protected override void OnServiceLog(string message, AppServiceLogType logType) => LogAppServiceMessage(message, logType);
/// <summary>
/// Routes service logs to the right system logs method based on the AppServiceLogType.
/// </summary>
public Guid LogAppServiceMessage(string message, AppServiceLogType logType)
{
switch (logType)
{
case AppServiceLogType.Info: return LogInfo(message);
case AppServiceLogType.Error: return LogException(message);
default: return Guid.Empty;
}
}
public Guid LogInfo(string message, DateTime? timeOfOccurrence = null, string logType = null, long? duration = null)
{
var utcNow = DateTime.UtcNow;
var log = new SystemLog
{
LogLevel = (int)LogLevel.Info,
LogType = logType,
Message = message,
StackTrace = null,
Duration = duration,
TimeOfOccurrence = timeOfOccurrence ?? utcNow,
CreateDate = utcNow,
LastUpdated = utcNow,
CreatedByUserID = BrokeredApplication.InstanceID,
Domain = Config.TryGet(ConfigurationKeys.DomainName, "Not set")
};
_logs.Enqueue(log);
return log.ID;
}
public Guid LogException(Exception exception, DateTime? timeOfOccurrence = null, string logType = null, long? duration = null)
{
var utcNow = DateTime.UtcNow;
var log = new SystemLog
{
LogLevel = (int)LogLevel.Error,
LogType = logType,
Message = exception.FlattenMessage(),
StackTrace = exception.StackTrace,
Duration = duration,
TimeOfOccurrence = timeOfOccurrence ?? utcNow,
CreateDate = utcNow,
LastUpdated = utcNow,
CreatedByUserID = BrokeredApplication.InstanceID,
Domain = Config.TryGet(ConfigurationKeys.DomainName, "Not set")
};
_logs.Enqueue(log);
return log.ID;
}
public Guid LogException(string message, DateTime? timeOfOccurrence = null, string stackTrace = null, string logType = null, long? duration = null)
{
var utcNow = DateTime.UtcNow;
var log = new SystemLog
{
LogLevel = (int)LogLevel.Error,
LogType = logType,
Message = message,
StackTrace = stackTrace,
Duration = duration,
TimeOfOccurrence = timeOfOccurrence ?? utcNow,
CreateDate = utcNow,
LastUpdated = utcNow,
CreatedByUserID = BrokeredApplication.InstanceID,
Domain = Config.TryGet(ConfigurationKeys.DomainName, "Not set")
};
_logs.Enqueue(log);
return log.ID;
}
public static SystemLog Convert(SystemLogDTO logDTO)
{
return new SystemLog
{
LogLevel = logDTO.LogLevel,
LogType = logDTO.LogType,
Message = logDTO.Message,
StackTrace = logDTO.StackTrace,
Duration = logDTO.Duration,
TimeOfOccurrence = logDTO.TimeOfOccurrence,
CreateDate = DateTime.UtcNow,
Domain = Config.TryGet(ConfigurationKeys.DomainName, "Not set")
};
}
public override object GetServiceStatus()
{
return new Dictionary<string,object> {
{ "settings", new Dictionary<string,object> {
{ "log_batch_size", _logBatchSize },
{ "sleep_time", ((int)SleepTime.TotalMilliseconds).ToString() + " milliseconds" },
{ "stop_timeout", ((int)StopTimeout.TotalMilliseconds).ToString() + " milliseconds" }
} },
{ "system_log_stats", new Dictionary<string,object> {
{ "logs_enqueued", _logs.LogsEnqueued },
{ "max_logs_dequeued_at_once", _logs.MaxLogsDequeuedAtOnce },
{ "total_logs_written", _logs.TotalLogsWritten },
{ "total_logs_dumped", _logs.TotalLogsDumped },
{ "throttling", _logs.Throttling },
{ "total_logs_throttled", _logs.TotalLogsThrottled },
{ "total_failed_write_attempts", _logs.TotalFailedWriteAttempts },
{ "logs_written_per_second_burst", _logs.LogsPerSecondBurst.ToString("0.00") },
{ "logs_written_per_second_realtime", _logs.LogsPerSecondRealTime.ToString("0.00") },
{ "max_database_write_milliseconds", (int)_logs.MaxDatabaseWriteTime.TotalMilliseconds },
{ "max_aws_write_milliseconds", (int)_logs.MaxAwsWriteTime.TotalMilliseconds }
} }
};
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment