2024-10-24 22:04:32 +02:00
// 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
{
/// <summary>
/// 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.
/// </summary>
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
2024-10-28 13:09:08 +01:00
private string etwFolderPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , @"Microsoft\PowerToys\" , "etw" ) ;
2024-10-24 22:04:32 +02:00
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
/// <summary>
/// Initializes a new instance of the <see cref="ETWTrace"/> class.
/// </summary>
public ETWTrace ( )
2024-10-28 13:09:08 +01:00
{
Init ( ) ;
}
public ETWTrace ( string etwPath )
{
this . etwFolderPath = etwPath ;
Init ( ) ;
}
private void Init ( )
2024-10-24 22:04:32 +02:00
{
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 ) ;
}
/// <inheritdoc/>
public void Dispose ( )
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this . Dispose ( disposing : true ) ;
GC . SuppressFinalize ( this ) ;
}
/// <summary>
/// Starts the trace session.
/// </summary>
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 ;
}
}
/// <summary>
/// Stops the trace session.
/// </summary>
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 ;
}
}
}
/// <summary>
/// Disposes the object.
/// </summary>
/// <param name="disposing">boolean for disposing.</param>
protected virtual void Dispose ( bool disposing )
{
if ( ! this . disposedValue )
{
if ( disposing )
{
this . Stop ( ) ;
}
this . disposedValue = true ;
}
}
}
}