/ src / common / ManagedTelemetry / Telemetry / EtwTrace.cs
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  }