Пример #1
0
 void IActivityMonitorClient.OnAutoTagsChanged(CKTrait newTags)
 {
     if (_pushTopicAndAutoTagsToTarget)
     {
         _bridgeTarget.SetAutoTags(newTags);
     }
 }
Пример #2
0
 internal ActivityLogGroupConclusion(CKTrait t, string conclusion)
 {
     Debug.Assert(t != null && t.Context == ActivityMonitor.Tags.Context);
     Debug.Assert(conclusion != null);
     Tag  = t;
     Text = conclusion;
 }
Пример #3
0
 void DoSetAutoTags(CKTrait newTags)
 {
     Debug.Assert(_enteredThreadId == Thread.CurrentThread.ManagedThreadId);
     Debug.Assert(newTags != null && _currentTag != newTags && newTags.Context == Tags.Context);
     _currentTag = newTags;
     _output.BridgeTarget.TargetAutoTagsChanged(newTags);
     MonoParameterSafeCall((client, tags) => client.OnAutoTagsChanged(tags), newTags);
 }
Пример #4
0
        CKTraitContext(string name, char separator, bool shared, ICKBinaryReader r)
        {
            if (String.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException(Core.Impl.CoreResources.ArgumentMustNotBeNullOrWhiteSpace, "uniqueName");
            }
            Name      = name.Normalize();
            Separator = separator;
            if (!shared)
            {
                Monitor.Enter(_basicLock);
            }
            var found = _regexes.FirstOrDefault(reg => reg.Key[0] == separator);

            if (found.Key == null)
            {
                _separatorString = new String(separator, 1);
                string pattern = "(\\s*" + Regex.Escape(_separatorString) + "\\s*)+";
                _canonize2 = new Regex(pattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant);
                _regexes.Add(new KeyValuePair <string, Regex>(_separatorString, _canonize2));
            }
            else
            {
                _separatorString = found.Key;
                _canonize2       = found.Value;
            }
            if (!shared)
            {
                Monitor.Exit(_basicLock);
            }
            EmptyTrait = new CKTrait(this);
            if (r != null)
            {
                IEnumerable <KeyValuePair <string, CKTrait> > Read()
                {
                    yield return(new KeyValuePair <string, CKTrait>(String.Empty, EmptyTrait));

                    int count = r.ReadInt32();

                    for (int i = 0; i < count; ++i)
                    {
                        var s = r.ReadString();
                        yield return(new KeyValuePair <string, CKTrait>(s, new CKTrait(this, s)));
                    }
                }

                _tags = new ConcurrentDictionary <string, CKTrait>(Read(), StringComparer.Ordinal);
            }
            else
            {
                _tags = new ConcurrentDictionary <string, CKTrait>(StringComparer.Ordinal);
                _tags[String.Empty] = EmptyTrait;
            }
            EnumWithEmpty     = new CKTrait[] { EmptyTrait };
            _creationLock     = new Object();
            _independentIndex = shared ? 0 : Interlocked.Increment(ref _nextIndependentIndex);
        }
Пример #5
0
 /// <summary>
 /// Initializes a new <see cref="ActivityMonitorLogData"/>.
 /// </summary>
 /// <param name="level">Log level. Can not be <see cref="LogLevel.None"/>.</param>
 /// <param name="exception">Exception of the log. Can be null.</param>
 /// <param name="tags">Tags (from <see cref="ActivityMonitor.Tags"/>) to associate to the log. It will be union-ed with the current <see cref="IActivityMonitor.AutoTags"/>.</param>
 /// <param name="text">Text of the log. Can be null or empty only if <paramref name="exception"/> is not null: the <see cref="T:Exception.Message"/> is the text.</param>
 /// <param name="logTime">
 /// Time of the log.
 /// You can use <see cref="DateTimeStamp.UtcNow"/> or <see cref="ActivityMonitorExtension.NextLogTime">IActivityMonitor.NextLogTime()</see> extension method.
 /// </param>
 /// <param name="fileName">Name of the source file that emitted the log. Can be null.</param>
 /// <param name="lineNumber">Line number in the source file that emitted the log. Can be null.</param>
 public ActivityMonitorLogData(LogLevel level, Exception exception, CKTrait tags, string text, DateTimeStamp logTime, string fileName, int lineNumber)
     : this(level, fileName, lineNumber)
 {
     if (MaskedLevel == LogLevel.None || MaskedLevel == LogLevel.Mask)
     {
         throw new ArgumentException(Impl.ActivityMonitorResources.ActivityMonitorInvalidLogLevel, "level");
     }
     Initialize(text, exception, tags, logTime);
 }
 internal Entry(CKTrait tags, LogLevel level, string text, DateTimeStamp logTime, Exception ex)
 {
     Debug.Assert((level & LogLevel.IsFiltered) == 0);
     Tags        = tags;
     MaskedLevel = level;
     LogTime     = logTime;
     Text        = text;
     Exception   = ex;
 }
        /// <summary>
        /// Sends a text obtained through a delegate with an exception and associated tags.
        /// The delegate will be called only if the log is not filtered.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorLineSender"/> object.</param>
        /// <param name="ex">The exception. Must not be null.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="text">Function that returns a string. Must not be null.</param>
        public static void Send(this IActivityMonitorLineSender @this, Exception ex, CKTrait tags, Func <string> text)
        {
            ActivityMonitorLineSender s = (ActivityMonitorLineSender)@this;

            if (s.IsRejected)
            {
                return;
            }
            s.InitializeAndSend(ex, tags, text == null ? null : text());
        }
        /// <summary>
        /// Sends a formatted text with an exception and associated tags.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorLineSender"/> object.</param>
        /// <param name="ex">The exception. Must not be null.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="format">The text format of the log with 4 placeholders.</param>
        /// <param name="arguments">Multiple parameters to format.</param>
        static public void Send(this IActivityMonitorLineSender @this, Exception ex, CKTrait tags, string format, params object[] arguments)
        {
            ActivityMonitorLineSender s = (ActivityMonitorLineSender)@this;

            if (s.IsRejected)
            {
                return;
            }
            s.InitializeAndSend(ex, tags, format == null ? null : String.Format(format, arguments));
        }
        /// <summary>
        /// Sends a text with an exception and associated tags.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorLineSender"/> object.</param>
        /// <param name="ex">The exception. Must not be null.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="text">The text of the log.</param>
        static public void Send(this IActivityMonitorLineSender @this, Exception ex, CKTrait tags, string text)
        {
            ActivityMonitorLineSender s = (ActivityMonitorLineSender)@this;

            if (s.IsRejected)
            {
                return;
            }
            s.InitializeAndSend(ex, tags, text);
        }
        /// <summary>
        /// Sends a log with a text obtained through a parameterized delegate with associated tags.
        /// The delegate will be called only if the log is not filtered.
        /// </summary>
        /// <typeparam name="T1">Type of the first parameter that <paramref name="text"/> accepts.</typeparam>
        /// <typeparam name="T2">Type of the second parameter that <paramref name="text"/> accepts.</typeparam>
        /// <typeparam name="T3">Type of the third parameter that <paramref name="text"/> accepts.</typeparam>
        /// <param name="this">This <see cref="IActivityMonitorLineSender"/> object.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="text">Function that returns a string. Must not be null.</param>
        /// <param name="param1">First parameter for the <paramref name="text"/> delegate.</param>
        /// <param name="param2">Second parameter for the <paramref name="text"/> delegate.</param>
        /// <param name="param3">Third parameter for the <paramref name="text"/> delegate.</param>
        public static void Send <T1, T2, T3>(this IActivityMonitorLineSender @this, CKTrait tags, Func <T1, T2, T3, string> text, T1 param1, T2 param2, T3 param3)
        {
            ActivityMonitorLineSender s = (ActivityMonitorLineSender)@this;

            if (s.IsRejected)
            {
                return;
            }
            s.InitializeAndSend(null, tags, text == null ? null : text(param1, param2, param3));
        }
        /// <summary>
        /// Sends a formatted text with associated tags.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorLineSender"/> object.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="format">The text format of the log with 3 placeholders.</param>
        /// <param name="arg0">Parameter to format (placeholder {0}).</param>
        /// <param name="arg1">Parameter to format (placeholder {1}).</param>
        /// <param name="arg2">Parameter to format (placeholder {2}).</param>
        static public void Send(this IActivityMonitorLineSender @this, CKTrait tags, string format, object arg0, object arg1, object arg2)
        {
            ActivityMonitorLineSender s = (ActivityMonitorLineSender)@this;

            if (s.IsRejected)
            {
                return;
            }
            s.InitializeAndSend(null, tags, format == null ? null : String.Format(format, arg0, arg1, arg2));
        }
Пример #12
0
        /// <summary>
        /// Sends a text with associated tags.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorGroupSender"/> object.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="text">The text of the log.</param>
        static public IDisposableGroup Send(this IActivityMonitorGroupSender @this, CKTrait tags, string text)
        {
            ActivityMonitorGroupSender s = (ActivityMonitorGroupSender)@this;

            if (s.IsRejected)
            {
                return(s.Monitor.UnfilteredOpenGroup(s));
            }
            return(s.InitializeAndSend(null, tags, text));
        }
 internal void TargetAutoTagsChanged(CKTrait newTags)
 {
     foreach (var b in _callbacks)
     {
         if (b.PullTopicAndAutoTagsFromTarget)
         {
             b.OnTargetAutoTagsChanged(newTags);
         }
     }
 }
Пример #14
0
        /// <summary>
        /// Sends a formatted text with associated tags.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorGroupSender"/> object.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="format">The text format of the log with 5 placeholders.</param>
        /// <param name="arguments">Multiple parameters to format.</param>
        static public IDisposableGroup Send(this IActivityMonitorGroupSender @this, CKTrait tags, string format, params object[] arguments)
        {
            ActivityMonitorGroupSender s = (ActivityMonitorGroupSender)@this;

            if (s.IsRejected)
            {
                return(s.Monitor.UnfilteredOpenGroup(s));
            }
            return(s.InitializeAndSend(null, tags, format == null ? null : String.Format(format, arguments)));
        }
Пример #15
0
 static Tags()
 {
     Context                 = new CKTraitContext("ActivityMonitor");
     Empty                   = Context.EmptyTrait;
     UserConclusion          = Context.FindOrCreate("c:User");
     GetTextConclusion       = Context.FindOrCreate("c:GetText");
     MonitorTopicChanged     = Context.FindOrCreate("MonitorTopicChanged");
     CreateDependentActivity = Context.FindOrCreate("dep:CreateActivity");
     StartDependentActivity  = Context.FindOrCreate("dep:StartActivity");
 }
Пример #16
0
        /// <summary>
        /// Initializes this data.
        /// </summary>
        /// <param name="text">
        /// Text of the log. Can be null or empty: if <paramref name="exception"/> is not null,
        /// the <see cref="Exception.Message"/> becomes the text otherwise <see cref="ActivityMonitor.NoLogText"/> is used.
        /// </param>
        /// <param name="exception">Exception of the log. Can be null.</param>
        /// <param name="tags">
        /// Tags (from <see cref="ActivityMonitor.Tags"/>) to associate to the log.
        /// It will be union-ed with the current <see cref="IActivityMonitor.AutoTags"/>.</param>
        /// <param name="logTime">
        /// Time of the log.
        /// You can use <see cref="DateTimeStamp.UtcNow"/> or <see cref="ActivityMonitorExtension.NextLogTime">IActivityMonitor.NextLogTime()</see> extension method.
        /// </param>
        public void Initialize(string text, Exception exception, CKTrait tags, DateTimeStamp logTime)
        {
            if (string.IsNullOrEmpty((_text = text)))
            {
                _text = exception == null ? ActivityMonitor.NoLogText : exception.Message;
            }

            _exception = exception;
            _tags      = tags ?? ActivityMonitor.Tags.Empty;
            _logTime   = logTime;
        }
 /// <summary>
 /// Initializes a new <see cref="ActivityMonitorTextWriterClient"/> bound to a
 /// function that must write a string, with a filter.
 /// </summary>
 /// <param name="writer">Function that writes the content.</param>
 /// <param name="filter">Filter to apply</param>
 public ActivityMonitorTextWriterClient(Action <string> writer, LogFilter filter)
     : base(filter)
 {
     if (writer == null)
     {
         throw new ArgumentNullException("writer");
     }
     _writer      = writer;
     _buffer      = new StringBuilder();
     _prefixLevel = _prefix = String.Empty;
     _currentTags = ActivityMonitor.Tags.Empty;
 }
Пример #18
0
        CKTrait FindOrCreate(string traits, bool create)
        {
            if (traits == null || traits.Length == 0)
            {
                return(_empty);
            }
            traits = traits.Normalize();
            if (traits.IndexOfAny(new[] { '\n', '\r' }) >= 0)
            {
                throw new ArgumentException(Impl.CoreResources.TraitsMustNotBeMultiLineString);
            }
            CKTrait m;

            if (!_traits.TryGetValue(traits, out m))
            {
                int      traitCount;
                string[] splitTraits = SplitMultiTrait(traits, out traitCount);
                if (traitCount <= 0)
                {
                    return(_empty);
                }
                if (traitCount == 1)
                {
                    m = FindOrCreateAtomicTrait(splitTraits[0], create);
                }
                else
                {
                    traits = String.Join(_separatorString, splitTraits, 0, traitCount);
                    if (!_traits.TryGetValue(traits, out m))
                    {
                        CKTrait[] atomics = new CKTrait[traitCount];
                        for (int i = 0; i < traitCount; ++i)
                        {
                            CKTrait trait = FindOrCreateAtomicTrait(splitTraits[i], create);
                            if ((atomics[i] = trait) == null)
                            {
                                return(null);
                            }
                        }
                        lock ( _creationLock )
                        {
                            if (!_traits.TryGetValue(traits, out m))
                            {
                                m = new CKTrait(this, traits, atomics);
                                _traits[traits] = m;
                            }
                        }
                    }
                    Debug.Assert(!m.IsAtomic && m.AtomicTraits.Count == traitCount, "Combined trait.");
                }
            }
            return(m);
        }
Пример #19
0
 internal DateTimeStamp CombineTagsAndAdjustLogTime(CKTrait tags, DateTimeStamp lastLogTime)
 {
     if (_tags.IsEmpty)
     {
         _tags = tags;
     }
     else
     {
         _tags = _tags.Union(tags);
     }
     return(_logTime = new DateTimeStamp(lastLogTime, _logTime.IsKnown ? _logTime : DateTimeStamp.UtcNow));
 }
Пример #20
0
 /// <summary>
 /// Called by IActivityMonitorBoundClient clients to initialize Topic and AutoTag from
 /// inside their SetMonitor or any other methods provided that a reentrant and concurrent lock
 /// has been obtained (otherwise an InvalidOperationException is thrown).
 /// </summary>
 void IActivityMonitorImpl.InitializeTopicAndAutoTags(string newTopic, CKTrait newTags, string fileName, int lineNumber)
 {
     RentrantOnlyCheck();
     if (newTopic != null && _topic != newTopic)
     {
         DoSetTopic(newTopic, fileName, lineNumber);
     }
     if (newTags != null && _currentTag != newTags)
     {
         DoSetAutoTags(newTags);
     }
 }
Пример #21
0
        /// <summary>
        /// Sends a formatted text with associated tags.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorGroupSender"/> object.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="format">The text format of the log with 1 placeholders.</param>
        /// <param name="arg0">Parameter to format (placeholder {0}).</param>
        static public IDisposableGroup Send(this IActivityMonitorGroupSender @this, CKTrait tags, string format, object arg0)
        {
            ActivityMonitorGroupSender s = (ActivityMonitorGroupSender)@this;

            if (s.IsRejected)
            {
                return(s.Monitor.UnfilteredOpenGroup(s));
            }
            if (arg0 is Exception)
            {
                throw new ArgumentException(Impl.ActivityMonitorResources.PossibleWrongOverloadUseWithException, "arg0");
            }
            return(s.InitializeAndSend(null, tags, format == null ? null : String.Format(format, arg0)));
        }
        /// <summary>
        /// Sends a formatted text with associated tags.
        /// </summary>
        /// <param name="this">This <see cref="IActivityMonitorLineSender"/> object.</param>
        /// <param name="tags">Tags for the log.</param>
        /// <param name="format">The text format of the log with 1 placeholders.</param>
        /// <param name="arg0">Parameter to format (placeholder {0}).</param>
        static public void Send(this IActivityMonitorLineSender @this, CKTrait tags, string format, object arg0)
        {
            ActivityMonitorLineSender s = (ActivityMonitorLineSender)@this;

            if (s.IsRejected)
            {
                return;
            }
            if (arg0 is Exception)
            {
                throw new ArgumentException(Impl.ActivityMonitorResources.PossibleWrongOverloadUseWithException, "arg0");
            }
            s.InitializeAndSend(null, tags, format == null ? null : String.Format(format, arg0));
        }
Пример #23
0
 /// <summary>
 /// Initializes a new conclusion for a group.
 /// </summary>
 /// <param name="conclusion">Must not be null (may be empty).</param>
 /// <param name="tag">Must be null or be registered in <see cref="ActivityMonitor.Tags"/>.</param>
 public ActivityLogGroupConclusion(string conclusion, CKTrait tag = null)
 {
     if (conclusion == null)
     {
         throw new ArgumentNullException("conclusion");
     }
     if (tag == null)
     {
         tag = ActivityMonitor.Tags.Empty;
     }
     else if (tag.Context != ActivityMonitor.Tags.Context)
     {
         throw new ArgumentException(Impl.ActivityMonitorResources.ActivityMonitorTagMustBeRegistered, "tag");
     }
     Tag  = tag;
     Text = conclusion;
 }
Пример #24
0
        CKTrait FindOrCreateAtomicTrait(string trait, bool create)
        {
            CKTrait m;

            if (!_traits.TryGetValue(trait, out m) && create)
            {
                lock ( _creationLock )
                {
                    if (!_traits.TryGetValue(trait, out m))
                    {
                        m = new CKTrait(this, trait);
                        _traits[trait] = m;
                    }
                }
                Debug.Assert(m.IsAtomic, "Special construction for atomic traits.");
            }
            return(m);
        }
        /// <summary>
        /// Writes all information.
        /// </summary>
        /// <param name="data">Log data.</param>
        protected override void OnContinueOnSameLevel(ActivityMonitorLogData data)
        {
            var w = _buffer.Clear();

            w.AppendMultiLine(_prefixLevel, data.Text, true);
            if (_currentTags != data.Tags)
            {
                w.Append(" -[").Append(data.Tags).Append(']');
                _currentTags = data.Tags;
            }
            w.AppendLine();
            if (data.Exception != null)
            {
                DumpException(w, _prefix, !data.IsTextTheExceptionMessage, data.Exception);
            }

            _writer(_buffer.ToString());
        }
Пример #26
0
        /// <summary>
        /// Initializes a new context for traits with the given separator.
        /// </summary>
        /// <param name="name">Name for the context. Must not be null nor whitespace.</param>
        /// <param name="separator">Separator if it must differ from '|'.</param>
        public CKTraitContext(string name, char separator = '|')
        {
            if (String.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException(Impl.CoreResources.ArgumentMustNotBeNullOrWhiteSpace, "uniqueName");
            }
            _uniqueName      = name.Normalize();
            _uniqueIndex     = Interlocked.Increment(ref _index);
            _separator       = separator;
            _separatorString = new String(separator, 1);
            string pattern = "(\\s*" + Regex.Escape(_separatorString) + "\\s*)+";

            _canonize2            = new Regex(pattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant);
            _empty                = new CKTrait(this);
            _traits               = new ConcurrentDictionary <string, CKTrait>(StringComparer.Ordinal);
            _traits[String.Empty] = _empty;
            _enumerableWithEmpty  = new CKTrait[] { _empty };
            _creationLock         = new Object();
        }
Пример #27
0
        /// <summary>
        /// Obtains a trait from a list of atomic (already sorted) traits.
        /// Used by fall back generation.
        /// </summary>
        internal CKTrait FindOrCreate(CKTrait[] atomicTraits, int count)
        {
            Debug.Assert(count > 1, "Atomic traits are handled directly.");

            Debug.Assert(!Array.Exists(atomicTraits, mA => mA.Context != this || mA.AtomicTraits.Count != 1), "Traits are from this Context and they are atomic and not empty.");

            StringBuilder b = new StringBuilder(atomicTraits[0].ToString());

            for (int i = 1; i < count; ++i)
            {
                Debug.Assert(StringComparer.Ordinal.Compare(atomicTraits[i - 1].ToString(), atomicTraits[i].ToString()) < 0, "Traits are already sorted and NO DUPLICATE exists.");
                b.Append(_separator).Append(atomicTraits[i].ToString());
            }
            string  traits = b.ToString();
            CKTrait m;

            if (!_traits.TryGetValue(traits, out m))
            {
                // We must clone the array since fall backs generation reuses it.
                if (atomicTraits.Length != count)
                {
                    CKTrait[] subArray = new CKTrait[count];
                    Array.Copy(atomicTraits, subArray, count);
                    atomicTraits = subArray;
                }
                else
                {
                    atomicTraits = (CKTrait[])atomicTraits.Clone();
                }
                lock ( _creationLock )
                {
                    if (!_traits.TryGetValue(traits, out m))
                    {
                        m = new CKTrait(this, traits, atomicTraits);
                        _traits[traits] = m;
                    }
                }
            }
            return(m);
        }
Пример #28
0
 void Build(ActivityMonitorOutput output, CKTrait tags, bool applyAutoConfigurations)
 {
     Debug.Assert(Tags.Context.Separator == '|', "Separator must be the |.");
     _output = output;
     _groups = new Group[8];
     for (int i = 0; i < _groups.Length; ++i)
     {
         _groups[i] = new Group(this, i);
     }
     _currentTag  = tags ?? Tags.Empty;
     _uniqueId    = Guid.NewGuid();
     _topic       = String.Empty;
     _lastLogTime = DateTimeStamp.MinValue;
     if (applyAutoConfigurations)
     {
         var autoConf = AutoConfiguration;
         if (autoConf != null)
         {
             autoConf(this);
         }
     }
 }
Пример #29
0
        /// <summary>
        /// Obtains a trait from a list of atomic (already sorted) traits.
        /// Used by the Add, Toggle, Remove, Intersect methods.
        /// </summary>
        internal CKTrait FindOrCreate(List <CKTrait> atomicTraits)
        {
            if (atomicTraits.Count == 0)
            {
                return(_empty);
            }
            Debug.Assert(atomicTraits[0].Context == this, "This is one of our traits.");
            Debug.Assert(atomicTraits[0].AtomicTraits.Count == 1, "This is an atomic trait and not the empty one.");
            if (atomicTraits.Count == 1)
            {
                return(atomicTraits[0]);
            }
            StringBuilder b = new StringBuilder(atomicTraits[0].ToString());

            for (int i = 1; i < atomicTraits.Count; ++i)
            {
                Debug.Assert(atomicTraits[i].Context == this, "This is one of our traits.");
                Debug.Assert(atomicTraits[i].AtomicTraits.Count == 1, "This is an atomic trait and not the empty one.");
                Debug.Assert(StringComparer.Ordinal.Compare(atomicTraits[i - 1].ToString(), atomicTraits[i].ToString()) < 0,
                             "Traits are already sorted and NO DUPLICATES exist.");
                b.Append(_separator).Append(atomicTraits[i].ToString());
            }
            string  traits = b.ToString();
            CKTrait m;

            if (!_traits.TryGetValue(traits, out m))
            {
                lock ( _creationLock )
                {
                    if (!_traits.TryGetValue(traits, out m))
                    {
                        m = new CKTrait(this, traits, atomicTraits.ToArray());
                        _traits[traits] = m;
                    }
                }
            }
            return(m);
        }
        /// <summary>
        /// Writes a group opening.
        /// </summary>
        /// <param name="g">Group information.</param>
        protected override void OnGroupOpen(IActivityLogGroup g)
        {
            var    w          = _buffer.Clear();
            string levelLabel = g.MaskedGroupLevel.ToString();
            string start      = string.Format("{0}> {1}: ", _prefix, levelLabel);

            _prefix     += "|  ";
            _prefixLevel = _prefix;
            string prefixLabel = _prefixLevel + new string( ' ', levelLabel.Length + 1 );

            w.Append(start).AppendMultiLine(prefixLabel, g.GroupText, false);
            if (_currentTags != g.GroupTags)
            {
                w.Append(" -[").Append(g.GroupTags).Append(']');
                _currentTags = g.GroupTags;
            }
            w.AppendLine();
            if (g.Exception != null)
            {
                DumpException(w, _prefix, !g.IsGroupTextTheExceptionMessage, g.Exception);
            }
            _writer(_buffer.ToString());
        }