EtwTrace.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Diagnostics; 7 using System.Diagnostics.Tracing; 8 using System.Globalization; 9 using System.IO; 10 using System.Threading; 11 using System.Threading.Tasks; 12 using Microsoft.Diagnostics.Tracing.Session; 13 14 namespace Microsoft.PowerToys.Telemetry 15 { 16 /// <summary> 17 /// This class is based loosely on the C++ ETWTrace class in Win32client/Framework project. 18 /// It is intended to record telemetry events generated by the PowerToys processes so that end users 19 /// can view them if they want. 20 /// </summary> 21 public class ETWTrace : IDisposable 22 { 23 internal const EventKeywords TelemetryKeyword = (EventKeywords)0x0000200000000000; 24 internal const EventKeywords MeasuresKeyword = (EventKeywords)0x0000400000000000; 25 internal const EventKeywords CriticalDataKeyword = (EventKeywords)0x0000800000000000; 26 27 private readonly bool telemetryEnabled = DataDiagnosticsSettings.GetEnabledValue(); // This is the global telemetry setting on whether to log events 28 private readonly bool telemetryRecordingEnabled = DataDiagnosticsSettings.GetViewEnabledValue(); // This is the setting for recording telemetry events to disk for viewing 29 private string etwFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\PowerToys\", "etw"); 30 private bool disposedValue; 31 private string sessionName; 32 private string etwFilePath; 33 private bool started; 34 #nullable enable 35 private TraceEventSession? traceSession; 36 37 internal sealed class Lister : EventListener 38 { 39 public Lister() 40 : base() 41 { 42 } 43 } 44 45 private Lister? listener; 46 #nullable disable 47 48 /// <summary> 49 /// Initializes a new instance of the <see cref="ETWTrace"/> class. 50 /// </summary> 51 public ETWTrace() 52 { 53 Init(); 54 } 55 56 public ETWTrace(string etwPath) 57 { 58 this.etwFolderPath = etwPath; 59 60 Init(); 61 } 62 63 private void Init() 64 { 65 if (File.Exists(etwFolderPath)) 66 { 67 File.Delete(etwFolderPath); 68 } 69 70 if (!Directory.Exists(etwFolderPath)) 71 { 72 Directory.CreateDirectory(etwFolderPath); 73 } 74 75 if (this.telemetryEnabled && this.telemetryRecordingEnabled) 76 { 77 this.Start(); 78 } 79 80 listener = new Lister(); 81 listener.EnableEvents(PowerToysTelemetry.Log, EventLevel.LogAlways); 82 } 83 84 /// <inheritdoc/> 85 public void Dispose() 86 { 87 // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 88 this.Dispose(disposing: true); 89 GC.SuppressFinalize(this); 90 } 91 92 /// <summary> 93 /// Starts the trace session. 94 /// </summary> 95 public void Start() 96 { 97 lock (this) 98 { 99 if (this.started) 100 { 101 return; 102 } 103 104 new Task(() => 105 { 106 while (true) 107 { 108 Thread.Sleep(30 * 1000); 109 110 this.traceSession.Flush(); 111 } 112 }).Start(); 113 114 string executable = Process.GetCurrentProcess().ProcessName; 115 string dateTimeNow = DateTime.Now.ToString("MM-d-yyyy__H_mm_ss", CultureInfo.InvariantCulture); 116 this.sessionName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", executable, Environment.ProcessId, dateTimeNow); 117 this.etwFilePath = Path.Combine(etwFolderPath, $"{this.sessionName}.etl"); 118 119 this.traceSession = new TraceEventSession( 120 this.sessionName, this.etwFilePath, (TraceEventSessionOptions)(TraceEventSessionOptions.Create | TraceEventSessionOptions.PrivateLogger | TraceEventSessionOptions.PrivateInProcLogger)); 121 TraceEventProviderOptions args = new TraceEventProviderOptions(); 122 123 this.traceSession.EnableProvider( 124 PowerToysTelemetry.Log.Guid, 125 matchAnyKeywords: (ulong)TelemetryKeyword | (ulong)MeasuresKeyword | (ulong)CriticalDataKeyword); 126 127 this.started = true; 128 } 129 } 130 131 /// <summary> 132 /// Stops the trace session. 133 /// </summary> 134 public void Stop() 135 { 136 lock (this) 137 { 138 if (!this.started) 139 { 140 return; 141 } 142 143 if (this.traceSession != null) 144 { 145 Trace.TraceInformation("Disposing EventTraceSession"); 146 this.traceSession.Dispose(); 147 this.traceSession = null; 148 this.started = false; 149 } 150 } 151 } 152 153 /// <summary> 154 /// Disposes the object. 155 /// </summary> 156 /// <param name="disposing">boolean for disposing.</param> 157 protected virtual void Dispose(bool disposing) 158 { 159 if (!this.disposedValue) 160 { 161 if (disposing) 162 { 163 this.Stop(); 164 } 165 166 this.disposedValue = true; 167 } 168 } 169 } 170 }