Пример #1
0
        IDisposableGroup DoOpenGroup(ActivityMonitorGroupData data)
        {
            Debug.Assert(_enteredThreadId == Thread.CurrentThread.ManagedThreadId);

            int idxNext = _current != null ? _current.Index + 1 : 0;

            if (idxNext == _groups.Length)
            {
                Array.Resize(ref _groups, _groups.Length * 2);
                for (int i = idxNext; i < _groups.Length; ++i)
                {
                    _groups[i] = new Group(this, i);
                }
            }
            _current = _groups[idxNext];
            if (!data.IsFilteredLog)
            {
                if (_actualFilterIsDirty)
                {
                    DoResyncActualFilter();
                }
                if (_actualFilter.Group == LogLevelFilter.Off || data.Level == LogLevel.None)
                {
                    _current.InitializeRejectedGroup(data);
                    return(_current);
                }
            }
            _lastLogTime = data.CombineTagsAndAdjustLogTime(_currentTag, _lastLogTime);
            _current.Initialize(data);
            _currentUnfiltered = _current;
            MonoParameterSafeCall((client, group) => client.OnOpenGroup(group), _current);
            return(_current);
        }
 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;
 }
Пример #3
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;
        }
Пример #4
0
        static string DumpErrorText(DateTimeStamp logTime, string text, LogLevel level, CKTrait tags, CKExceptionData exData)
        {
            StringBuilder buffer = CreateHeader(logTime, text, level, tags);

            if (exData != null)
            {
                exData.ToStringBuilder(buffer, String.Empty);
            }
            WriteFooter(level, buffer);
            return(buffer.ToString());
        }
Пример #5
0
        static string DumpErrorText(DateTimeStamp logTime, string text, LogLevel level, Exception ex, CKTrait tags)
        {
            StringBuilder buffer = CreateHeader(logTime, text, level, tags);

            if (ex != null)
            {
                ActivityMonitorTextWriterClient.DumpException(buffer, String.Empty, !ReferenceEquals(text, ex.Message), ex);
            }
            WriteFooter(level, buffer);
            return(buffer.ToString());
        }
Пример #6
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));
 }
Пример #7
0
 /// <summary>
 /// Closes the current <see cref="Group"/>. Optional parameter is polymorphic. It can be a string, a <see cref="ActivityLogGroupConclusion"/>,
 /// a <see cref="List{T}"/> or an <see cref="IEnumerable{T}"/> of ActivityLogGroupConclusion, or any object with an overridden <see cref="Object.ToString"/> method.
 /// See remarks (especially for List&lt;ActivityLogGroupConclusion&gt;).
 /// </summary>
 /// <param name="userConclusion">Optional string, enumerable of <see cref="ActivityLogGroupConclusion"/>) or object to conclude the group. See remarks.</param>
 /// <param name="logTime">Times-tamp of the group closing.</param>
 /// <remarks>
 /// An untyped object is used here to easily and efficiently accommodate both string and already existing ActivityLogGroupConclusion.
 /// When a List&lt;ActivityLogGroupConclusion&gt; is used, it will be directly used to collect conclusion objects (new conclusions will be added to it). This is an optimization.
 /// </remarks>
 public virtual void CloseGroup(DateTimeStamp logTime, object userConclusion = null)
 {
     ReentrantAndConcurrentCheck();
     try
     {
         DoCloseGroup(logTime, userConclusion);
     }
     finally
     {
         ReentrantAndConcurrentRelease();
     }
 }
 static bool MatchOriginatorAndTime(StringMatcher m, out Guid id, out DateTimeStamp time)
 {
     time = DateTimeStamp.MinValue;
     if (!m.TryMatchGuid(out id))
     {
         return(false);
     }
     if (!m.TryMatchText(" at "))
     {
         return(false);
     }
     return(m.MatchDateTimeStamp(out time));
 }
Пример #9
0
        static StringBuilder CreateHeader(DateTimeStamp logTime, string text, LogLevel level, CKTrait tags)
        {
            StringBuilder buffer = new StringBuilder();

            buffer.Append('<').Append(level.ToString()).Append('>').Append('@').Append(logTime.ToString());
            if (tags != null && !tags.IsEmpty)
            {
                buffer.Append(" - ").Append(tags.ToString());
            }
            buffer.AppendLine();
            if (text != null && text.Length > 0)
            {
                buffer.Append(text).AppendLine();
            }
            return(buffer);
        }
Пример #10
0
        void DoUnfilteredLog(ActivityMonitorLogData data)
        {
            Debug.Assert(_enteredThreadId == Thread.CurrentThread.ManagedThreadId);
            Debug.Assert(data.Level != LogLevel.None);
            Debug.Assert(!String.IsNullOrEmpty(data.Text));

            if (!data.IsFilteredLog)
            {
                if (_actualFilterIsDirty)
                {
                    DoResyncActualFilter();
                }
                if (_actualFilter.Line == LogLevelFilter.Off)
                {
                    return;
                }
            }

            _lastLogTime = data.CombineTagsAndAdjustLogTime(_currentTag, _lastLogTime);
            List <IActivityMonitorClient> buggyClients = null;

            foreach (var l in _output.Clients)
            {
                try
                {
                    l.OnUnfilteredLog(data);
                }
                catch (Exception exCall)
                {
                    CriticalErrorCollector.Add(exCall, l.GetType().FullName);
                    if (buggyClients == null)
                    {
                        buggyClients = new List <IActivityMonitorClient>();
                    }
                    buggyClients.Add(l);
                }
            }
            if (buggyClients != null)
            {
                foreach (var l in buggyClients)
                {
                    _output.ForceRemoveBuggyClient(l);
                }
                _clientFilter = DoGetBoundClientMinimalFilter();
                UpdateActualFilter();
            }
        }
Пример #11
0
        static string FindUniqueTimedFileOrFolder(string pathPrefix, string fileSuffix, DateTime time, int maxTryBeforeGuid, Func <string, bool> tester)
        {
            if (pathPrefix == null)
            {
                throw new ArgumentNullException("pathPrefix");
            }
            if (fileSuffix == null)
            {
                throw new ArgumentNullException("fileSuffix");
            }
            if (maxTryBeforeGuid < 0)
            {
                throw new ArgumentOutOfRangeException("maxTryBeforeGuid");
            }

            DateTimeStamp timeStamp = new DateTimeStamp(time);
            int           counter   = 0;
            string        result    = pathPrefix + timeStamp.ToString() + fileSuffix;

            for ( ; ;)
            {
                if (tester(result))
                {
                    break;
                }
                if (counter < maxTryBeforeGuid)
                {
                    timeStamp = new DateTimeStamp(timeStamp, timeStamp);
                    result    = pathPrefix + timeStamp.ToString() + fileSuffix;
                }
                else
                {
                    if (counter == maxTryBeforeGuid + 1)
                    {
                        throw new Exception(Impl.CoreResources.FileUtilUnableToCreateUniqueTimedFileOrFolder);
                    }
                    if (counter == maxTryBeforeGuid)
                    {
                        result = pathPrefix + FormatTimedUniqueFilePart(time) + fileSuffix;
                    }
                }
                ++counter;
            }
            return(result);
        }
Пример #12
0
 /// <summary>
 /// Initializes or reinitializes this group (if it has been disposed).
 /// </summary>
 internal void Initialize(ActivityMonitorGroupData data)
 {
     SavedMonitorFilter = Monitor._configuredFilter;
     SavedMonitorTags   = Monitor._currentTag;
     if ((_unfilteredParent = Monitor._currentUnfiltered) != null)
     {
         _depth = _unfilteredParent._depth + 1;
     }
     else
     {
         _depth = 1;
     }
     // Logs everything when a Group is an error: we then have full details available without
     // logging all with Error or Fatal.
     if (data.MaskedLevel >= LogLevel.Error && Monitor._configuredFilter != LogFilter.Debug)
     {
         Monitor.DoSetConfiguredFilter(LogFilter.Debug);
     }
     _closeLogTime = DateTimeStamp.MinValue;
     _data         = data;
 }
Пример #13
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);
         }
     }
 }
Пример #14
0
        /// <summary>
        /// Matches a <see cref="DateTimeStamp"/>.
        /// </summary>
        /// <param name="this">This <see cref="StringMatcher"/>.</param>
        /// <param name="time">Resulting time stamp on successful match; <see cref="DateTimeStamp.Unknown"/> otherwise.</param>
        /// <returns>True if the time stamp has been matched.</returns>
        static public bool MatchDateTimeStamp(this StringMatcher @this, out DateTimeStamp time)
        {
            time = DateTimeStamp.Unknown;
            int      savedIndex = @this.StartIndex;
            DateTime t;

            if ([email protected](out t))
            {
                return(@this.SetError());
            }
            byte uniquifier = 0;

            if (@this.MatchChar('('))
            {
                int unique;
                if ([email protected](out unique, 0, 255) || [email protected](')'))
                {
                    return(@this.BackwardAddError(savedIndex));
                }
                uniquifier = (byte)unique;
            }
            time = new DateTimeStamp(t, uniquifier);
            return(@this.Forward(0));
        }
Пример #15
0
 /// <summary>
 /// Logs a text regardless of <see cref="IActivityMonitor.ActualFilter">ActualFilter</see> level.
 /// </summary>
 /// <param name="this">This <see cref="IActivityMonitor"/>.</param>
 /// <param name="tags">
 /// Tags (from <see cref="ActivityMonitor.Tags"/>) to associate to the log.
 /// These tags will be union-ed with the current <see cref="IActivityMonitor.AutoTags">AutoTags</see>.
 /// </param>
 /// <param name="level">Log level. Must not be <see cref="LogLevel.None"/>.</param>
 /// <param name="text">Text to log. Must not be null or empty.</param>
 /// <param name="logTime">
 /// Time-stamp of the log entry.
 /// You can use <see cref="DateTimeStamp.UtcNow"/> or <see cref="ActivityMonitorExtension.NextLogTime">IActivityMonitor.NextLogTime()</see> extension method.
 /// </param>
 /// <param name="ex">Optional exception associated to the log. When not null, a Group is automatically created.</param>
 /// <param name="fileName">The source code file name from which the log is emitted.</param>
 /// <param name="lineNumber">The line number in the source from which the log is emitted.</param>
 /// <remarks>
 /// The <paramref name="text"/> can not be null or empty.
 /// <para>
 /// Each call to log is considered as a unit of text: depending on the rendering engine, a line or a
 /// paragraph separator (or any appropriate separator) should be appended between each text if
 /// the <paramref name="level"/> is the same as the previous one.
 /// </para>
 /// <para>If needed, the special text <see cref="ActivityMonitor.ParkLevel"/> ("PARK-LEVEL") can be used as a convention
 /// to break the current <see cref="LogLevel"/> and resets it: the next log, even with the same LogLevel, should be
 /// treated as if a different LogLevel is used.
 /// </para>
 /// </remarks>
 static public void UnfilteredLog(this IActivityMonitor @this, CKTrait tags, LogLevel level, string text, DateTimeStamp logTime, Exception ex, [CallerFilePath] string fileName = null, [CallerLineNumber] int lineNumber = 0)
 {
     @this.UnfilteredLog(new ActivityMonitorLogData(level, ex, tags, text, logTime, fileName, lineNumber));
 }
Пример #16
0
 /// <summary>
 /// Initializes a new <see cref="ActivityMonitorGroupData"/>.
 /// </summary>
 /// <param name="level">Log level. Can not be <see cref="LogLevel.None"/>.</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="Exception.Message"/> is the text.</param>
 /// <param name="logTime">
 /// Time of the log.
 /// You may use <see cref="DateTimeStamp.UtcNow"/> or <see cref="ActivityMonitorExtension.NextLogTime">IActivityMonitor.NextLogTime()</see> extension method.
 /// </param>
 /// <param name="exception">Exception of the log. Can be null.</param>
 /// <param name="getConclusionText">Optional function that provides delayed obtention of the group conclusion: will be called on group closing.</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 ActivityMonitorGroupData(LogLevel level, CKTrait tags, string text, DateTimeStamp logTime, Exception exception, Func <string> getConclusionText, string fileName, int lineNumber)
     : base(level, exception, tags, text, logTime, fileName, lineNumber)
 {
     _getConclusion = getConclusionText;
 }
Пример #17
0
 /// <summary>
 /// Initializes this group data.
 /// </summary>
 /// <param name="text">Text of the log. Can be null or empty only if <paramref name="exception"/> is not null: the <see cref="Exception.Message"/> is the text.</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 may use <see cref="DateTimeStamp.UtcNow"/> or <see cref="ActivityMonitorExtension.NextLogTime">IActivityMonitor.NextLogTime()</see> extension method.
 /// </param>
 /// <param name="getConclusionText">Optional function that provides delayed obtention of the group conclusion: will be called on group closing.</param>
 public void Initialize(string text, Exception exception, CKTrait tags, DateTimeStamp logTime, Func <string> getConclusionText)
 {
     base.Initialize(text, exception, tags, logTime);
     _getConclusion = getConclusionText;
 }
Пример #18
0
        void DoCloseGroup(DateTimeStamp logTime, object userConclusion = null)
        {
            Debug.Assert(_enteredThreadId == Thread.CurrentThread.ManagedThreadId);
            Group g = _current;

            if (g != null)
            {
                // Handles the rejected case first (easiest).
                if (g.IsRejectedGroup)
                {
                    if (g.SavedMonitorFilter != _configuredFilter)
                    {
                        DoSetConfiguredFilter(g.SavedMonitorFilter);
                    }
                    _currentTag = g.SavedMonitorTags;
                    _current    = g.Index > 0 ? _groups[g.Index - 1] : null;
                }
                else
                {
                    #region Closing the group
                    g.CloseLogTime = _lastLogTime = new DateTimeStamp(_lastLogTime, logTime.IsKnown ? logTime : DateTimeStamp.UtcNow);
                    var conclusions = userConclusion as List <ActivityLogGroupConclusion>;
                    if (conclusions == null && userConclusion != null)
                    {
                        conclusions = new List <ActivityLogGroupConclusion>();
                        string s = userConclusion as string;
                        if (s != null)
                        {
                            conclusions.Add(new ActivityLogGroupConclusion(Tags.UserConclusion, s));
                        }
                        else
                        {
                            if (userConclusion is ActivityLogGroupConclusion)
                            {
                                conclusions.Add((ActivityLogGroupConclusion)userConclusion);
                            }
                            else
                            {
                                IEnumerable <ActivityLogGroupConclusion> multi = userConclusion as IEnumerable <ActivityLogGroupConclusion>;
                                if (multi != null)
                                {
                                    conclusions.AddRange(multi);
                                }
                                else
                                {
                                    conclusions.Add(new ActivityLogGroupConclusion(Tags.UserConclusion, userConclusion.ToString()));
                                }
                            }
                        }
                    }
                    g.GroupClosing(ref conclusions);

                    bool hasBuggyClients = false;
                    List <IActivityMonitorClient> buggyClients = null;
                    foreach (var l in _output.Clients)
                    {
                        try
                        {
                            l.OnGroupClosing(g, ref conclusions);
                        }
                        catch (Exception exCall)
                        {
                            CriticalErrorCollector.Add(exCall, l.GetType().FullName);
                            if (buggyClients == null)
                            {
                                buggyClients = new List <IActivityMonitorClient>();
                            }
                            buggyClients.Add(l);
                        }
                    }
                    if (buggyClients != null)
                    {
                        foreach (var l in buggyClients)
                        {
                            _output.ForceRemoveBuggyClient(l);
                        }
                        buggyClients.Clear();
                        hasBuggyClients = true;
                    }
                    if (g.SavedMonitorFilter != _configuredFilter)
                    {
                        DoSetConfiguredFilter(g.SavedMonitorFilter);
                    }
                    _currentTag        = g.SavedMonitorTags;
                    _current           = g.Index > 0 ? _groups[g.Index - 1] : null;
                    _currentUnfiltered = (Group)g.Parent;

                    var sentConclusions = conclusions != null?conclusions.ToArray() : Util.Array.Empty <ActivityLogGroupConclusion>();

                    foreach (var l in _output.Clients)
                    {
                        try
                        {
                            l.OnGroupClosed(g, sentConclusions);
                        }
                        catch (Exception exCall)
                        {
                            CriticalErrorCollector.Add(exCall, l.GetType().FullName);
                            if (buggyClients == null)
                            {
                                buggyClients = new List <IActivityMonitorClient>();
                            }
                            buggyClients.Add(l);
                        }
                    }
                    if (buggyClients != null)
                    {
                        foreach (var l in buggyClients)
                        {
                            _output.ForceRemoveBuggyClient(l);
                        }
                        hasBuggyClients = true;
                    }
                    if (hasBuggyClients)
                    {
                        _clientFilter = DoGetBoundClientMinimalFilter();
                        UpdateActualFilter();
                    }
                    #endregion
                }
                string prevTopic = g.PreviousTopic;
                if (prevTopic != null)
                {
                    DoSetTopic(prevTopic, g.FileName, g.LineNumber);
                }
                g.GroupClosed();
            }
        }
Пример #19
0
 /// <summary>
 /// Opens a group regardless of <see cref="IActivityMonitor.ActualFilter">ActualFilter</see> level.
 /// <see cref="CloseGroup"/> must be called in order to close the group, and/or the returned object must be disposed (both safely can be called:
 /// the group is closed on the first action, the second one is ignored).
 /// </summary>
 /// <param name="this">This <see cref="IActivityMonitor"/>.</param>
 /// <param name="tags">Tags (from <see cref="ActivityMonitor.Tags"/>) to associate to the log. It will be union-ed with current <see cref="IActivityMonitor.AutoTags">AutoTags</see>.</param>
 /// <param name="level">Log level. The <see cref="LogLevel.None"/> level is used to open a filtered group. See remarks.</param>
 /// <param name="getConclusionText">Optional function that will be called on group closing.</param>
 /// <param name="text">Text to log (the title of the group). Null text is valid and considered as <see cref="String.Empty"/> or assigned to the <see cref="Exception.Message"/> if it exists.</param>
 /// <param name="logTime">
 /// Time of the log entry.
 /// You can use <see cref="DateTimeStamp.UtcNow"/> or <see cref="ActivityMonitorExtension.NextLogTime">IActivityMonitor.NextLogTime()</see> extension method.
 /// </param>
 /// <param name="ex">Optional exception associated to the group.</param>
 /// <param name="fileName">The source code file name from which the group is opened.</param>
 /// <param name="lineNumber">The line number in the source from which the group is opened.</param>
 /// <returns>A disposable object that can be used to close the group.</returns>
 /// <remarks>
 /// <para>
 /// Opening a group does not change the current <see cref="IActivityMonitor.MinimalFilter">MinimalFilter</see>, except when
 /// opening a <see cref="LogLevel.Fatal"/> or <see cref="LogLevel.Error"/> group: in such case, the Filter is automatically
 /// sets to <see cref="LogFilter.Debug"/> to capture all potential information inside the error group.
 /// </para>
 /// <para>
 /// Changes to the monitor's current Filter or AutoTags that occur inside a group are automatically restored to their original values when the group is closed.
 /// This behavior guaranties that a local modification (deep inside unknown called code) does not impact caller code: groups are a way to easily isolate such
 /// configuration changes.
 /// </para>
 /// <para>
 /// Note that this automatic configuration restoration works even if the group is filtered (when the <paramref name="level"/> is None).
 /// </para>
 /// </remarks>
 static public IDisposable UnfilteredOpenGroup(this IActivityMonitor @this, CKTrait tags, LogLevel level, Func <string> getConclusionText, string text, DateTimeStamp logTime, Exception ex, [CallerFilePath] string fileName = null, [CallerLineNumber] int lineNumber = 0)
 {
     return(@this.UnfilteredOpenGroup(new ActivityMonitorGroupData(level, tags, text, logTime, ex, getConclusionText, fileName, lineNumber)));
 }
Пример #20
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 DependentToken(Guid monitorId, DateTimeStamp logTime, string topic)
 {
     _originatorId = monitorId;
     _creationDate = logTime;
     _topic        = topic;
 }
            /// <summary>
            /// Attempts to parse the start message of a dependent activity (tagged with <see cref="ActivityMonitor.Tags.StartDependentActivity"/>).
            /// </summary>
            /// <param name="startMessage">The start message to parse.</param>
            /// <param name="id">The originator monitor identifier.</param>
            /// <param name="time">The creation time of the dependent activity.</param>
            /// <returns>True on success.</returns>
            static public bool TryParseStartMessage(string startMessage, out Guid id, out DateTimeStamp time)
            {
                int idx = startMessage.IndexOf('{');

                if (idx <= 0)
                {
                    id   = Guid.Empty;
                    time = DateTimeStamp.MinValue;
                    return(false);
                }
                return(MatchOriginatorAndTime(new StringMatcher(startMessage, idx), out id, out time));
            }