Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,37 @@ wxc-exec.exe --debug config.json

See [docs/diagnostics.md](docs/diagnostics.md) for full diagnostics reference.

## Telemetry (Experimental)

MXC supports optional TraceLogging ETW telemetry for execution observability. When enabled, structured events (`MXC.Execution` and `MXC.Error`) are emitted to the local ETW subsystem via the Rust [`tracelogging`](https://crates.io/crates/tracelogging) crate. Every event includes common fields (Version, Channel, IsDebugging, `UTCReplace_AppSessionGuid`) as Part C custom event data.

Telemetry is **experimental** and requires:
1. The `--experimental` CLI flag
2. `"experimental": { "telemetry": { "enabled": true } }` in the JSON config

On non-Windows platforms, all telemetry functions are no-ops.

### Data Collection

The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkID=824704. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.

#### How to turn telemetry off

Telemetry is **off by default**. MXC emits telemetry only when **both** of the following are set, so no action is required to keep it disabled:

1. The `--experimental` CLI flag is passed, **and**
2. `"experimental": { "telemetry": { "enabled": true } }` is present in the JSON config.

Omitting either (the default) turns telemetry off entirely. On non-Windows platforms all telemetry functions are no-ops.

#### What official builds send

Official/shipped Microsoft builds set a TraceLogging provider group GUID at build time and route `MXC.Execution` and `MXC.Error` events to Microsoft through the UTC pipeline when telemetry is enabled. **Local and open-source builds send nothing to Microsoft by default** — the public source ships without a provider group GUID, so events are emitted to the local ETW subsystem only and are not routed to any Microsoft collection pipeline. Internal builds that set the `MXC_TELEMETRY_PROVIDER_GROUP_GUID` environment variable at build time enable the Microsoft-routed path.

No PII is collected. Events contain only execution metrics (duration, backend type, exit code) and a bounded error category (`error_type`). Free-form error message text is never emitted, so paths, usernames, and credentials cannot leak through telemetry. If you use the SDK to build applications, you are responsible for providing appropriate telemetry notices to your own users.

Privacy information can be found at https://privacy.microsoft.com and in the Microsoft privacy statement at https://go.microsoft.com/fwlink/?LinkID=824704.

## Documentation

| Document | Description |
Expand All @@ -231,11 +262,12 @@ See [docs/diagnostics.md](docs/diagnostics.md) for full diagnostics reference.
| [docs/macos-support/seatbelt-backend.md](docs/macos-support/seatbelt-backend.md) | Seatbelt backend (macOS) |
| [docs/windows-sandbox/windows-sandbox.md](docs/windows-sandbox/windows-sandbox.md) | Windows Sandbox backend |
| [docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md](docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md) | State-aware sandbox lifecycle API |
| [docs/telemetry/telemetry.md](docs/telemetry/telemetry.md) | TraceLogging telemetry architecture |

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.

## License

See [LICENSE.md](LICENSE.md) for details.
See [LICENSE.md](LICENSE.md) for details.
3 changes: 3 additions & 0 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ production configs and the dev schema when working on experimental features:
"launchMethod": "exec", // "exec" or "open" (LaunchServices, for Apple-constrained apps)
"nestedPty": true, // Allow inner process to allocate its own pty (posix_openpt)
"keychainAccess": false // Allow Keychain via securityd / trustd / cfprefsd / lsd.*
},
"telemetry": { // Telemetry (experimental, Windows only)
"enabled": true // Emit TraceLogging ETW events via pure Rust tracelogging crate
}
}
}
Expand Down
197 changes: 197 additions & 0 deletions docs/telemetry/telemetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# MXC Telemetry — Pure Rust TraceLogging Architecture

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: can we move this into a docs/telemetry folder?

Comment thread
RamonArjona4 marked this conversation as resolved.

MXC uses the Rust [`tracelogging`](https://crates.io/crates/tracelogging) crate
(published by Microsoft) for TraceLogging ETW telemetry. No C++ shim, WIL, or
FFI is required.
Comment thread
MGudgin marked this conversation as resolved.

## Overview

```
┌──────────────────────────────────────────────────────┐
│ wxc_common::telemetry │
│ (Rust — config resolution, sanitisation, types) │
│ │
│ init() / log_execution() / log_error() / shutdown() │
└───────────────┬──────────────────────────────────────┘
│ Direct Rust function calls
┌──────────────────────────────────────────────────────┐
│ mxc_telemetry (Rust crate) │
│ src/lib.rs — define_provider! + write_event! │
│ │
│ Windows: ETW events via tracelogging crate │
│ Linux/macOS: no-op stubs │
└──────────────────────────────────────────────────────┘
```

## Why the Rust `tracelogging` Crate (Not WIL C++ Shim)

An earlier design used a WIL C++ shim compiled via the `cc` crate. PR review
feedback correctly noted that the WIL dependency added C++ compilation, NuGet
download, FFI unsafety, and blocked non-Windows contributors from building the
crate. The Rust `tracelogging` crate provides the core ETW primitives needed,
and the small set of WIL features MXC actually uses can be replicated with
Rust constants and `write_event!` struct fields.

### Feature comparison

| Feature | WIL (`wil/TraceLogging.h`) | Rust `tracelogging` crate | MXC approach |
|---|---|---|---|
| **Provider group GUID** | `TraceLoggingOptionMicrosoftTelemetry()` | `group_id("...")` in `define_provider!` | `build.rs` generates `provider_def.rs` with/without `group_id` based on env var |
| **Sampling keywords** | `MICROSOFT_KEYWORD_MEASURES` named constant | Raw `u64` in `keyword(...)` | `const MICROSOFT_KEYWORD_MEASURES: u64 = 0x0000_4000_0000_0000` |
| **Common event fields** | `_GENERIC_PARTB_FIELDS_ENABLED` pattern | `struct("Name", { ... })` in `write_event!` | `struct("COMMON_MXC_PARAMS", { Version, Channel, IsDebugging, UTCReplace_AppSessionGuid })` |
| **Provider lifecycle** | `IMPLEMENT_TRACELOGGING_CLASS` singleton | `define_provider!` static + `register()`/`unregister()` | `OnceLock<ProviderState>` for version/channel, manual lifecycle |
| **Privacy Data Tags** | `TelemetryPrivacyDataTag(PDT_*)` | `u64("PartA_PrivTags", &val)` field | `PDT_PRODUCT_AND_SERVICE_USAGE` on all events |
| **Activity tracking** | `DEFINE_TELEMETRY_ACTIVITY` | Manual `Opcode` | Not needed for current events |

The remaining gap (activity tracking) is not needed for current events.
If needed later, it can be added incrementally.

## Common Event Fields (Part C)

Every MXC telemetry event includes a `COMMON_MXC_PARAMS` struct grouping
shared Part C custom event fields:

| Field | Type | Description |
|-------|------|-------------|
| `Version` | string | MXC crate version from `CARGO_PKG_VERSION` |
| `Channel` | string | `"dev"` for debug builds, `"release"` for release |
| `IsDebugging` | bool | `cfg!(debug_assertions)` — true for debug builds |
| `UTCReplace_AppSessionGuid` | bool | Always `true` — tells UTC to replace the app session GUID with a per-session identifier for privacy |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: app session guid and per-session identifier what's the difference?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "app session GUID" is the session-correlation GUID that UTC (the Windows telemetry agent) assigns to each diagnostic session by default. We always set UTCReplace_AppSessionGuid=true (see docs/telemetry/telemetry.md, the UTCReplace_AppSessionGuid row), which directs UTC to substitute that app session GUID with a fresh per-session identifier — a privacy-preserving id scoped to the individual session — before events leave the device. So emitted events carry the per-session id, not the raw app session GUID, and cannot be correlated back to it. No code change needed.


## Events

### MXC.Execution

Emitted when a one-shot execution completes (success or failure). It is also
emitted on early-exit failures in the one-shot executors — configuration,
policy, and backend-init failures that terminate before a runner produces a
result (with `mxc.exit_code` = 1 and `mxc.outcome` = `failure`).

> **Note:** The state-aware lifecycle (`provision` / `start` / `exec` /
> `stop` / `deprovision`) is not yet instrumented; only the one-shot path
> emits telemetry.

| Field | Type | Description |
|-------|------|-------------|
| `mxc.backend` | string | Containment backend name |
| `mxc.exit_code` | int32 | Process exit code |
| `mxc.outcome` | string | `"success"` or `"failure"` |
| `mxc.duration_ms` | uint64 | Total execution time |
| `mxc.failure_reason` | string | Failure category (if applicable) |

### MXC.Error

Emitted on execution errors.

| Field | Type | Description |
|-------|------|-------------|
| `mxc.backend` | string | Containment backend name |
| `mxc.error_type` | string | Error category (`config_error`, `process_error`, etc.) |
| `mxc.exit_code` | int32 | Process exit code |

> **No free-form error text is emitted.** Error messages can contain paths,
> usernames, or credentials, so `MXC.Error` deliberately carries only the
> bounded `error_type` category and the numeric `exit_code` — never the
> message string itself.

## Cross-Platform Behaviour

| Platform | Behaviour |
|----------|-----------|
| Windows | Full ETW telemetry via `tracelogging` crate |
| Linux | No-op — all telemetry functions return immediately |
| macOS | No-op — all telemetry functions return immediately |

## Private GUID Substitution (Internal Builds)

MXC supports an optional Microsoft telemetry group GUID for internal builds.
The mechanism is public; only the GUID value is private.

### How it works

```
build.rs execution flow
========================

1. Check MXC_TELEMETRY_PROVIDER_GROUP_GUID env var
├── NOT set → generate: define_provider!(MXC_PROVIDER, "Microsoft.MXC");
└── SET → generate: define_provider!(MXC_PROVIDER, "Microsoft.MXC",
group_id("{guid}"));

2. lib.rs includes the generated provider_def.rs via include!()
```

The provider GUID is **not** specified in either branch. The `tracelogging`
crate's `define_provider!` macro derives it deterministically from the provider
name using the standard ETW name-hash algorithm (the same algorithm used by
`<TraceLoggingProvider.h>`, WIL's `IMPLEMENT_TRACELOGGING_CLASS`, and .NET's
`EventSource`). For `"Microsoft.MXC"` the derived GUID is
`{7f10def4-a258-5fea-510e-2c3bb976687f}`. Keeping the name and GUID in lockstep
this way prevents drift and avoids hard-coding a literal that could collide
with another team's GUID.

### CI pipeline steps

Internal Microsoft builds set `MXC_TELEMETRY_PROVIDER_GROUP_GUID` to the real
Microsoft telemetry group GUID before `cargo build` on Windows, so events route
through the telemetry pipeline. Community forks that lack access to the private
GUID do not set this variable — the provider is registered without a group GUID
(plain ETW only).

> **Follow-up:** The provider group GUID is now provided by a secret variable
> on the official Windows build pipeline, so official builds can route events
> through the telemetry pipeline. The build has always honored the variable
> (see *Local developer testing* below); public builds and community forks,
> which do not have access to the variable, continue to register the provider
> without a group GUID (plain ETW only).

### Local developer testing

```powershell
# Test with a dummy group GUID (not the real one)
$env:MXC_TELEMETRY_PROVIDER_GROUP_GUID = '00000000-1111-2222-3333-444444444444'
cargo build -p mxc_telemetry

# Test without (public build)
Remove-Item Env:\MXC_TELEMETRY_PROVIDER_GROUP_GUID
cargo build -p mxc_telemetry
```

### What's public vs. private

| Item | Public? | Why |
|------|---------|-----|
| Provider name `"Microsoft.MXC"` | ✅ | Standard ETW naming |
| Provider GUID `{7f10def4-a258-5fea-510e-2c3bb976687f}` | ✅ | Derived from the name; identifies the provider, harmless |
| `build.rs` env var mechanism | ✅ | Mechanism is public |
| `MXC_TELEMETRY_PROVIDER_GROUP_GUID` env var name | ✅ | Key is public; value is private |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just set this up as a pipeline variable for our official pipeline here you should be able to click the variables button at the top right and see/confirm the guid I added.

Based on what you have in your build.rs below I believe this should actually work out the box as that environment variable should be visible to cargo at build time. I'll send you a link to the sharepoint which will show you how you can run an official build manually using this local branch.

| Actual Microsoft telemetry group GUID | ❌ | Private — set in CI only |

## SDK License Override (EULA for npm Package)

The public GitHub repo ships `sdk/LICENSE.md` as a plain MIT license. For
internal npm publishes, a separate EULA containing a **Section 2 — DATA**
clause (covering telemetry disclosure, opt-out, and GDPR) will be updated at
pack/publish time.

### How it works

```
1. CI pipeline (or local script) sets MXC_LICENSE_OVERRIDE env var
pointing to the markdown file of the EULA including additional telemetry language.
Note that the new EULA will include language outlining what data can be collected but
will otherwise remain MIT licensed.

2. A license-override script (added in a follow-up build-integration PR) runs:
├── MXC_LICENSE_OVERRIDE is set:
│ ├── Back up sdk/LICENSE.md → sdk/LICENSE.md.public
│ └── Copy new EULA over sdk/LICENSE.md
└── MXC_LICENSE_OVERRIDE is NOT set:
└── Restore sdk/LICENSE.md from .public backup (if exists)

3. npm pack / npm publish picks up the new EULA as the LICENSE.md
in the published package (sdk/package.json "files" includes LICENSE.md).

4. After publish, the revert path restores the original EULA document.
```
24 changes: 24 additions & 0 deletions schemas/dev/mxc-config.schema.0.8.0-dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@
],
"description": "Seatbelt backend config (pre-promotion alias)."
},
"telemetry": {
"anyOf": [
{
"$ref": "#/definitions/Telemetry"
},
{
"type": "null"
}
],
"description": "Telemetry configuration."
},
"test": {
"anyOf": [
{
Expand Down Expand Up @@ -749,6 +760,19 @@
},
"type": "object"
},
"Telemetry": {
"description": "Telemetry configuration (`experimental.telemetry`).",
"properties": {
"enabled": {
"description": "Explicit telemetry override. `true` = force on, `false` = force off, omitted = disabled (default off).",
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"TestFeature": {
"description": "Placeholder experimental feature.",
"properties": {
Expand Down
15 changes: 15 additions & 0 deletions sdk/src/generated/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export interface Experimental {
* Seatbelt backend config (pre-promotion alias).
*/
seatbelt?: Seatbelt | null;
/**
* Telemetry configuration.
*/
telemetry?: Telemetry | null;
/**
* Placeholder feature for testing experimental infrastructure.
*/
Expand Down Expand Up @@ -357,6 +361,17 @@ export interface Seatbelt {
profileOverride?: string | null;
}

/**
* Telemetry configuration (`experimental.telemetry`).
*/
export interface Telemetry {
/**
* Explicit telemetry override. `true` = force on, `false` = force off, omitted = disabled (default off).
*/
enabled?: boolean | null;
[k: string]: unknown;
}

/**
* Placeholder experimental feature.
*/
Expand Down
13 changes: 13 additions & 0 deletions sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,17 @@ export interface PortMapping {
protocol?: 'tcp';
}

/**
* Telemetry configuration for experimental TraceLogging ETW support.
*/
export interface TelemetryConfig {
/**
* Explicit telemetry override. `true` = force on, `false` = force off,
* `undefined` = off (default).
*/
enabled?: boolean;
}

/**
* Main WXC configuration
*/
Expand Down Expand Up @@ -292,6 +303,8 @@ export interface ContainerConfig {
experimental?: {
/** WSLC SDK configuration for Linux containers from Windows */
wslc?: WslcConfig;
/** Telemetry configuration for experimental TraceLogging ETW support */
telemetry?: TelemetryConfig;
};
/** macOS Seatbelt sandbox configuration (macOS only) */
seatbelt?: SeatbeltConfig;
Expand Down
24 changes: 24 additions & 0 deletions src/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading