diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 064e9ec94b..8ae7b6efff 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -197,6 +197,7 @@ Canvascustomlayout CAPTUREBLT CAPTURECHANGED CARETBLINKING +carlos Carlseibert CAtl caub @@ -217,6 +218,7 @@ certmgr cfp CHANGECBCHAIN changecursor +chatasweetie checkmarks CHILDACTIVATE CHILDWINDOW @@ -2242,6 +2244,7 @@ YSpeed YStr YTimer YVIRTUALSCREEN +zamora ZEROINIT zonability zonable diff --git a/doc/devdocs/events.md b/doc/devdocs/events.md new file mode 100644 index 0000000000..1bc2d5bba6 --- /dev/null +++ b/doc/devdocs/events.md @@ -0,0 +1,197 @@ +# Telemetry Events + +PowerToys collects limited telemetry to understand feature usage, reliability, and product quality. When adding a new telemetry event, follow the steps below to ensure the event is properly declared, documented, and available after release. + +**⚠️ Important**: Telemetry must never include personal information, file paths, or user‑generated content. + +## Developer Effort Overview (What to Expect) + +Adding a telemetry event is a **multi-step process** that typically spans several areas of the codebase and documentation. + +At a high level, developers should expect to: + +1. Within one PR: + 1. Add a new telemetry event(s) to module + 1. Add the new event(s) DATA_AND_PRIVACY.md +1. Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s) + +### Privacy Guidelines + +**NEVER** log: + +- User data (text, files, emails, etc.) +- File paths or filenames +- Personal information +- Sensitive system information +- Anything that could identify a specific user + +DO log: + +- Feature usage (which features, how often) +- Success/failure status +- Timing/performance metrics +- Error types (not error messages with user data) +- Aggregate counts + +### Event Naming Convention + +Follow this pattern: `UtilityName_EventDescription` + +Examples: + +- `ColorPicker_Session` +- `FancyZones_LayoutApplied` +- `PowerRename_Rename` +- `AdvancedPaste_FormatClicked` +- `CmdPal_ExtensionInvoked` + +## Adding Telemetry Events to PowerToys + +PowerToys uses ETW (Event Tracing for Windows) for telemetry in both C++ and C# modules. The telemetry system is: + +- Opt-in by default (disabled since v0.86) +- Privacy-focused - never logs personal info, file paths, or user-generated content +- Controlled by registry - HKEY_CURRENT_USER\Software\Classes\PowerToys\AllowDataDiagnostics + +### C++ Telemetry Implementation + +**Core Components** + +| File | Purpose | +| ------------- |:-------------:| +| [ProjectTelemetry.h](../../src/common/Telemetry/ProjectTelemetry.h) | Declares the global ETW provider g_hProvider | +| [TraceBase.h](../../src/common/Telemetry/TraceBase.h) | Base class with RegisterProvider(), UnregisterProvider(), and IsDataDiagnosticsEnabled() check | +| [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Privacy tags and telemetry option group macros + + +#### Pattern for C++ Modules + +1. Create a `Trace` class inheriting from `telemetry::TraceBase` (src/common/Telemetry/TraceBase.h): + + ```c + // trace.h + #pragma once + #include + + class Trace : public telemetry::TraceBase + { + public: + static void MyEvent(/* parameters */); + }; + ``` + +2. Implement events using `TraceLoggingWriteWrapper`: + + ```cpp + // trace.cpp + #include "trace.h" + #include + + TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + + void Trace::MyEvent(bool enabled) + { + TraceLoggingWriteWrapper( + g_hProvider, + "ModuleName_EventName", // Event name + TraceLoggingBoolean(enabled, "Enabled"), // Event data + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + } + ``` + +**Key C++ Telemetry Macros** + +| Macro | Purpose | +| ------------- |:-------------:| +| `TraceLoggingWriteWrapper` [CustomAction.cpp](../../installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp) | Wraps `TraceLoggingWrite` with `IsDataDiagnosticsEnabled()` check | +| `ProjectTelemetryPrivacyDataTag(tag)` [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Sets privacy classification | + +### C# Telemetry Implementation + +**Core Components** + +| File | Purpose | +| ------------- |:-------------:| +| [PowerToysTelemetry.cs](../../src/common/ManagedTelemetry/Telemetry/PowerToysTelemetry.cs) | Singleton `Log` instance with `WriteEvent()` method | +| [EventBase.cs](../../src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) | Base class for all events (provides `EventName`, `Version`) | +| [IEvent.cs](../../src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) | Interface requiring `PartA_PrivTags` property | +| [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Inherits from `EventSource`, defines ETW constants | +| [DataDiagnosticsSettings.cs](../../src/common/ManagedTelemetry/Telemetry/DataDiagnosticsSettings.cs) | Registry-based enable/disable check + +#### Pattern for C# Modules + +1. Create an event class inheriting from `EventBase` and implementing `IEvent`: + + ```csharp + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Tracing; + using Microsoft.PowerToys.Telemetry; + using Microsoft.PowerToys.Telemetry.Events; + + namespace MyModule.Telemetry + { + [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + public class MyModuleEvent : EventBase, IEvent + { + // Event properties (logged as telemetry data) + public string SomeProperty { get; set; } + public int SomeValue { get; set; } + + // Required: Privacy tag + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + + // Optional: Set EventName in constructor (defaults to class name) + public MyModuleEvent(string prop, int val) + { + EventName = "MyModule_EventName"; + SomeProperty = prop; + SomeValue = val; + } + } + } + ``` + +2. Log the event: + +```csharp +PowerToysTelemetry.Log.WriteEvent(new MyModuleEvent("value", 42)); +``` + +**Privacy Tags (C#)** + +| Tag | Use Case | +| ------------- |:-------------:| +| `PartA_PrivTags.ProductAndServiceUsage` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Feature usage events +| `PartA_PrivTags.ProductAndServicePerformance` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Performance/timing events + +### Update DATA_AND_PRIVACY.md file + +Add your new event(s) to [DATA_AND_PRIVACY.md](../../DATA_AND_PRIVACY.md). + +## Launch Product Version Containing the new events + +Events do not become active until they ship in a released PowerToys version. After your PRs are merged: + +- The event will begin firing once users install the version that includes it +- In order for PowerToys to process these events, you must complete the next section + +## Next Steps + +Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s). + +## Summary + +Required steps: + +1. In one PR: + - Add the event(s) in code + - Document event(s) in DATA_AND_PRIVACY.md +1. Ship the change in a PowerToys release +1. Reach out for next steps