using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Text; namespace Kit.Helpers.Log { public interface ICallStackLog { string LogMessage { get; } ICallStackLog NextLevel(); void FinishLevel(); void BeginCall(string message); void EndCall(); } public class EmptyCallStackLog : ICallStackLog { public string LogMessage { get { return ""; } } public ICallStackLog NextLevel() { return CallStackLog.Empty; } public void FinishLevel() { return; } public void BeginCall(string message) { return; } public void EndCall() { return; } } public class CallStackLog : ICallStackLog { protected ConcurrentDictionary Logs { get; } protected ConcurrentBag ChildCallStackLogs { get; set; } protected readonly int _level; protected readonly string _path; protected string _currentCall; protected int _number; protected readonly Stopwatch _stopwatch = new Stopwatch(); public static ICallStackLog Empty { get { return new EmptyCallStackLog(); } } protected string Number { get { return _number.ToString("D5"); } } public CallStackLog() { Logs = new ConcurrentDictionary(); ChildCallStackLogs = new ConcurrentBag(); _level = 0; _path = ""; _number = 1; } public CallStackLog(int level, string path) { Logs = new ConcurrentDictionary(); ChildCallStackLogs = new ConcurrentBag(); _level = level; _path = path; _number = 1; } public string LogMessage { get { StringBuilder stringBuilder = new StringBuilder(); foreach (var logLine in Logs.OrderBy(x => x.Key)) { string time = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:0.000}", logLine.Value / 1000.0f); stringBuilder.AppendLine(logLine.Key + $". Time - {time} ms"); } return stringBuilder.ToString(); } } public ConcurrentDictionary GetLogs { get { return this.Logs; } } public ICallStackLog NextLevel() { var childCallStackLog = new CallStackLog(_level + 1, _path + this.Number + "."); ChildCallStackLogs.Add(childCallStackLog); return childCallStackLog; } public void FinishLevel() { foreach (CallStackLog childCallStackLog in ChildCallStackLogs) { lock (Logs) { foreach (var logLine in childCallStackLog.Logs) { if (Logs.ContainsKey(logLine.Key)) { Logs[logLine.Key] += logLine.Value; } else { Logs[logLine.Key] = logLine.Value; } } } } ChildCallStackLogs = new ConcurrentBag(); } public void BeginCall(string message) { _currentCall = message; _stopwatch.Restart(); } public void EndCall() { _stopwatch.Stop(); lock (Logs) { long ticksByMicrosecond = Stopwatch.Frequency / (1000L * 1000L); long microSeconds = _stopwatch.ElapsedTicks / ticksByMicrosecond; Logs.TryAdd($"{_path}{this.Number} {_currentCall.ToStringOrEmpty()}", microSeconds); _number++; } } } public class CallStackLogFile : ICallStackLog { protected object _lock = new object(); protected ConcurrentBag ChildCallStackLogs { get; set; } protected readonly int _level; protected readonly string _path; protected string _currentCall; protected int _number; protected readonly Stopwatch _stopwatch = new Stopwatch(); protected readonly StreamWriter _writer; public static ICallStackLog Empty { get { return new EmptyCallStackLog(); } } protected string Number { get { return _number.ToString("D5"); } } public CallStackLogFile(StreamWriter writer) { _writer = writer; ChildCallStackLogs = new ConcurrentBag(); _level = 0; _path = ""; _number = 1; } public CallStackLogFile(StreamWriter writer, int level, string path) { _writer = writer; ChildCallStackLogs = new ConcurrentBag(); _level = level; _path = path; _number = 1; } public string LogMessage => string.Empty; public ICallStackLog NextLevel() { var childCallStackLog = new CallStackLogFile(_writer, _level + 1, _path + this.Number + "."); ChildCallStackLogs.Add(childCallStackLog); return childCallStackLog; } public void FinishLevel() { ChildCallStackLogs = new ConcurrentBag(); } public void BeginCall(string message) { _currentCall = message; _stopwatch.Restart(); } public void EndCall() { _stopwatch.Stop(); lock (_lock) { long microSeconds = _stopwatch.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L)); string time = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:0.000}", microSeconds / 1000.0f); _writer?.WriteLine($"{_path}{this.Number} {_currentCall.ToStringOrEmpty()}. Time - {time} ms"); _number++; } } } }