|
| 1 | +<!-- Give your RFC a descriptive name saying what it would accomplish or what feature it defines --> |
| 2 | +RFC: Server request metrics |
| 3 | +============= |
| 4 | + |
| 5 | +<!-- RFCs start with the "RFC" status and are then either "Implemented" or "Rejected". --> |
| 6 | +> Status: RFC |
| 7 | +> |
| 8 | +> Applies to: server |
| 9 | +
|
| 10 | +<!-- A great RFC will include a list of changes at the bottom so that the implementor can be sure they haven't missed anything --> |
| 11 | +For a summarized list of proposed changes, see the [Changes Checklist](#changes-checklist) section. |
| 12 | + |
| 13 | +<!-- Insert a short paragraph explaining, at a high level, what this RFC is for --> |
| 14 | +This RFC defines the spec for metrique metrics integration for smithy generated servers using middleware. It will enable users to have an out-of-the-box experience with a set of default request and response metrics, as well as exposing the ability to define and integrate their own additional metrics using metrique in a turnkey fashion. The design ensures to maintain support for folding metrics from all layers of middleware, from custom outer-level middleware to model-level middleware and operation handlers. It also ensures that configuration options are available and extensible. |
| 15 | + |
| 16 | +<!-- The "Terminology" section is optional but is really useful for defining the technical terms you're using in the RFC --> |
| 17 | +Terminology |
| 18 | +----------- |
| 19 | + |
| 20 | +- **metrique**: https://crates.io/crates/metrique |
| 21 | + |
| 22 | +<!-- Explain how users will use this new feature and, if necessary, how this compares to the current user experience --> |
| 23 | +The user experience if this RFC is implemented |
| 24 | +---------------------------------------------- |
| 25 | + |
| 26 | +### In the current version of the SDK |
| 27 | + |
| 28 | +Users need to define their own metrics middleware. This involves creating a metrics HTTP plugin to support operation-level metrics, as well as a metrics layer if there is a need to fold outer and route level metrics. |
| 29 | + |
| 30 | +To avoid bloating this RFC, see below for an example of the code the user would need to write to integrate metrics into their smithy-rs service with compatibility with all middleware layers. This is with a barebones example with no imports, a few placeholder metrics, and no further logic for injection of customizations such as initialization settings. |
| 31 | + |
| 32 | +https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=9c40172125518215d2b4482526d1e306 |
| 33 | + |
| 34 | +### Once this RFC is implemented |
| 35 | + |
| 36 | +Users will be able to add default metrics to their service like this: |
| 37 | + |
| 38 | +```rust |
| 39 | +fn main() { |
| 40 | + let metrics_layer = MetricsLayer::new(); |
| 41 | + |
| 42 | + let app = PokemonService::builder(config) |
| 43 | + .get_pokemon_species(get_pokemon_species) |
| 44 | + .build() |
| 45 | + .expect("failed to build an instance of PokemonService"); |
| 46 | + |
| 47 | + let service = metrics_layer.layer(app); |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +For configuration of things like initialization, opting out, overriding how certain default metrics are set, etc, a builder will be provided: |
| 52 | + |
| 53 | +```rust |
| 54 | +fn main() { |
| 55 | + let config = MetricsLayerConfig::builder() |
| 56 | + .without_start_metric() |
| 57 | + .build(); |
| 58 | + let metrics_layer = MetricsLayer::builder(config) |
| 59 | + .init_metrics(|| { DefaultMetrics::default().append_on_drop(custom_sink) }) |
| 60 | + .build(); |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +To define additional metrics or add to the request extensions on top of the defaults: |
| 65 | + |
| 66 | +```rust |
| 67 | +fn main() { |
| 68 | + let metrics_layer: MetricsLayer<MyMetrics> = MetricsLayer::builder(MetricsLayerConfig::default()) |
| 69 | + .set_request_metrics(set_request_metrics) |
| 70 | + .build(); |
| 71 | +} |
| 72 | + |
| 73 | +#[smithy_metrics] |
| 74 | +#[metrics] |
| 75 | +struct MyMetrics { |
| 76 | + #[smithy_metrics(extension)] |
| 77 | + #[metrics(flatten)] |
| 78 | + operation_metrics: OperationMetrics, |
| 79 | + custom_metric: Option<String> |
| 80 | +} |
| 81 | + |
| 82 | +#[metrics] |
| 83 | +struct OperationMetrics { |
| 84 | + get_pokemon_species_metrics: Option<String> |
| 85 | +} |
| 86 | + |
| 87 | +fn set_request_metrics(req: Request<Body>, metrics: MyMetricsGuard) { |
| 88 | + req.custom_metric = ...; |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +<!-- Explain the implementation of this new feature --> |
| 93 | +How to actually implement this RFC |
| 94 | +---------------------------------- |
| 95 | + |
| 96 | +There will be two new rust-runtime crates `aws-smithy-http-server-metrics` and `aws-smithy-http-server-metrics-macro`, with types re-exported in the generated server. |
| 97 | + |
| 98 | +### `MetricsLayer` struct |
| 99 | + |
| 100 | +The focal struct that users can add to their service as a tower `Layer` for metrics, containing `new` and `builder` methods to get a `MetricsLayer` with the default configuration or a `MetricsLayerBuilder`, respectively. Because the `MetricsLayer` is specific to the struct provided in the type parameter, the `MetricsLayerBuilder` will be a product of the `#[smithy_metrics]` proc macro expansion, responsible for providing methods to customize things like the metrics initialization and setting custom request/response metrics. Contrarily, the `MetricsLayerConfig` along with its builder will be explicitly defined for general configuration not bound to any specific type parameter, such as enabling/disabling default metrics. |
| 101 | + |
| 102 | +Contains a generic type parameter bound by a marker trait with a default of `DefaultMetrics`, which will be a type defined in the library that uses the `#[smithy_metrics]` expansion. |
| 103 | + |
| 104 | +This will allow users the following construction experiences: |
| 105 | + |
| 106 | +- `MetricsLayer::new()` for the default metrics and extensions |
| 107 | + |
| 108 | +- `MetricsLayer::<CustomMetrics>::new()` for the default metrics with potential renaming or additional extensions from attribute proc macros |
| 109 | + |
| 110 | +- `MetricsLayer::builder(config)` for a builder to configure things like metrics initialization, how default metrics are set from the request/response objects, etc |
| 111 | + |
| 112 | +- `MetricsLayer::<CustomMetrics>::builder` for a builder with the ability to set their custom-defined metrics as well |
| 113 | + |
| 114 | +### `MetricsLayerConfig` struct |
| 115 | + |
| 116 | +Along with `MetricsLayerConfigBuilder`, structs for the general configuration of constructing a `MetricsLayer` not bound to any specific metrics type. This will include things like enabling/disabling default metrics. |
| 117 | + |
| 118 | +### `MetricsLayerService` struct |
| 119 | + |
| 120 | +A tower service for metrics that will contain the core logic for setting the metrics from the request/response objects. |
| 121 | + |
| 122 | +### `MetricsPlugin` struct |
| 123 | + |
| 124 | +An HTTP plugin metrics struct to allow for default operation-level metrics. |
| 125 | + |
| 126 | +The plugin system will mitigate the need to define a layer per operation for metrics like the operation name. A Metrics Plugin will be implemented to set the operation and service metrics from the `Operation` and `Service` type parameters. It will be applied to the service in the operation setters, which will require a change in the server codegen. |
| 127 | + |
| 128 | +### `MetricsPluginService` struct |
| 129 | + |
| 130 | +A tower service for metrics that will contain the logic for setting the default operation-specific metrics. |
| 131 | + |
| 132 | +### `DefaultMetrics` struct |
| 133 | + |
| 134 | +A struct for default metrics. This will use the `#[smithy_metrics]` and be used as the default type parameter for `MetricsLayer`. |
| 135 | + |
| 136 | +### `DefaultRequestMetrics` struct |
| 137 | + |
| 138 | +A struct that will contain fields for the default request metrics, a field for which will be added to a struct containing the `#[smithy_metrics]` annotation. |
| 139 | + |
| 140 | +### `DefaultResponseMetrics` struct |
| 141 | + |
| 142 | +A struct that will contain fields for the default response metrics, a field for which will be added to a struct containing the `#[smithy_metrics]` annotation. |
| 143 | + |
| 144 | +### `#[smithy_metrics]` attribute proc macro |
| 145 | + |
| 146 | +A proc macro that can be placed on a metrique metrics struct for purposes such as the addition of default request/response metrics fields, the implementation of a marker trait, and the expansion of a `MetricsLayerBuilder` with concrete type being the annotated struct. |
| 147 | + |
| 148 | +This will also come with `#[smithy_metrics(rename(x = "y"))]` to rename default fields and `#[smithy_metrics(extension)]` to mark struct fields for insertion to the request extensions to be used in custom middleware or operation handlers. |
| 149 | + |
| 150 | +<!-- Include a checklist of all the things that need to happen for this RFC's implementation to be considered complete --> |
| 151 | +Changes checklist |
| 152 | +----------------- |
| 153 | + |
| 154 | +- [] Create `rust-runtime` crates `aws-smithy-http-server-metrics` and `aws-smithy-http-server-metrics-macro` |
| 155 | + |
| 156 | +- [] Implement struct `MetricsLayer<T>` |
| 157 | + |
| 158 | + - [] Implement `new` and `builder` methods |
| 159 | + |
| 160 | +- [] Implement struct `MetricsLayerBuilder<T>` |
| 161 | + |
| 162 | + - [] Implement method for custom metrics initialization |
| 163 | + |
| 164 | + - [] Implement methods for setting custom metrics from request and response objects |
| 165 | + |
| 166 | +- [] Implement struct `MetricsLayerConfig` |
| 167 | + |
| 168 | + - [] Implement default with out-of-the-box metrics enabled |
| 169 | + |
| 170 | +- [] Implement struct `MetricsLayerConfigBuilder` |
| 171 | + |
| 172 | + - [] Implement methods to opt out of all or individual default request/response metrics |
| 173 | + |
| 174 | +- [] Implement struct `MetricsPlugin` |
| 175 | + |
| 176 | + - [] Implement HTTPMarker |
| 177 | + |
| 178 | + - [] Implement `Plugin` to map to `MetricsService` |
| 179 | + |
| 180 | +- [] Implement struct `MetricsService` to contain the tower service logic for invoking the passed functions for setting request/response metrics and adding to the request extensions |
| 181 | + |
| 182 | + - [] Implement `Clone` |
| 183 | + |
| 184 | + - [] Implement tower `Service` to contain logic for: |
| 185 | + |
| 186 | + - [] Initializing the metrics using the initialization function if passed, `Default` and a global entry sink, or emitting a compiler error saying one of these two must be satisfied |
| 187 | + |
| 188 | + - [] Calling the request/response metrics handlers |
| 189 | + |
| 190 | +- [] Implement struct `DefaultMetrics` to be a unit struct with the `#[smithy_metrics]` attribute to expand a builder with just the default metrics |
| 191 | + |
| 192 | +- [] Implement struct `DefaultRequestMetrics` to be the type of a field that gets added to a `#[smithy_metrics]`-annotated struct to add the default request metrics to |
| 193 | + |
| 194 | +- [] Implement struct `DefaultResponseMetrics`to be the type of a field that gets added to a `#[smithy_metrics]`-annotated struct to add the default response metrics to |
| 195 | + |
| 196 | +- [] Implement proc macro attribute `#[smithy_metrics]` |
| 197 | + |
| 198 | + - [] Implement expansion for implementing a marker struct |
| 199 | + |
| 200 | + - [] Implement expansion to wrap the types of fields annotated with `#[smithy_metrics(extension)]` with `Slotguard` if the user hasn't explicitly done so, or show an error message telling them to |
| 201 | + |
| 202 | + - [] Implement expansion to define and implement a `MetricsLayerBuilder` with the concrete type parameter being the annotated struct with methods to: |
| 203 | + |
| 204 | + - [] Pass a custom metrics initialization function |
| 205 | + |
| 206 | + - [] Pass custom request/response setter functions |
| 207 | + |
| 208 | +- [] Create proc macro attribute `#[smithy_metrics(extension)]` on fields of a metrics struct to give users the ability to annotate the fields they want to be accessible from the request extensions down the line, such as in custom middleware or operation handlers |
| 209 | + |
| 210 | +- [] Create proc macro attribute `#[smithy_metrics(rename(default_metric_name = "custom_name"))]` to give users the ability to rename default metrics |
| 211 | + |
| 212 | +- [] Create a type alias for extension type containing the slotguards of the types from the annotated fields of the metrics struct, e.g. `type OperationMetricsExtension = Extension<SlotGuard<OperationMetrics>>` |
| 213 | + |
| 214 | +- [] In the server codegen, apply the metrics plugin in all operation setters |
0 commit comments