view Implab/Diagnostics/TraceContext.cs @ 52:edf0bc558596

improved trace system
author cin
date Mon, 05 May 2014 07:35:48 +0400
parents d9d794b61bb9
children 790e8a997d30
line wrap: on
line source

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Implab.Diagnostics {
    /// <summary>
    /// Контекст трассировки, привязывается к потоку и содержит в себе информацию о стеке логических операций.
    /// </summary>
    /// <remarks>
    /// Контекст трассировки передается слушателям событий для определения места, где возникло событие.
    /// </remarks>
    public class TraceContext {
        LogicalOperation m_currentOperation;
        readonly LogicalOperation m_bound;
        readonly int m_threadId;

        [ThreadStatic]
        static TraceContext _current;

        /// <summary>
        /// Текущий контекст трассировки для потока, создается астоматически при первом обращении.
        /// </summary>
        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;
        }

        /// <summary>
        /// При необходимости копирует состояние контекста трассивровки в текущий поток.
        /// </summary>
        /// <param name="from">Исходный контекст трассировки, который передается.</param>
        /// <remarks>
        /// <para>
        /// Копирование происходит за счет создания нового контекста трассировки и заполнением его
        /// состояния из переданного контекста. При этом копируется стек операций, однако в новом
        /// контексте ранее начатые логические операции не могут быть завершены.
        /// </para>
        /// <para>
        /// Если передача состояния состоялась, то вызывается событие трассировки <see cref="TraceEventType.Fork"/>.
        /// </para>
        /// </remarks>
        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();
            }
        }

        /// <summary>
        /// Задает текущему потоку указанный контекст, текущей поток может заканчивать ранее начатые
        /// логические операции в указанном контексте.
        /// </summary>
        /// <param name="source"></param>
        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();
            }
        }

        /// <summary>
        /// Отсоединяет текущий контекст трассировки от потока, для дальнейшей его передачи другому потоку
        /// <see cref="Attach(TraceContext)"/>.
        /// </summary>
        /// <returns>Контекст трассировки потока</returns>
        /// <remarks>
        /// После отсоединения контекста трассировки от потока, при первом обращении к трассировке в этом
        /// потоке будет создан новый контекст.
        /// </remarks>
        public static TraceContext Detach() {
            var context = Current;
            context.LogEvent(TraceEventType.Detach, null);
            _current = null;
            return context;
        }

        /// <summary>
        /// Создает постоянную копию текущего контекста, данную копию можно хранить и использовать для передачи через <see cref="Fork(TraceContext)"/>
        /// </summary>
        /// <returns>Копия текущего контекста трассировки.</returns>
        public static TraceContext Snapshot() {
            return _current == null ? new TraceContext() : new TraceContext(_current,false);
        }

        /// <summary>
        /// Выполняет переданное действие в указанном контексте трассировки, по окончании восстанавливает предыдущий контекст трассировки потока.
        /// </summary>
        /// <param name="action"></param>
        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;
            }
        }

        /// <summary>
        /// Текущая логическая операция.
        /// </summary>
        public LogicalOperation CurrentOperation {
            get {
                return m_currentOperation;
            }
        }

        /// <summary>
        /// Операция ниже которой нельзя опускаться в стеке логических операций, т.е. она не может быть завершена в текущем контексте.
        /// </summary>
        public LogicalOperation BoundOperation {
            get {
                return m_bound;
            }
        }

        /// <summary>
        /// Поток, в котором создан контекст трассировки.
        /// </summary>
        public int ThreadId {
            get {
                return m_threadId;
            }
        }

        /// <summary>
        /// Начинает безымянную логическую операцию.
        /// </summary>
        public void StartLogicalOperation() {
            StartLogicalOperation(null);
        }

        /// <summary>
        /// Начинает логическую операцию с указанным именем. Созданная операция будет добвалена в стек логических операций контекста, затем будет создано соответсвующее событие.
        /// </summary>
        /// <param name="name">Имя начинаемой операции.</param>
        public void StartLogicalOperation(string name) {
            m_currentOperation = new LogicalOperation(name, m_currentOperation);
            LogEvent(TraceEventType.OperationStarted, name);
        }

        /// <summary>
        /// Заканчивает логическую операцию начатую в текущем контексте. Операции, начатые в других контекстах не могут быть закончены в текущем контексте.
        /// </summary>
        /// <remarks>
        /// При вызове данного метода создается событие журнала трассировки, либо о завершении операции, либо об ошибки, поскольку данная операция
        /// начата в другом контексте.
        /// </remarks>
        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;
            }
        }

        /// <summary>
        /// Создает копию контекста и возвращается на предыдущую операцию в текущем контексте, это позволяет начать операцию в одном потоке, а завершить - в другом.
        /// </summary>
        /// <returns>Контекст трассировки, который можно присоединить к другому потоку.</returns>
        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(IPromiseBase promise) {
            Safe.ArgumentNotNull(promise, "promise");

            var ctx = DetachLogicalOperation();
            promise.Finally(() => {
                var old = _current;
                TraceContext.Attach(ctx);
                TraceContext.Current.EndLogicalOperation();
                _current = old;
            });
        }

        /// <summary>
        /// Заврешает все начатые в этом контексте операции
        /// </summary>
        public void EndAllOperations() {
            while (m_bound != m_currentOperation)
                EndLogicalOperation();
        }

        void LogEvent(TraceEventType type, string format, params object[] args) {
            LogChannel<TraceEvent>.Default.LogEvent(this, TraceEvent.Create(type, format, args));
        }
    }
}