using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Implab.Diagnostics { /// /// Контекст трассировки, привязывается к потоку и содержит в себе информацию о стеке логических операций. /// /// /// Контекст трассировки передается слушателям событий для определения места, где возникло событие. /// public class TraceContext { LogicalOperation m_currentOperation; readonly LogicalOperation m_bound; readonly int m_threadId; [ThreadStatic] static TraceContext _current; /// /// Текущий контекст трассировки для потока, создается астоматически при первом обращении. /// public static TraceContext Current { get { if (_current == null) { _current = new TraceContext(); _current.LogEvent(TraceEventType.Created,"[{0}]", _current.ThreadId); } return _current; } } TraceContext(TraceContext context) : this(context, false) { } TraceContext(TraceContext context, bool attach) { if (context == null) throw new ArgumentNullException("context"); m_currentOperation = context.CurrentOperation; m_bound = attach ? context.BoundOperation : context.CurrentOperation; m_threadId = Thread.CurrentThread.ManagedThreadId; } TraceContext() { m_currentOperation = new LogicalOperation(); m_bound = m_currentOperation; m_threadId = Thread.CurrentThread.ManagedThreadId; } /// /// При необходимости копирует состояние контекста трассивровки в текущий поток. /// /// Исходный контекст трассировки, который передается. /// /// /// Копирование происходит за счет создания нового контекста трассировки и заполнением его /// состояния из переданного контекста. При этом копируется стек операций, однако в новом /// контексте ранее начатые логические операции не могут быть завершены. /// /// /// Если передача состояния состоялась, то вызывается событие трассировки . /// /// public static void Fork(TraceContext from) { if (_current == from) return; if (from != null) { var context = new TraceContext(from); context.LogEvent(TraceEventType.Fork, "[{0}]-->[{1}]",from.ThreadId, context.ThreadId); _current = context; } else { _current = new TraceContext(); } } /// /// Задает текущему потоку указанный контекст, текущей поток может заканчивать ранее начатые /// логические операции в указанном контексте. /// /// public static void Attach(TraceContext source) { if (_current == source) return; if (source != null) { var context = new TraceContext(source, true); context.LogEvent(TraceEventType.Attach, "[{0}]-->[{1}]", source.ThreadId, context.ThreadId); _current = context; } else { _current = new TraceContext(); } } /// /// Отсоединяет текущий контекст трассировки от потока, для дальнейшей его передачи другому потоку /// . /// /// Контекст трассировки потока /// /// После отсоединения контекста трассировки от потока, при первом обращении к трассировке в этом /// потоке будет создан новый контекст. /// public static TraceContext Detach() { var context = Current; context.LogEvent(TraceEventType.Detach, null); _current = null; return context; } /// /// Создает постоянную копию текущего контекста, данную копию можно хранить и использовать для передачи через /// /// Копия текущего контекста трассировки. public static TraceContext Snapshot() { return _current == null ? new TraceContext() : new TraceContext(_current,false); } /// /// Выполняет переданное действие в указанном контексте трассировки, по окончании восстанавливает предыдущий контекст трассировки потока. /// /// public void Invoke(Action action) { if (action == null) throw new ArgumentNullException("action"); var old = _current; Fork(this); try { action(); } finally { if(_current != null) _current.EndAllOperations(); _current = old; } } /// /// Текущая логическая операция. /// public LogicalOperation CurrentOperation { get { return m_currentOperation; } } /// /// Операция ниже которой нельзя опускаться в стеке логических операций, т.е. она не может быть завершена в текущем контексте. /// public LogicalOperation BoundOperation { get { return m_bound; } } /// /// Поток, в котором создан контекст трассировки. /// public int ThreadId { get { return m_threadId; } } /// /// Начинает безымянную логическую операцию. /// public void StartLogicalOperation() { StartLogicalOperation(null); } /// /// Начинает логическую операцию с указанным именем. Созданная операция будет добвалена в стек логических операций контекста, затем будет создано соответсвующее событие. /// /// Имя начинаемой операции. public void StartLogicalOperation(string name) { m_currentOperation = new LogicalOperation(name, m_currentOperation); LogEvent(TraceEventType.OperationStarted, name); } /// /// Заканчивает логическую операцию начатую в текущем контексте. Операции, начатые в других контекстах не могут быть закончены в текущем контексте. /// /// /// При вызове данного метода создается событие журнала трассировки, либо о завершении операции, либо об ошибки, поскольку данная операция /// начата в другом контексте. /// public void EndLogicalOperation() { if (m_bound == m_currentOperation) { LogEvent(TraceEventType.Error, "Trying to end the operation which isn't belongs to current trace"); } else { var op = m_currentOperation; LogEvent(TraceEventType.OperationCompleted, "{0} {1} ms", op.Name, op.Duration); m_currentOperation = m_currentOperation.Parent; } } /// /// Создает копию контекста и возвращается на предыдущую операцию в текущем контексте, это позволяет начать операцию в одном потоке, а завершить - в другом. /// /// Контекст трассировки, который можно присоединить к другому потоку. public TraceContext DetachLogicalOperation() { if (m_bound == m_currentOperation) { return new TraceContext(); } else { var detached = new TraceContext(this, true); m_currentOperation = m_currentOperation.Parent; return detached; } } public void BindLogicalOperationToPromise(IPromise promise) { Safe.ArgumentNotNull(promise, "promise"); var ctx = DetachLogicalOperation(); promise.Anyway(() => { var old = _current; TraceContext.Attach(ctx); TraceContext.Current.EndLogicalOperation(); _current = old; }); } /// /// Заврешает все начатые в этом контексте операции /// public void EndAllOperations() { while (m_bound != m_currentOperation) EndLogicalOperation(); } void LogEvent(TraceEventType type, string format, params object[] args) { LogChannel.Default.LogEvent(this, TraceEvent.Create(type, format, args)); } } }