// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.Diagnostics.Tracing; using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Diagnostics.Tracing.Session; namespace Microsoft.PowerToys.Telemetry { /// /// This class is based loosely on the C++ ETWTrace class in Win32client/Framework project. /// It is intended to record telemetry events generated by the PowerToys processes so that end users /// can view them if they want. /// public class ETWTrace : IDisposable { internal const EventKeywords TelemetryKeyword = (EventKeywords)0x0000200000000000; internal const EventKeywords MeasuresKeyword = (EventKeywords)0x0000400000000000; internal const EventKeywords CriticalDataKeyword = (EventKeywords)0x0000800000000000; private readonly bool telemetryEnabled = DataDiagnosticsSettings.GetEnabledValue(); // This is the global telemetry setting on whether to log events private readonly bool telemetryRecordingEnabled = DataDiagnosticsSettings.GetViewEnabledValue(); // This is the setting for recording telemetry events to disk for viewing private string etwFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\PowerToys\", "etw"); private bool disposedValue; private string sessionName; private string etwFilePath; private bool started; #nullable enable private TraceEventSession? traceSession; internal sealed class Lister : EventListener { public Lister() : base() { } } private Lister? listener; #nullable disable /// /// Initializes a new instance of the class. /// public ETWTrace() { Init(); } public ETWTrace(string etwPath) { this.etwFolderPath = etwPath; Init(); } private void Init() { if (File.Exists(etwFolderPath)) { File.Delete(etwFolderPath); } if (!Directory.Exists(etwFolderPath)) { Directory.CreateDirectory(etwFolderPath); } if (this.telemetryEnabled && this.telemetryRecordingEnabled) { this.Start(); } listener = new Lister(); listener.EnableEvents(PowerToysTelemetry.Log, EventLevel.LogAlways); } /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method this.Dispose(disposing: true); GC.SuppressFinalize(this); } /// /// Starts the trace session. /// public void Start() { lock (this) { if (this.started) { return; } new Task(() => { while (true) { Thread.Sleep(30 * 1000); this.traceSession.Flush(); } }).Start(); string executable = Process.GetCurrentProcess().ProcessName; string dateTimeNow = DateTime.Now.ToString("MM-d-yyyy__H_mm_ss", CultureInfo.InvariantCulture); this.sessionName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", executable, Environment.ProcessId, dateTimeNow); this.etwFilePath = Path.Combine(etwFolderPath, $"{this.sessionName}.etl"); this.traceSession = new TraceEventSession( this.sessionName, this.etwFilePath, (TraceEventSessionOptions)(TraceEventSessionOptions.Create | TraceEventSessionOptions.PrivateLogger | TraceEventSessionOptions.PrivateInProcLogger)); TraceEventProviderOptions args = new TraceEventProviderOptions(); this.traceSession.EnableProvider( PowerToysTelemetry.Log.Guid, matchAnyKeywords: (ulong)TelemetryKeyword | (ulong)MeasuresKeyword | (ulong)CriticalDataKeyword); this.started = true; } } /// /// Stops the trace session. /// public void Stop() { lock (this) { if (!this.started) { return; } if (this.traceSession != null) { Trace.TraceInformation("Disposing EventTraceSession"); this.traceSession.Dispose(); this.traceSession = null; this.started = false; } } } /// /// Disposes the object. /// /// boolean for disposing. protected virtual void Dispose(bool disposing) { if (!this.disposedValue) { if (disposing) { this.Stop(); } this.disposedValue = true; } } } }