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; }
/// <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; }
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()); }
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()); }
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)); }
/// <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<ActivityLogGroupConclusion>). /// </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<ActivityLogGroupConclusion> 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)); }
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); }
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(); } }
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); }
/// <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; }
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); } } }
/// <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)); }
/// <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)); }
/// <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; }
/// <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; }
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(); } }
/// <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))); }
/// <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)); }