/// <summary> /// Logs the end of the current scope. /// </summary> public void Dispose() { if (!isDisposed) { isDisposed = true; FL.LogScope(FieldLogScopeType.ThreadEnd, name); GC.SuppressFinalize(this); } }
/// <summary> /// Logs the end of the current scope. /// </summary> public void Dispose() { if (!isDisposed) { isDisposed = true; FL.Leave(name); GC.SuppressFinalize(this); } }
/// <inheritdoc/> public override void WriteLine(string message) { msgSb.Append(message); var final = msgSb.ToString(); msgSb.Length = 0; if (!FL.IsShutdown) { FL.Trace(sourceName + ": " + final); } }
private static void CreateForRectangle(Rectangle rect) { if (KeepTime <= TimeSpan.Zero || MaxTotalSize <= 0) { return; // Nothing to do } try { string fileName = GetFileNameWithoutExtension(); if (fileName != null) { // Source: http://stackoverflow.com/a/5049138/143684 // Source: http://stackoverflow.com/a/1163770/143684 using (Bitmap bitmap = new Bitmap(rect.Width, rect.Height)) { using (Graphics graphics = Graphics.FromImage(bitmap)) { graphics.CopyFromScreen(rect.X, rect.Y, 0, 0, bitmap.Size, CopyPixelOperation.SourceCopy); } // Save bitmap as PNG and JPEG bitmap.Save(fileName + ".png", ImageFormat.Png); SaveJpegImage(bitmap, fileName + ".jpg", 75); // Evaluate both file sizes and decide which to keep long pngSize = new FileInfo(fileName + ".png").Length; long jpgSize = new FileInfo(fileName + ".jpg").Length; if (pngSize > jpgSize * 2) { File.Delete(fileName + ".png"); } else { File.Delete(fileName + ".jpg"); } } Purge(); } } catch (Exception ex) { FL.Error(ex, "Creating screenshot"); } }
/// <summary> /// Creates a new log file reader and adds it to the priority's log file enumerator. /// </summary> /// <param name="prio">The priority of files to write.</param> /// <param name="fileName">The name of the log file.</param> /// <param name="fromFsw">Indicates whether the reader was created from a FileSystemWatcher event.</param> private void AddNewReader(FieldLogPriority prio, string fileName, bool fromFsw) { // Must be within a lock(readerLock)! FL.Trace("AddNewReader, prio=" + prio + ", fileName=" + Path.GetFileName(fileName) + ", fromFsw=" + fromFsw); // Reject the new file if it's already in the queue (delayed FSW event after active scan) if (readers.ContainsKey(prio) && readers[prio] != null && readers[prio].ContainsFile(fileName)) { // This file is already current or queued FL.Checkpoint("This file is already current or queued"); return; } var reader = new FieldLogFileReader(fileName, true); ManualResetEvent h; if (!prioReadSignals.TryGetValue(prio, out h)) { h = new ManualResetEvent(false); prioReadSignals[prio] = h; } reader.ReadWaitHandle = h; if (!readers.ContainsKey(prio) || readers[prio] == null) { // This is the first file of this priority readers[prio] = new FieldLogFileEnumerator(reader); readers[prio].Error += FieldLogFileEnumerator_Error; readTasks[(int)prio] = Task <bool> .Factory.StartNew(readers[prio].MoveNext); // Signal the blocking ReadLogItem method that there's a new reader now newFilePrioEvent.Set(); } else { // Chain the new reader after the last reader in the queue readers[prio].Append(reader, fromFsw); // TODO,DEBUG: What for? //newFilePrioEvent.Set(); } }
private void StartSubmitTool() { if (sendCheckBox.Checked) { string exeFile = Application.ExecutablePath; if (!string.IsNullOrEmpty(exeFile)) { exeFile = Path.GetDirectoryName(exeFile); exeFile = Path.Combine(exeFile, "LogSubmit.exe"); if (File.Exists(exeFile)) { // Found the log submit tool, now start it try { Process.Start(exeFile, "/errordlg /logpath \"" + FL.LogFileBasePath + "\""); } catch (Exception ex) { // Start failed, show an error message FL.Critical(ex, "Starting log submit tool"); MessageBox.Show( "The log submit tool could not be started." + " " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } return; } } // Log submit tool not found but logs should be sent, show an error message FL.Error("Could not start log submit tool, path or file not found"); MessageBox.Show( "The log submit tool could not be started. The path or file was not found. Please start the tool manually from the application installation directory.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// Advances the enumerator to the next log item of the currently read log file. If there /// are no more items in this file and there is a NextReader set, the first log item of the /// next file reader is selected. If there are no more items in this file and WaitMode is /// set, the method will block until another log item is appended to the current file or /// the wait operation is cancelled by a close event. /// </summary> /// <returns>true if the enumerator was successfully advanced to the next log item; /// false if the enumerator has passed the end of the collection.</returns> public bool MoveNext() { FieldLogFileReader nextReader = null; do { if (nextReader != null) { FL.Trace(reader.ItemCount + " items read from " + Path.GetFileName(reader.FileName)); reader = nextReader; FL.Trace("Switching to next reader " + Path.GetFileName(reader.FileName)); } try { item = reader.ReadLogItem(); } catch (Exception ex) { FL.Error(ex, "Reading item from log file"); OnError(ex); // Skip the rest of the current file and continue with the next one if // available. If this is the last file and WaitMode is set, this priority will // not be monitored anymore. item = null; } if (item == null && reader.IsClosing) { // Close event must have been set Dispose(); return(false); } nextReader = reader.NextReader; }while (item == null && nextReader != null); return(item != null); }
/// <summary> /// Appends a new FieldLogFileReader at the end of this enumerator. /// </summary> /// <param name="newReader">The new reader to append.</param> /// <param name="fromFsw">Indicates whether the reader was created from a FileSystemWatcher event.</param> public void Append(FieldLogFileReader newReader, bool fromFsw) { FieldLogFileReader currentLastReader = LastReader; currentLastReader.NextReader = newReader; // Unset wait mode for this reader, now that we know where to continue after this file if (fromFsw) { // The file is newly created. Take some time to actually start reading the previous // file before switching to this one. Once the first item has been read from the // file, more items will likely exist in the file, and the file is read until the // end. Then it will still sit there waiting for more items until the rest of this // delay has elapsed (which is not a big problem, if we get any items from that // file at all). #if NET20 new Thread(() => { Thread.Sleep(1000); currentLastReader.WaitMode = false; }).Start(); #else Task.Factory.StartNew(() => { Thread.Sleep(1000); currentLastReader.WaitMode = false; }); #endif } else { currentLastReader.WaitMode = false; } FL.Trace( "Appending next reader", "this=" + Path.GetFileName(currentLastReader.FileName) + "\nNext=" + Path.GetFileName(newReader.FileName) + "\nItems read from this=" + currentLastReader.ItemCount); }
private static void ThreadProc() { #if NET20 localOffset = (int)TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.UtcNow).TotalMinutes; #else localOffset = (int)TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow).TotalMinutes; #endif // Only check for offset changes every new minute (plus 1 second safety margin) to save // resources for clearing the TimeZoneInfo cache nextOffsetCheck = DateTime.UtcNow.Ticks / 600000000 * 600000000 + 610000000; do { try { Thread.Sleep(CheckInterval); } catch (ThreadInterruptedException) { // That's fine. } // Check for UTC time changes TimeSpan offset = DateTime.UtcNow - FL.UtcNow; if (Math.Abs(offset.TotalMilliseconds) >= FL.CheckTimeThreshold) { FL.RebaseTime(); string msg = "System time changed by " + offset.TotalMilliseconds.ToString("0.0", CultureInfo.InvariantCulture) + " ms"; var item = new FieldLogTextItem(FieldLogPriority.Notice, msg, "Changes less than " + FL.CheckTimeThreshold + " ms are not reported."); // Discard this log item if there was no other item logged within the last // 10 seconds because it wouldn't be interesting anyway. If no other log items // may have discontinuous timestamps, the time recalibration can occur silently. FL.LogInternal(item, TimeSpan.FromSeconds(10)); Debug.WriteLine(msg); prevLoggedOffset = 0; } else if (FL.LogTimeThreshold >= 0 && Math.Abs(offset.TotalMilliseconds - prevLoggedOffset) > FL.LogTimeThreshold) { FL.TraceData("System time offset", offset.TotalMilliseconds); prevLoggedOffset = offset.TotalMilliseconds; } // Check for local time zone changes if (DateTime.UtcNow.Ticks >= nextOffsetCheck) { // Clear the cache to get the real current setting #if NET20 int newLocalOffset = (int)TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.UtcNow).TotalMinutes; #else TimeZoneInfo.ClearCachedData(); int newLocalOffset = (int)TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow).TotalMinutes; #endif if (newLocalOffset != localOffset) { int hours = localOffset / 60; int mins = Math.Abs(localOffset) % 60; int newHours = newLocalOffset / 60; int newMins = Math.Abs(newLocalOffset) % 60; localOffset = newLocalOffset; string msg = "Local time UTC offset changed from " + hours.ToString("+00;-00;+00") + ":" + mins.ToString("00") + " to " + newHours.ToString("+00;-00;+00") + ":" + newMins.ToString("00"); string details = "\u0001UtcOffset=" + newLocalOffset; FL.Notice(msg, details); Debug.WriteLine(msg); } nextOffsetCheck = DateTime.UtcNow.Ticks / 600000000 * 600000000 + 610000000; } }while (!cancelPending); }
/// <summary> /// Shows the application error dialog. This is the only method that is called to show or /// update an error dialog. If a dialog is already open, the error is added to it. /// </summary> /// <param name="canContinue">Indicates whether the application can continue.</param> /// <param name="errorMsg">The error message to display.</param> /// <param name="ex">The <see cref="Exception"/> instance to display as details object.</param> /// <param name="terminateTimerEnabled">Indicates whether the termination safety timer has been started.</param> public static void ShowError(bool canContinue, string errorMsg, object ex, bool terminateTimerEnabled) { lock (syncLock) { try { if (currentInstance == null) { currentInstance = new AppErrorDialog(); currentInstance.SetCanContinue(canContinue); currentInstance.errorLabel.Text = errorMsg; currentInstance.grid.SelectedObject = ex; currentInstance.detailsLabel.Enabled = ex != null; if (terminateTimerEnabled) { currentInstance.EnableTerminateTimer(); } // Source: http://stackoverflow.com/a/3992635/143684 uiThread = new Thread(UiThreadStart); uiThread.Name = "FieldLog.AppErrorDialogUIThread"; uiThread.SetApartmentState(ApartmentState.STA); uiThread.Start(); } else { // Add next error to existing dialog // Wait until the window handle is created int count = 0; while (!currentInstance.IsHandleCreated) { if (count++ > 500) { throw new TimeoutException("Application error dialog was not created in reasonable time."); } Thread.Sleep(10); } currentInstance.Invoke(new AddErrorDelegate(currentInstance.AddError), canContinue, errorMsg, ex, terminateTimerEnabled); } } catch (Exception ex2) { FL.Critical(ex2, "FieldLog.Showing AppErrorDialog", false); FL.Flush(); MessageBox.Show( "Error showing the application error dialog. Details should be logged.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // Make sure we won't continue in this thread if it's not possible while (!canContinue) { Thread.Sleep(1000000); } // Slow down or halt the application as long as there are many pending errors. // The error dialog runs in its own thread so it will still respond to user input. :-) // (Unless, of course, should an error occur in the error dialog…) if (currentInstance != null && currentInstance.GetNextErrorsCount() >= 20) { Thread.Sleep(1000); } while (currentInstance != null && currentInstance.GetNextErrorsCount() >= 40) { Thread.Sleep(1000); } }
private AppErrorDialog() { if (!appErrorInitialized) { Application.EnableVisualStyles(); appErrorInitialized = true; } string title = FL.AppErrorDialogTitle; string appName = FL.AppName; if (!string.IsNullOrEmpty(appName)) { title = appName + " – " + title; } this.BackColor = SystemColors.Window; this.ControlBox = false; this.MinimizeBox = false; this.MaximizeBox = false; this.Font = SystemFonts.MessageBoxFont; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.ShowInTaskbar = false; this.Size = new Size(550, 300); this.StartPosition = FormStartPosition.CenterScreen; this.Text = title; this.TopMost = true; tablePanel = new TableLayoutPanel(); tablePanel.Dock = DockStyle.Fill; tablePanel.RowCount = 6; tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 0)); tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.ColumnCount = 1; tablePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); this.Controls.Add(tablePanel); introLabel = new Label(); introLabel.BackColor = Color.FromArgb(221, 74, 59); introLabel.ForeColor = Color.White; introLabel.Dock = DockStyle.Fill; introLabel.AutoSize = true; introLabel.Font = new Font( SystemFonts.MessageBoxFont.FontFamily, SystemFonts.MessageBoxFont.SizeInPoints * 1.3f, SystemFonts.MessageBoxFont.Style); introLabel.MaximumSize = new Size(this.ClientSize.Width, 0); introLabel.Padding = new Padding(6, 4, 7, 6); introLabel.Margin = new Padding(); introLabel.UseCompatibleTextRendering = false; introLabel.UseMnemonic = false; tablePanel.Controls.Add(introLabel); tablePanel.SetRow(introLabel, 0); tablePanel.SetColumn(introLabel, 0); errorPanel = new Panel(); errorPanel.AutoScroll = true; errorPanel.Dock = DockStyle.Fill; errorPanel.Margin = new Padding(7, 8, 10, 6); errorPanel.Padding = new Padding(); tablePanel.Controls.Add(errorPanel); tablePanel.SetRow(errorPanel, 1); tablePanel.SetColumn(errorPanel, 0); errorLabel = new Label(); errorLabel.AutoSize = true; errorLabel.MaximumSize = new Size(this.ClientSize.Width - 20, 0); errorLabel.Padding = new Padding(); errorLabel.Margin = new Padding(); errorLabel.UseCompatibleTextRendering = false; errorLabel.UseMnemonic = false; errorPanel.Controls.Add(errorLabel); logLabel = new LinkLabel(); logLabel.AutoSize = true; logLabel.MaximumSize = new Size(this.ClientSize.Width - 20, 0); logLabel.Margin = new Padding(8, 6, 10, 0); logLabel.Padding = new Padding(); if (FL.LogFileBasePath != null) { logLabel.Text = string.Format(FL.AppErrorDialogLogPath, FL.LogFileBasePath.Replace("\\", "\\\u200B") + "*.fl"); string dir = Path.GetDirectoryName(FL.LogFileBasePath).Replace("\\", "\\\u200B"); logLabel.LinkArea = new LinkArea(FL.AppErrorDialogLogPath.IndexOf("{0}", StringComparison.Ordinal), dir.Length); logLabel.LinkClicked += (s, e) => { Process.Start(Path.GetDirectoryName(FL.LogFileBasePath)); }; } else { logLabel.Text = FL.AppErrorDialogNoLog; logLabel.LinkArea = new LinkArea(0, 0); } logLabel.UseCompatibleTextRendering = false; logLabel.UseMnemonic = false; tablePanel.Controls.Add(logLabel); tablePanel.SetRow(logLabel, 2); tablePanel.SetColumn(logLabel, 0); detailsLabel = new LinkLabel(); detailsLabel.AutoSize = true; detailsLabel.Margin = new Padding(7, 6, 10, 10); detailsLabel.Padding = new Padding(); detailsLabel.TabIndex = 11; detailsLabel.Text = FL.AppErrorDialogDetails; detailsLabel.UseCompatibleTextRendering = false; detailsLabel.Visible = CanShowDetails; tablePanel.Controls.Add(detailsLabel); tablePanel.SetRow(detailsLabel, 3); tablePanel.SetColumn(detailsLabel, 0); var attr = new TypeConverterAttribute(typeof(ExpandableObjectConverter)); TypeDescriptor.AddAttributes(typeof(Exception), attr); grid = new PropertyGrid(); grid.Dock = DockStyle.Fill; grid.Margin = new Padding(10, 10, 10, 10); grid.ToolbarVisible = false; grid.HelpVisible = false; grid.PropertySort = PropertySort.Alphabetical; grid.UseCompatibleTextRendering = false; grid.Visible = false; tablePanel.Controls.Add(grid); tablePanel.SetRow(grid, 4); tablePanel.SetColumn(grid, 0); bool isGridColumnResized = false; grid.Resize += (s, e) => { if (!isGridColumnResized) { isGridColumnResized = true; // Source: http://stackoverflow.com/a/14475276/143684 FieldInfo fi = grid.GetType().GetField("gridView", BindingFlags.Instance | BindingFlags.NonPublic); if (fi != null) { Control view = fi.GetValue(grid) as Control; if (view != null) { MethodInfo mi = view.GetType().GetMethod("MoveSplitterTo", BindingFlags.Instance | BindingFlags.NonPublic); if (mi != null) { mi.Invoke(view, new object[] { 170 }); } mi = view.GetType().GetMethod("set_GrayTextColor", BindingFlags.Instance | BindingFlags.NonPublic); if (mi != null) { mi.Invoke(view, new object[] { Color.Black }); } } } } }; detailsLabel.LinkClicked += (s, e) => { detailsLabel.Hide(); this.Height += 300; this.Top -= Math.Min(this.Top - 4, 150); tablePanel.RowStyles[4].Height = 350; grid.Visible = true; }; buttonsPanel = new TableLayoutPanel(); buttonsPanel.AutoSize = true; buttonsPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink; buttonsPanel.BackColor = SystemColors.Control; buttonsPanel.Dock = DockStyle.Fill; buttonsPanel.Margin = new Padding(); buttonsPanel.Padding = new Padding(10, 10, 10, 10); buttonsPanel.ColumnCount = 4; buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); tablePanel.Controls.Add(buttonsPanel); tablePanel.SetRow(buttonsPanel, 5); tablePanel.SetColumn(buttonsPanel, 0); sendCheckBox = new CheckBox(); sendCheckBox.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom; sendCheckBox.AutoSize = true; sendCheckBox.Enabled = FL.CanSubmitLog; if (sendCheckBox.Enabled) { sendCheckBox.Checked = true; } sendCheckBox.FlatStyle = FlatStyle.System; sendCheckBox.Margin = new Padding(); sendCheckBox.Padding = new Padding(); sendCheckBox.Text = FL.AppErrorDialogSendLogs; sendCheckBox.UseCompatibleTextRendering = false; buttonsPanel.Controls.Add(sendCheckBox); buttonsPanel.SetRow(sendCheckBox, 0); buttonsPanel.SetColumn(sendCheckBox, 0); nextButton = new Button(); nextButton.AutoSize = true; nextButton.AutoSizeMode = AutoSizeMode.GrowAndShrink; nextButton.FlatStyle = FlatStyle.System; nextButton.Margin = new Padding(6, 0, 0, 0); nextButton.Padding = new Padding(2, 1, 2, 1); nextButton.Text = FL.AppErrorDialogNext; nextButton.UseCompatibleTextRendering = false; nextButton.UseVisualStyleBackColor = true; nextButton.Visible = false; nextButton.Click += (s, e) => { ShowNextError(); }; buttonsPanel.Controls.Add(nextButton); buttonsPanel.SetRow(nextButton, 0); buttonsPanel.SetColumn(nextButton, 1); terminateButton = new Button(); terminateButton.AutoSize = true; terminateButton.AutoSizeMode = AutoSizeMode.GrowAndShrink; terminateButton.FlatStyle = FlatStyle.System; terminateButton.Margin = new Padding(6, 0, 0, 0); terminateButton.Padding = new Padding(2, 1, 2, 1); terminateButton.Text = FL.AppErrorDialogTerminate; terminateButton.UseCompatibleTextRendering = false; terminateButton.UseVisualStyleBackColor = true; terminateButton.Click += (s, e) => { StartSubmitTool(); Close(); FL.Shutdown(); Environment.Exit(1); }; buttonsPanel.Controls.Add(terminateButton); buttonsPanel.SetRow(terminateButton, 0); buttonsPanel.SetColumn(terminateButton, 2); continueButton = new Button(); continueButton.AutoSize = true; continueButton.AutoSizeMode = AutoSizeMode.GrowAndShrink; continueButton.FlatStyle = FlatStyle.System; continueButton.Margin = new Padding(6, 0, 0, 0); continueButton.Padding = new Padding(2, 1, 2, 1); continueButton.Text = FL.AppErrorDialogContinue; continueButton.UseCompatibleTextRendering = false; continueButton.UseVisualStyleBackColor = true; continueButton.Click += (s, e) => { StartSubmitTool(); Close(); }; buttonsPanel.Controls.Add(continueButton); buttonsPanel.SetRow(continueButton, 0); buttonsPanel.SetColumn(continueButton, 3); }
private void WriteToFieldLog(string source, TraceEventType eventType, int id, string msg) { string shortMsg = null; // Name comparisons roughly in a descending order of frequency, to optimise performance if (source == PresentationTraceSources.DataBindingSource.Name) { HandleDataBindingMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.RoutedEventSource.Name) { if (id == 3) { if (eventType == TraceEventType.Start) { eventType = TraceEventType.Verbose; // Don't indent for this } else if (eventType == TraceEventType.Stop) { return; // Don't log this } } //if (id == 4) //{ // return; // Don't log this //} HandleRoutedEventMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.ResourceDictionarySource.Name) { HandleResourceDictionaryMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.MarkupSource.Name) { HandleMarkupMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.AnimationSource.Name) { HandleAnimationMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.DependencyPropertySource.Name) { HandleDependencyPropertyMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.FreezableSource.Name) { if (id == 1) { return; // Don't log this, it appears everywhere and comes from bugs in WPF } HandleFreezableMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.HwndHostSource.Name) { HandleHwndHostMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.NameScopeSource.Name) { HandleNameScopeMessage(id, ref msg, ref shortMsg); } else if (source == PresentationTraceSources.ShellSource.Name) { HandleShellMessage(id, ref msg, ref shortMsg); } // DocumentsSource does not have any information and is not augmented here // Fallback short message if unknown event ID if (shortMsg == null) { shortMsg = "ID " + id; } // Select appropriate FieldLog priority or otherwise highlight event type FieldLogPriority prio = FieldLogPriority.Trace; switch (eventType) { case TraceEventType.Critical: case TraceEventType.Error: prio = FieldLogPriority.Error; break; case TraceEventType.Warning: prio = FieldLogPriority.Warning; break; case TraceEventType.Information: prio = FieldLogPriority.Info; break; case TraceEventType.Start: //shortMsg += " [Start]"; break; case TraceEventType.Stop: shortMsg += " [Stop]"; break; case TraceEventType.Suspend: shortMsg += " [Suspend]"; break; case TraceEventType.Resume: shortMsg += " [Resume]"; break; case TraceEventType.Transfer: shortMsg += " [Transfer]"; break; } // Write message to the log if it's still there if (!FL.IsShutdown) { if (eventType == TraceEventType.Stop) { indentLevel--; } string indentPrefix = null; if (indentLevel < 0) { indentPrefix = "«"; indentLevel = 0; } else if (indentLevel > 0) { // Use a cached pre-generated indent prefix for better performance while (indentStrings.Count <= indentLevel) { int newLevel = indentStrings.Count; string prefix = ""; for (int i = 1; i <= newLevel; i++) { prefix += "- "; } indentStrings.Add(prefix); } indentPrefix = indentStrings[indentLevel]; } FL.Text( prio, indentPrefix + shortName + ": " + shortMsg, (!string.IsNullOrEmpty(msg) ? msg + "\n\n" : "") + "Event ID: " + id + "\nEvent type: " + eventType + "\nSource: " + sourceName); if (eventType == TraceEventType.Start) { indentLevel++; } } }
/// <summary> /// Handles a custom time measurement timer for saving the time data. /// </summary> /// <param name="state">Unused.</param> private void OnCustomTimer(object state) { if (key == FL.EnsureJitTimerKey) { return; } long ticks, ticksPc, ticksTt, localCounter; lock (syncLock) { // Do nothing if the stopwatch was just started again or current data has already // been written if (stopwatch.IsRunning || !writePending) { return; } // Clear the flag while we're in the lock region writePending = false; // Fetch the data in the lock region localCounter = counter; // Subtract 4 ticks per measurement, determined by tests // TODO: 0-tick intervals have been observed many times. Is the correction needed at all? long correction = localCounter * 0; ticks = stopwatch.Elapsed.Ticks - correction; if (ticks < 0) { ticks = 0; } if (localCounter > 0) { ticksPc = ticks / localCounter; } else { ticksPc = 0; } long counterTt = localCounter - prevCounter; if (counterTt > 0) { ticksTt = (ticks - prevTicks) / counterTt; } else { ticksTt = 0; } prevTicks = ticks; prevCounter = localCounter; } // Total time // (Add 5 ticks for simple microseconds rounding) long roundTicks = ticks + 5; int seconds = (int)(roundTicks / 10000000); int ms = (int)((roundTicks % 10000000) / 10000); int us = (int)((roundTicks % 10000) / 10); // Per call, this time (since last item) long roundTicksTt = ticksTt + 5; int secondsTt = (int)(roundTicksTt / 10000000); int msTt = (int)((roundTicksTt % 10000000) / 10000); int usTt = (int)((roundTicksTt % 10000) / 10); // Per call time long roundTicksPc = ticksPc + 5; int secondsPc = (int)(roundTicksPc / 10000000); int msPc = (int)((roundTicksPc % 10000000) / 10000); int usPc = (int)((roundTicksPc % 10000) / 10); string text = "Custom timer " + key + " at " + localCounter; string details = localCounter + " calls\n" + secondsPc.ToString() + "." + msPc.ToString("000") + "\u2009" + usPc.ToString("000") + " seconds per call (" + ticksPc + " ticks)\n" + secondsTt.ToString() + "." + msTt.ToString("000") + "\u2009" + usTt.ToString("000") + " seconds per call since last item (" + ticksTt + " ticks)\n" + seconds.ToString() + "." + ms.ToString("000") + "\u2009" + us.ToString("000") + " seconds total (" + ticks + " ticks)"; FL.Trace(text, details); }
/// <summary> /// Logs an exception raised in the task. Call this like the ContinueWith method with the /// <see cref="TaskContinuationOptions.OnlyOnFaulted"/> option. /// </summary> /// <typeparam name="TResult">The type of the result produced by this Task.</typeparam> /// <param name="task"></param> /// <param name="prio">The priority of the log item.</param> /// <param name="taskName">The name of the task, used for the exception log item context.</param> /// <returns>A new continuation <see cref="Task"/>.</returns> public static Task LogFaulted <TResult>(this Task <TResult> task, FieldLogPriority prio, string taskName) { return(task.ContinueWith(t => FL.Exception(prio, t.Exception, taskName + " Task"), TaskContinuationOptions.OnlyOnFaulted)); }
/// <summary> /// Initialises a new instance of the FieldLogThreadScope class and logs the thread scope /// beginning. /// </summary> /// <param name="name">The thread scope name.</param> public FieldLogThreadScope(string name) { this.name = name; FL.LogScope(FieldLogScopeType.ThreadStart, name); }
/// <summary> /// Initialises a new instance of the CustomTimerScope class and calls the Start method of /// the CustomTimerInfo instance. /// </summary> /// <param name="key">The custom timer key for a dictionary lookup.</param> /// <param name="incrementCounter">Increment the counter value.</param> /// <param name="writeImmediately">true to write the timer value immediately when stopping, false for the normal delay.</param> public CustomTimerScope(string key, bool incrementCounter, bool writeImmediately) { this.key = key; this.writeImmediately = writeImmediately; cti = FL.StartTimer(key, incrementCounter); }
/// <summary> /// Initialises a new instance of the FieldLogScope class and logs the scope beginning. /// </summary> /// <param name="name">The scope name.</param> public FieldLogScope(string name) { this.name = name; FL.Enter(name); }
private AppErrorDialog() { if (!appErrorInitialized) { Application.EnableVisualStyles(); appErrorInitialized = true; } string title = FL.AppErrorDialogTitle; string appName = FL.AppName; if (!string.IsNullOrEmpty(appName)) { title = appName + " – " + title; } AutoScaleDimensions = new SizeF(96, 96); AutoScaleMode = AutoScaleMode.Dpi; scaleFactor = CurrentAutoScaleDimensions.Width / 96; BackColor = SystemColors.Window; ControlBox = false; MinimizeBox = false; MaximizeBox = false; Font = SystemFonts.MessageBoxFont; FormBorderStyle = FormBorderStyle.FixedDialog; ShowInTaskbar = false; Size = new Size(Scale(550), Scale(350)); StartPosition = FormStartPosition.CenterScreen; Text = title; TopMost = true; tablePanel = new TableLayoutPanel(); tablePanel.Dock = DockStyle.Fill; tablePanel.RowCount = 6; tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 0)); tablePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); tablePanel.ColumnCount = 1; tablePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); Controls.Add(tablePanel); introLabel = new Label(); introLabel.BackColor = Color.FromArgb(221, 74, 59); introLabel.ForeColor = Color.White; introLabel.Dock = DockStyle.Fill; introLabel.AutoSize = true; introLabel.Font = new Font( SystemFonts.MessageBoxFont.FontFamily, SystemFonts.MessageBoxFont.SizeInPoints * 1.3f, SystemFonts.MessageBoxFont.Style); introLabel.MaximumSize = new Size(ClientSize.Width, 0); introLabel.Padding = Scale(new Padding(6, 4, 7, 6)); introLabel.Margin = new Padding(); introLabel.UseCompatibleTextRendering = false; introLabel.UseMnemonic = false; tablePanel.Controls.Add(introLabel); tablePanel.SetRow(introLabel, 0); tablePanel.SetColumn(introLabel, 0); errorPanel = new Panel(); errorPanel.AutoScroll = true; errorPanel.Dock = DockStyle.Fill; errorPanel.Margin = Scale(new Padding(7, 8, 10, 6)); errorPanel.Padding = new Padding(); tablePanel.Controls.Add(errorPanel); tablePanel.SetRow(errorPanel, 1); tablePanel.SetColumn(errorPanel, 0); errorLabel = new Label(); errorLabel.AutoSize = true; // Always keep the vertical scrollbar width free because the label wouldn't get smaller // when the vertical scrollbar appears and then the horizontal scrollbar kicks in as well. errorLabel.MaximumSize = new Size(errorPanel.ClientSize.Width - SystemInformation.VerticalScrollBarWidth - 2, 0); errorLabel.Padding = new Padding(); errorLabel.Margin = new Padding(); errorLabel.UseCompatibleTextRendering = false; errorLabel.UseMnemonic = false; errorPanel.Controls.Add(errorLabel); logLabel = new LinkLabel(); logLabel.AutoSize = true; logLabel.MaximumSize = new Size(ClientSize.Width - Scale(20), 0); logLabel.Margin = Scale(new Padding(8, 6, 10, 0)); logLabel.Padding = new Padding(); if (FL.LogFileBasePath != null) { logLabel.Text = string.Format(FL.AppErrorDialogLogPath, FL.LogFileBasePath.Replace("\\", "\\\u200B") + "*.fl"); string dir = Path.GetDirectoryName(FL.LogFileBasePath).Replace("\\", "\\\u200B"); logLabel.LinkArea = new LinkArea(FL.AppErrorDialogLogPath.IndexOf("{0}", StringComparison.Ordinal), dir.Length); logLabel.LinkClicked += (s, e) => { Process.Start(Path.GetDirectoryName(FL.LogFileBasePath)); }; } else { logLabel.Text = FL.AppErrorDialogNoLog; logLabel.LinkArea = new LinkArea(0, 0); } logLabel.UseCompatibleTextRendering = false; logLabel.UseMnemonic = false; tablePanel.Controls.Add(logLabel); tablePanel.SetRow(logLabel, 2); tablePanel.SetColumn(logLabel, 0); detailsLabel = new LinkLabel(); detailsLabel.AutoSize = true; detailsLabel.Margin = Scale(new Padding(7, 6, 10, 10)); detailsLabel.Padding = new Padding(); detailsLabel.TabIndex = 11; detailsLabel.Text = FL.AppErrorDialogDetails; detailsLabel.UseCompatibleTextRendering = false; detailsLabel.Visible = CanShowDetails; tablePanel.Controls.Add(detailsLabel); tablePanel.SetRow(detailsLabel, 3); tablePanel.SetColumn(detailsLabel, 0); var attr = new TypeConverterAttribute(typeof(ExpandableObjectConverter)); TypeDescriptor.AddAttributes(typeof(Exception), attr); grid = new PropertyGrid(); grid.Dock = DockStyle.Fill; grid.Margin = Scale(new Padding(10, 10, 10, 10)); grid.ToolbarVisible = false; grid.HelpVisible = false; grid.PropertySort = PropertySort.Alphabetical; grid.UseCompatibleTextRendering = false; grid.Visible = false; tablePanel.Controls.Add(grid); tablePanel.SetRow(grid, 4); tablePanel.SetColumn(grid, 0); bool isGridColumnResized = false; grid.Resize += (s, e) => { if (!isGridColumnResized) { isGridColumnResized = true; // Source: http://stackoverflow.com/a/14475276/143684 FieldInfo fi = grid.GetType().GetField("gridView", BindingFlags.Instance | BindingFlags.NonPublic); if (fi != null) { Control view = fi.GetValue(grid) as Control; if (view != null) { MethodInfo mi = view.GetType().GetMethod("MoveSplitterTo", BindingFlags.Instance | BindingFlags.NonPublic); if (mi != null) { mi.Invoke(view, new object[] { Scale(170) }); } mi = view.GetType().GetMethod("set_GrayTextColor", BindingFlags.Instance | BindingFlags.NonPublic); if (mi != null) { mi.Invoke(view, new object[] { Color.Black }); } } } } }; detailsLabel.LinkClicked += (s, e) => { detailsLabel.Hide(); int maxHeight = Screen.FromControl(this).Bounds.Height; int missingHeight = 0; int desiredHeight = Height + Scale(300); Height = desiredHeight; if (Height < desiredHeight) { // A window is automatically constrained to the size of its screen, so we can // compare the current size with what we had requested to know how much is // missing for the layout. missingHeight = desiredHeight - Height; } Top -= Scale(Math.Min(Top - 4, 150)); if (Top < 0) { Top = 0; } tablePanel.RowStyles[4].Height = Scale(350) - missingHeight; grid.Visible = true; }; buttonsPanel = new TableLayoutPanel(); buttonsPanel.AutoSize = true; buttonsPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink; buttonsPanel.BackColor = SystemColors.Control; buttonsPanel.Dock = DockStyle.Fill; buttonsPanel.Margin = new Padding(); buttonsPanel.Padding = Scale(new Padding(10, 10, 10, 10)); buttonsPanel.ColumnCount = 4; buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); buttonsPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); tablePanel.Controls.Add(buttonsPanel); tablePanel.SetRow(buttonsPanel, 5); tablePanel.SetColumn(buttonsPanel, 0); sendCheckBox = new CheckBox(); sendCheckBox.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom; sendCheckBox.AutoSize = true; sendCheckBox.Enabled = FL.CanSubmitLog; if (sendCheckBox.Enabled) { sendCheckBox.Checked = true; } sendCheckBox.FlatStyle = FlatStyle.System; sendCheckBox.Margin = new Padding(); sendCheckBox.Padding = new Padding(); sendCheckBox.Text = FL.AppErrorDialogSendLogs; sendCheckBox.UseCompatibleTextRendering = false; buttonsPanel.Controls.Add(sendCheckBox); buttonsPanel.SetRow(sendCheckBox, 0); buttonsPanel.SetColumn(sendCheckBox, 0); nextButton = new Button(); nextButton.AutoSize = true; nextButton.AutoSizeMode = AutoSizeMode.GrowAndShrink; nextButton.FlatStyle = FlatStyle.System; nextButton.Margin = Scale(new Padding(6, 0, 0, 0)); nextButton.Padding = Scale(new Padding(2, 1, 2, 1)); nextButton.Text = FL.AppErrorDialogNext; nextButton.UseCompatibleTextRendering = false; nextButton.UseVisualStyleBackColor = true; nextButton.Visible = false; nextButton.Click += (s, e) => { ShowNextError(); }; buttonsPanel.Controls.Add(nextButton); buttonsPanel.SetRow(nextButton, 0); buttonsPanel.SetColumn(nextButton, 1); terminateButton = new Button(); terminateButton.AutoSize = true; terminateButton.AutoSizeMode = AutoSizeMode.GrowAndShrink; terminateButton.FlatStyle = FlatStyle.System; terminateButton.Margin = Scale(new Padding(6, 0, 0, 0)); terminateButton.Padding = Scale(new Padding(2, 1, 2, 1)); terminateButton.Text = FL.AppErrorDialogTerminate; terminateButton.UseCompatibleTextRendering = false; terminateButton.UseVisualStyleBackColor = true; terminateButton.Click += (s, e) => { StartSubmitTool(); Close(); FL.Shutdown(); Environment.Exit(1); }; buttonsPanel.Controls.Add(terminateButton); buttonsPanel.SetRow(terminateButton, 0); buttonsPanel.SetColumn(terminateButton, 2); continueButton = new Button(); continueButton.AutoSize = true; continueButton.AutoSizeMode = AutoSizeMode.GrowAndShrink; continueButton.FlatStyle = FlatStyle.System; continueButton.Margin = Scale(new Padding(6, 0, 0, 0)); continueButton.Padding = Scale(new Padding(2, 1, 2, 1)); continueButton.Text = FL.AppErrorDialogContinue; continueButton.UseCompatibleTextRendering = false; continueButton.UseVisualStyleBackColor = true; continueButton.Click += (s, e) => { StartSubmitTool(); Close(); }; buttonsPanel.Controls.Add(continueButton); buttonsPanel.SetRow(continueButton, 0); buttonsPanel.SetColumn(continueButton, 3); }
/// <summary> /// Purges screenshot files. /// </summary> public static void Purge() { try { string basePath = FL.LogFileBasePath; if (basePath == null) { return; // Nothing to do } string logDir = Path.GetDirectoryName(basePath); string logFile = Path.GetFileName(basePath); DateTime purgeTime = FL.UtcNow.Subtract(KeepTime); foreach (string fileName in Directory.GetFiles(logDir, logFile + "-scr-*.*")) { FileInfo fi = new FileInfo(fileName); if (fi.LastWriteTimeUtc < purgeTime) { // File is old enough to be deleted try { File.Delete(fileName); } catch { // Retry next time (might be locked by a log viewer reading the file) } } } // Keep maximum data size string[] fileNames = Directory.GetFiles(logDir, logFile + "-scr-*.*"); DateTime[] fileTimes = new DateTime[fileNames.Length]; long[] fileSizes = new long[fileNames.Length]; long totalUsedSize = 0; for (int i = 0; i < fileNames.Length; i++) { FileInfo fi = new FileInfo(fileNames[i]); fileTimes[i] = fi.LastWriteTimeUtc; fileSizes[i] = fi.Length; totalUsedSize += fileSizes[i]; } while (totalUsedSize > MaxTotalSize) { // Find oldest file int oldestIndex = -1; DateTime oldestTime = DateTime.MaxValue; for (int i = 0; i < fileTimes.Length; i++) { if (fileTimes[i] < oldestTime) { oldestTime = fileTimes[i]; oldestIndex = i; } } if (oldestIndex == -1) { break; // Nothing more to delete } // Delete the file and reduce the total size try { File.Delete(fileNames[oldestIndex]); totalUsedSize -= fileSizes[oldestIndex]; } catch { // Try the next file } fileTimes[oldestIndex] = DateTime.MaxValue; // Don't consider this file again } } catch (Exception ex) { FL.Error(ex, "Purging screenshots"); } }
/// <summary> /// Initialises a new instance of the CustomTimerScope class and calls the Start method of /// the CustomTimerInfo instance. /// </summary> /// <param name="key">The custom timer key for a dictionary lookup.</param> public CustomTimerScope(string key) { this.key = key; cti = FL.StartTimer(key); }
/// <summary> /// Logs an exception raised in the task. Call this like the ContinueWith method with the /// <see cref="TaskContinuationOptions.OnlyOnFaulted"/> option. /// </summary> /// <typeparam name="TResult">The type of the result produced by this Task.</typeparam> /// <param name="task"></param> /// <param name="taskName">The name of the task, used for the exception log item context.</param> /// <returns>A new continuation <see cref="Task"/>.</returns> public static Task LogFaulted <TResult>(this Task <TResult> task, string taskName) { return(task.ContinueWith(t => FL.Error(t.Exception, taskName + " Task"), TaskContinuationOptions.OnlyOnFaulted)); }