├── .github ├── .kodiak.toml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.md ├── appinsights-contracts-codegen ├── Cargo.toml ├── schema │ ├── AvailabilityData.json │ ├── Base.json │ ├── ContextTagKeys.json │ ├── Data.json │ ├── DataPoint.json │ ├── DataPointType.json │ ├── Domain.json │ ├── Envelope.json │ ├── EventData.json │ ├── ExceptionData.json │ ├── ExceptionDetails.json │ ├── MessageData.json │ ├── MetricData.json │ ├── PageViewData.json │ ├── RemoteDependencyData.json │ ├── RequestData.json │ ├── SeverityLevel.json │ └── StackFrame.json └── src │ ├── ast │ ├── attributes.rs │ ├── enums.rs │ ├── fields.rs │ ├── mod.rs │ ├── namespaces.rs │ ├── schemas.rs │ ├── structs.rs │ └── types.rs │ ├── compiler │ ├── generator │ │ ├── enums.rs │ │ ├── mod.rs │ │ ├── packages.rs │ │ ├── schemas.rs │ │ ├── structs.rs │ │ └── types.rs │ ├── mod.rs │ ├── module.rs │ └── visitor.rs │ ├── lib.rs │ ├── main.rs │ └── parser.rs ├── appinsights ├── Cargo.toml ├── examples │ ├── blocking.rs │ └── default.rs ├── src │ ├── blocking │ │ ├── integration_tests.rs │ │ └── mod.rs │ ├── channel │ │ ├── command.rs │ │ ├── memory.rs │ │ ├── mod.rs │ │ ├── retry.rs │ │ └── state.rs │ ├── client │ │ ├── integration_tests.rs │ │ └── mod.rs │ ├── config.rs │ ├── context.rs │ ├── contracts │ │ ├── availability_data.rs │ │ ├── base.rs │ │ ├── data.rs │ │ ├── data_point.rs │ │ ├── data_point_type.rs │ │ ├── envelope.rs │ │ ├── event_data.rs │ │ ├── exception_data.rs │ │ ├── exception_details.rs │ │ ├── message_data.rs │ │ ├── metric_data.rs │ │ ├── mod.rs │ │ ├── page_view_data.rs │ │ ├── remote_dependency_data.rs │ │ ├── request_data.rs │ │ ├── response.rs │ │ ├── severity_level.rs │ │ └── stack_frame.rs │ ├── lib.rs │ ├── telemetry │ │ ├── availability.rs │ │ ├── event.rs │ │ ├── exception.rs │ │ ├── measurements.rs │ │ ├── metric │ │ │ ├── aggregation.rs │ │ │ ├── measurement.rs │ │ │ ├── mod.rs │ │ │ └── stats.rs │ │ ├── mod.rs │ │ ├── page_view.rs │ │ ├── properties.rs │ │ ├── remote_dependency.rs │ │ ├── request.rs │ │ ├── tags.rs │ │ └── trace.rs │ ├── time.rs │ ├── timeout.rs │ ├── transmitter.rs │ └── uuid.rs └── tests │ ├── logger.rs │ ├── telemetry.rs │ └── telemetry_blocking.rs └── rustfmt.toml /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | # .kodiak.toml 2 | version = 1 3 | 4 | [approve] 5 | auto_approve_usernames = ["dependabot-preview"] 6 | 7 | [merge] 8 | method = "squash" # default: "merge" 9 | delete_branch_on_merge = true # default: false 10 | 11 | [merge.message] 12 | title = "pull_request_title" # default: "github_default" 13 | body = "pull_request_body" # default: "github_default" 14 | strip_html_comments = true # default: false 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - automerge 10 | ignore: 11 | - dependency-name: reqwest 12 | versions: 13 | - 0.11.1 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | check: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: checkout branch 17 | uses: actions/checkout@master 18 | 19 | - name: install stable rust 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | profile: minimal 24 | components: rustfmt, clippy 25 | 26 | - name: clippy 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: clippy 30 | args: --workspace --all-features --tests --bins --examples -- -A clippy::enum_variant_names 31 | 32 | - name: check 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: check 36 | args: --workspace --all-features --bins --examples 37 | 38 | test: 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - name: checkout branch 43 | uses: actions/checkout@master 44 | 45 | - name: install stable rust 46 | uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | profile: minimal 50 | 51 | - name: tests 52 | uses: actions-rs/cargo@v1 53 | with: 54 | command: test 55 | args: --workspace --all-features -- --exact --nocapture 56 | env: 57 | APPINSIGHTS_INSTRUMENTATIONKEY: ${{ secrets.APPINSIGHTS_INSTRUMENTATIONKEY }} 58 | 59 | format: 60 | runs-on: ubuntu-latest 61 | 62 | steps: 63 | - name: checkout branch 64 | uses: actions/checkout@master 65 | 66 | - name: install rustfmt 67 | uses: actions-rs/toolchain@v1 68 | with: 69 | toolchain: stable 70 | profile: minimal 71 | components: rustfmt 72 | 73 | - name: fmt 74 | run: cargo fmt --all -- --check 75 | 76 | # - name: docs 77 | # run: cargo doc 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | # Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # IntelliJ Idea files 13 | /.idea/ 14 | 15 | # VS Code 16 | /.vscode/ 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "appinsights", 5 | "appinsights-contracts-codegen" 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Denis Molokanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Application Insights for Rust 2 | [![build-status](https://github.com/dmolokanov/appinsights-rs/workflows/CI/badge.svg)](https://github.com/dmolokanov/appinsights-rs/actions) 3 | [![crate-status](https://img.shields.io/crates/v/appinsights.svg)](https://crates.io/crates/appinsights) 4 | [![docs-status](https://docs.rs/appinsights/badge.svg)](https://docs.rs/appinsights) 5 | [![dependabot-status](https://api.dependabot.com/badges/status?host=github&repo=dmolokanov/appinsights-rs)](https://dependabot.com) 6 | 7 | This project provides a Rust SDK for [Application Insights](http://azure.microsoft.com/en-us/services/application-insights/). Application Insights is an APM service that helps to monitor running applications. This Rust crate allows to send various kinds of telemetry information to the server to be visualized later on Azure Portal. 8 | 9 | > :triangular_flag_on_post: **Disclaimer** 10 | > This project is not an officially recognized Microsoft product and is not an endorsement of any future product offering from Microsoft. 11 | > 12 | > _Microsoft and Azure are registered trademarks of Microsoft Corporation._ 13 | 14 | ## Installation 15 | ```bash 16 | $ cargo add appinsights 17 | ``` 18 | or just add this to your `Cargo.toml`: 19 | 20 | ```toml 21 | [dependencies] 22 | appinisghts = "0.2" 23 | ``` 24 | 25 | ## Usage 26 | 27 | To start tracking telemetry for your application first thing you need to do is to obtain an [Instrumentation Key](https://docs.microsoft.com/en-us/azure/azure-monitor/app/create-new-resource) and initialize `TelemetryClient` with it. 28 | 29 | This client will be used to send all telemetry data to Application Insights. This SDK doesn't collect any telemetry automatically, so this client should be used everywhere in the code to report health information about an application. 30 | 31 | ```rust 32 | use appinsights::TelemetryClient; 33 | 34 | #[tokio::main] 35 | async fn main() { 36 | // configure telemetry client with default settings 37 | let client = TelemetryClient::new("".to_string()); 38 | 39 | // send event telemetry to the Application Insights server 40 | client.track_event("application started"); 41 | 42 | // stop the client 43 | client.close_channel().await; 44 | } 45 | ``` 46 | If you need more control over the client's behavior, you can create a new instance of `TelemetryConfig` and initialize a `TelemetryClient` with it. 47 | 48 | ```rust 49 | use std::time::Duration; 50 | use appinsights::{TelemetryClient, TelemetryConfig}; 51 | use appinsights::telemetry::SeverityLevel; 52 | 53 | #[tokio::main] 54 | async fn main() { 55 | // configure telemetry config with custom settings 56 | let config = TelemetryConfig::builder() 57 | // provide an instrumentation key for a client 58 | .i_key("") 59 | // set a new maximum time to wait until data will be sent to the server 60 | .interval(Duration::from_secs(5)) 61 | // construct a new instance of telemetry configuration 62 | .build(); 63 | 64 | // configure telemetry client with default settings 65 | let client = TelemetryClient::from_config(config); 66 | 67 | // send trace telemetry to the Application Insights server 68 | client.track_trace("A database error occurred", SeverityLevel::Warning); 69 | 70 | // stop the client 71 | client.close_channel().await; 72 | } 73 | ``` 74 | 75 | ## License 76 | This project is licensed under the terms of the [MIT](LICENSE) license. -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | - [x] Common properties 3 | - [x] Context tags to expose convenient API 4 | - [x] Setup default context 5 | - [x] Add examples how to configure context (and pass to context) 6 | - [x] Fix mutability inconvenience for tags 7 | - [x] Add keywords to Cargo.toml 8 | - [x] Make flush and close operations work in a predictable manner 9 | - [ ] Check that telemetry items are not lost when server unavailable (500) and all retries exhausted 10 | - [ ] Throttle sending when items read from events_channel using limits from client config 11 | - [ ] Make flush_channel_and_wait() ? 12 | - [ ] Revisit telemetry client, items and context user facing methods 13 | - [ ] make Stats immutable 14 | - [ ] Support exceptions telemetry with rust backtrace 15 | - [ ] Handle message throttling from server 16 | - [ ] Validate parameters based on attributes of contracts schema 17 | - [ ] Make a HTTP client configurable via features 18 | - [ ] Makefile 19 | - [ ] Refactor codegen to produce contracts with zero change 20 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "appinsights-contracts-codegen" 3 | version = "0.1.0" 4 | authors = ["dmolokanov "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | codegen = "0.2" 9 | structopt = "0.3" 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | heck = "0.4" 13 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/AvailabilityData.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "Domain.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": { 15 | "declaration": { 16 | "structBase": null, 17 | "tag": "Struct", 18 | "structFields": [], 19 | "declParams": [], 20 | "declNamespaces": [ 21 | { 22 | "name": [ 23 | "AI" 24 | ] 25 | } 26 | ], 27 | "declName": "Domain", 28 | "declAttributes": [ 29 | { 30 | "attrName": [ 31 | "Description" 32 | ], 33 | "attrValue": "The abstract common base of all domains." 34 | } 35 | ] 36 | }, 37 | "type": "user" 38 | }, 39 | "tag": "Struct", 40 | "structFields": [ 41 | { 42 | "fieldModifier": "Required", 43 | "fieldDefault": { 44 | "value": 2, 45 | "type": "integer" 46 | }, 47 | "fieldType": "int32", 48 | "fieldName": "ver", 49 | "fieldAttributes": [ 50 | { 51 | "attrName": [ 52 | "Description" 53 | ], 54 | "attrValue": "Schema version" 55 | } 56 | ], 57 | "fieldOrdinal": 10 58 | }, 59 | { 60 | "fieldModifier": "Required", 61 | "fieldDefault": null, 62 | "fieldType": "string", 63 | "fieldName": "id", 64 | "fieldAttributes": [ 65 | { 66 | "attrName": [ 67 | "MaxStringLength" 68 | ], 69 | "attrValue": "512" 70 | }, 71 | { 72 | "attrName": [ 73 | "Description" 74 | ], 75 | "attrValue": "Identifier of a test run. Use it to correlate steps of test run and telemetry generated by the service." 76 | }, 77 | { 78 | "attrName": [ 79 | "ActAsRequired" 80 | ], 81 | "attrValue": "Renaming testRunId to id." 82 | } 83 | ], 84 | "fieldOrdinal": 21 85 | }, 86 | { 87 | "fieldModifier": "Required", 88 | "fieldDefault": null, 89 | "fieldType": "string", 90 | "fieldName": "name", 91 | "fieldAttributes": [ 92 | { 93 | "attrName": [ 94 | "MaxStringLength" 95 | ], 96 | "attrValue": "1024" 97 | }, 98 | { 99 | "attrName": [ 100 | "Description" 101 | ], 102 | "attrValue": "Name of the test that these availability results represent." 103 | }, 104 | { 105 | "attrName": [ 106 | "ActAsRequired" 107 | ], 108 | "attrValue": "Renaming testName to name." 109 | } 110 | ], 111 | "fieldOrdinal": 41 112 | }, 113 | { 114 | "fieldModifier": "Required", 115 | "fieldDefault": null, 116 | "fieldType": "string", 117 | "fieldName": "duration", 118 | "fieldAttributes": [ 119 | { 120 | "attrName": [ 121 | "Description" 122 | ], 123 | "attrValue": "Duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days." 124 | }, 125 | { 126 | "attrName": [ 127 | "CSType" 128 | ], 129 | "attrValue": "TimeSpan" 130 | } 131 | ], 132 | "fieldOrdinal": 50 133 | }, 134 | { 135 | "fieldModifier": "Required", 136 | "fieldDefault": null, 137 | "fieldType": "bool", 138 | "fieldName": "success", 139 | "fieldAttributes": [ 140 | { 141 | "attrName": [ 142 | "ActAsRequired" 143 | ], 144 | "attrValue": "Renaming result to success." 145 | }, 146 | { 147 | "attrName": [ 148 | "Description" 149 | ], 150 | "attrValue": "Success flag." 151 | } 152 | ], 153 | "fieldOrdinal": 61 154 | }, 155 | { 156 | "fieldModifier": "Optional", 157 | "fieldDefault": null, 158 | "fieldType": "string", 159 | "fieldName": "runLocation", 160 | "fieldAttributes": [ 161 | { 162 | "attrName": [ 163 | "MaxStringLength" 164 | ], 165 | "attrValue": "1024" 166 | }, 167 | { 168 | "attrName": [ 169 | "Description" 170 | ], 171 | "attrValue": "Name of the location where the test was run from." 172 | } 173 | ], 174 | "fieldOrdinal": 70 175 | }, 176 | { 177 | "fieldModifier": "Optional", 178 | "fieldDefault": null, 179 | "fieldType": "string", 180 | "fieldName": "message", 181 | "fieldAttributes": [ 182 | { 183 | "attrName": [ 184 | "MaxStringLength" 185 | ], 186 | "attrValue": "8192" 187 | }, 188 | { 189 | "attrName": [ 190 | "Description" 191 | ], 192 | "attrValue": "Diagnostic message for the result." 193 | } 194 | ], 195 | "fieldOrdinal": 80 196 | }, 197 | { 198 | "fieldModifier": "Optional", 199 | "fieldDefault": null, 200 | "fieldType": { 201 | "key": "string", 202 | "type": "map", 203 | "element": "string" 204 | }, 205 | "fieldName": "properties", 206 | "fieldAttributes": [ 207 | { 208 | "attrName": [ 209 | "Description" 210 | ], 211 | "attrValue": "Collection of custom properties." 212 | }, 213 | { 214 | "attrName": [ 215 | "MaxKeyLength" 216 | ], 217 | "attrValue": "150" 218 | }, 219 | { 220 | "attrName": [ 221 | "MaxValueLength" 222 | ], 223 | "attrValue": "8192" 224 | } 225 | ], 226 | "fieldOrdinal": 100 227 | }, 228 | { 229 | "fieldModifier": "Optional", 230 | "fieldDefault": null, 231 | "fieldType": { 232 | "key": "string", 233 | "type": "map", 234 | "element": "double" 235 | }, 236 | "fieldName": "measurements", 237 | "fieldAttributes": [ 238 | { 239 | "attrName": [ 240 | "Description" 241 | ], 242 | "attrValue": "Collection of custom measurements." 243 | }, 244 | { 245 | "attrName": [ 246 | "MaxKeyLength" 247 | ], 248 | "attrValue": "150" 249 | } 250 | ], 251 | "fieldOrdinal": 200 252 | } 253 | ], 254 | "declParams": [], 255 | "declNamespaces": [ 256 | { 257 | "name": [ 258 | "AI" 259 | ] 260 | } 261 | ], 262 | "declName": "AvailabilityData", 263 | "declAttributes": [ 264 | { 265 | "attrName": [ 266 | "Description" 267 | ], 268 | "attrValue": "Instances of AvailabilityData represent the result of executing an availability test." 269 | } 270 | ] 271 | } 272 | ] 273 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/Base.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [], 10 | "declarations": [ 11 | { 12 | "structBase": null, 13 | "tag": "Struct", 14 | "structFields": [ 15 | { 16 | "fieldModifier": "Optional", 17 | "fieldDefault": null, 18 | "fieldType": "string", 19 | "fieldName": "baseType", 20 | "fieldAttributes": [ 21 | { 22 | "attrName": [ 23 | "Name" 24 | ], 25 | "attrValue": "ItemTypeName" 26 | }, 27 | { 28 | "attrName": [ 29 | "Description" 30 | ], 31 | "attrValue": "Name of item (B section) if any. If telemetry data is derived straight from this, this should be null." 32 | } 33 | ], 34 | "fieldOrdinal": 10 35 | } 36 | ], 37 | "declParams": [], 38 | "declNamespaces": [ 39 | { 40 | "name": [ 41 | "AI" 42 | ] 43 | } 44 | ], 45 | "declName": "Base", 46 | "declAttributes": [ 47 | { 48 | "attrName": [ 49 | "Description" 50 | ], 51 | "attrValue": "Data struct to contain only C section with custom fields." 52 | } 53 | ] 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/Data.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "Base.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": { 15 | "declaration": { 16 | "structBase": null, 17 | "tag": "Struct", 18 | "structFields": [ 19 | { 20 | "fieldModifier": "Optional", 21 | "fieldDefault": null, 22 | "fieldType": "string", 23 | "fieldName": "baseType", 24 | "fieldAttributes": [ 25 | { 26 | "attrName": [ 27 | "Name" 28 | ], 29 | "attrValue": "ItemTypeName" 30 | }, 31 | { 32 | "attrName": [ 33 | "Description" 34 | ], 35 | "attrValue": "Name of item (B section) if any. If telemetry data is derived straight from this, this should be null." 36 | } 37 | ], 38 | "fieldOrdinal": 10 39 | } 40 | ], 41 | "declParams": [], 42 | "declNamespaces": [ 43 | { 44 | "name": [ 45 | "AI" 46 | ] 47 | } 48 | ], 49 | "declName": "Base", 50 | "declAttributes": [ 51 | { 52 | "attrName": [ 53 | "Description" 54 | ], 55 | "attrValue": "Data struct to contain only C section with custom fields." 56 | } 57 | ] 58 | }, 59 | "type": "user" 60 | }, 61 | "tag": "Struct", 62 | "structFields": [ 63 | { 64 | "fieldModifier": "Required", 65 | "fieldDefault": null, 66 | "fieldType": { 67 | "value": { 68 | "paramConstraint": null, 69 | "paramName": "TDomain" 70 | }, 71 | "type": "parameter" 72 | }, 73 | "fieldName": "baseData", 74 | "fieldAttributes": [ 75 | { 76 | "attrName": [ 77 | "Name" 78 | ], 79 | "attrValue": "Item" 80 | }, 81 | { 82 | "attrName": [ 83 | "Description" 84 | ], 85 | "attrValue": "Container for data item (B section)." 86 | } 87 | ], 88 | "fieldOrdinal": 20 89 | } 90 | ], 91 | "declParams": [ 92 | { 93 | "paramConstraint": null, 94 | "paramName": "TDomain" 95 | } 96 | ], 97 | "declNamespaces": [ 98 | { 99 | "name": [ 100 | "AI" 101 | ] 102 | } 103 | ], 104 | "declName": "Data", 105 | "declAttributes": [ 106 | { 107 | "attrName": [ 108 | "Description" 109 | ], 110 | "attrValue": "Data struct to contain both B and C sections." 111 | } 112 | ] 113 | } 114 | ] 115 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/DataPoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "DataPointType.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": null, 15 | "tag": "Struct", 16 | "structFields": [ 17 | { 18 | "fieldModifier": "Optional", 19 | "fieldDefault": null, 20 | "fieldType": "string", 21 | "fieldName": "ns", 22 | "fieldAttributes": [ 23 | { 24 | "attrName": [ 25 | "Description" 26 | ], 27 | "attrValue": "Namespace of the metric." 28 | }, 29 | { 30 | "attrName": [ 31 | "MaxStringLength" 32 | ], 33 | "attrValue": "256" 34 | } 35 | ], 36 | "fieldOrdinal": 5 37 | }, 38 | { 39 | "fieldModifier": "Required", 40 | "fieldDefault": null, 41 | "fieldType": "string", 42 | "fieldName": "name", 43 | "fieldAttributes": [ 44 | { 45 | "attrName": [ 46 | "Description" 47 | ], 48 | "attrValue": "Name of the metric." 49 | }, 50 | { 51 | "attrName": [ 52 | "MaxStringLength" 53 | ], 54 | "attrValue": "1024" 55 | } 56 | ], 57 | "fieldOrdinal": 10 58 | }, 59 | { 60 | "fieldModifier": "Optional", 61 | "fieldDefault": { 62 | "value": "Measurement", 63 | "type": "enum" 64 | }, 65 | "fieldType": { 66 | "declaration": { 67 | "tag": "Enum", 68 | "enumConstants": [ 69 | { 70 | "constantValue": null, 71 | "constantName": "Measurement" 72 | }, 73 | { 74 | "constantValue": null, 75 | "constantName": "Aggregation" 76 | } 77 | ], 78 | "declNamespaces": [ 79 | { 80 | "name": [ 81 | "AI" 82 | ] 83 | } 84 | ], 85 | "declName": "DataPointType", 86 | "declAttributes": [ 87 | { 88 | "attrName": [ 89 | "Description" 90 | ], 91 | "attrValue": "Type of the metric data measurement." 92 | } 93 | ] 94 | }, 95 | "type": "user" 96 | }, 97 | "fieldName": "kind", 98 | "fieldAttributes": [ 99 | { 100 | "attrName": [ 101 | "Description" 102 | ], 103 | "attrValue": "Metric type. Single measurement or the aggregated value." 104 | } 105 | ], 106 | "fieldOrdinal": 20 107 | }, 108 | { 109 | "fieldModifier": "Required", 110 | "fieldDefault": null, 111 | "fieldType": "double", 112 | "fieldName": "value", 113 | "fieldAttributes": [ 114 | { 115 | "attrName": [ 116 | "Description" 117 | ], 118 | "attrValue": "Single value for measurement. Sum of individual measurements for the aggregation." 119 | } 120 | ], 121 | "fieldOrdinal": 30 122 | }, 123 | { 124 | "fieldModifier": "Optional", 125 | "fieldDefault": null, 126 | "fieldType": { 127 | "type": "nullable", 128 | "element": "int32" 129 | }, 130 | "fieldName": "count", 131 | "fieldAttributes": [ 132 | { 133 | "attrName": [ 134 | "Description" 135 | ], 136 | "attrValue": "Metric weight of the aggregated metric. Should not be set for a measurement." 137 | } 138 | ], 139 | "fieldOrdinal": 40 140 | }, 141 | { 142 | "fieldModifier": "Optional", 143 | "fieldDefault": null, 144 | "fieldType": { 145 | "type": "nullable", 146 | "element": "double" 147 | }, 148 | "fieldName": "min", 149 | "fieldAttributes": [ 150 | { 151 | "attrName": [ 152 | "Description" 153 | ], 154 | "attrValue": "Minimum value of the aggregated metric. Should not be set for a measurement." 155 | } 156 | ], 157 | "fieldOrdinal": 50 158 | }, 159 | { 160 | "fieldModifier": "Optional", 161 | "fieldDefault": null, 162 | "fieldType": { 163 | "type": "nullable", 164 | "element": "double" 165 | }, 166 | "fieldName": "max", 167 | "fieldAttributes": [ 168 | { 169 | "attrName": [ 170 | "Description" 171 | ], 172 | "attrValue": "Maximum value of the aggregated metric. Should not be set for a measurement." 173 | } 174 | ], 175 | "fieldOrdinal": 60 176 | }, 177 | { 178 | "fieldModifier": "Optional", 179 | "fieldDefault": null, 180 | "fieldType": { 181 | "type": "nullable", 182 | "element": "double" 183 | }, 184 | "fieldName": "stdDev", 185 | "fieldAttributes": [ 186 | { 187 | "attrName": [ 188 | "Description" 189 | ], 190 | "attrValue": "Standard deviation of the aggregated metric. Should not be set for a measurement." 191 | } 192 | ], 193 | "fieldOrdinal": 70 194 | } 195 | ], 196 | "declParams": [], 197 | "declNamespaces": [ 198 | { 199 | "name": [ 200 | "AI" 201 | ] 202 | } 203 | ], 204 | "declName": "DataPoint", 205 | "declAttributes": [ 206 | { 207 | "attrName": [ 208 | "Description" 209 | ], 210 | "attrValue": "Metric data single measurement." 211 | } 212 | ] 213 | } 214 | ] 215 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/DataPointType.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [], 10 | "declarations": [ 11 | { 12 | "tag": "Enum", 13 | "enumConstants": [ 14 | { 15 | "constantValue": null, 16 | "constantName": "Measurement" 17 | }, 18 | { 19 | "constantValue": null, 20 | "constantName": "Aggregation" 21 | } 22 | ], 23 | "declNamespaces": [ 24 | { 25 | "name": [ 26 | "AI" 27 | ] 28 | } 29 | ], 30 | "declName": "DataPointType", 31 | "declAttributes": [ 32 | { 33 | "attrName": [ 34 | "Description" 35 | ], 36 | "attrValue": "Type of the metric data measurement." 37 | } 38 | ] 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/Domain.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [], 10 | "declarations": [ 11 | { 12 | "structBase": null, 13 | "tag": "Struct", 14 | "structFields": [], 15 | "declParams": [], 16 | "declNamespaces": [ 17 | { 18 | "name": [ 19 | "AI" 20 | ] 21 | } 22 | ], 23 | "declName": "Domain", 24 | "declAttributes": [ 25 | { 26 | "attrName": [ 27 | "Description" 28 | ], 29 | "attrValue": "The abstract common base of all domains." 30 | } 31 | ] 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/EventData.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "Domain.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": { 15 | "declaration": { 16 | "structBase": null, 17 | "tag": "Struct", 18 | "structFields": [], 19 | "declParams": [], 20 | "declNamespaces": [ 21 | { 22 | "name": [ 23 | "AI" 24 | ] 25 | } 26 | ], 27 | "declName": "Domain", 28 | "declAttributes": [ 29 | { 30 | "attrName": [ 31 | "Description" 32 | ], 33 | "attrValue": "The abstract common base of all domains." 34 | } 35 | ] 36 | }, 37 | "type": "user" 38 | }, 39 | "tag": "Struct", 40 | "structFields": [ 41 | { 42 | "fieldModifier": "Required", 43 | "fieldDefault": { 44 | "value": 2, 45 | "type": "integer" 46 | }, 47 | "fieldType": "int32", 48 | "fieldName": "ver", 49 | "fieldAttributes": [ 50 | { 51 | "attrName": [ 52 | "Description" 53 | ], 54 | "attrValue": "Schema version" 55 | } 56 | ], 57 | "fieldOrdinal": 10 58 | }, 59 | { 60 | "fieldModifier": "Required", 61 | "fieldDefault": null, 62 | "fieldType": "string", 63 | "fieldName": "name", 64 | "fieldAttributes": [ 65 | { 66 | "attrName": [ 67 | "MaxStringLength" 68 | ], 69 | "attrValue": "512" 70 | }, 71 | { 72 | "attrName": [ 73 | "Description" 74 | ], 75 | "attrValue": "Event name. Keep it low cardinality to allow proper grouping and useful metrics." 76 | }, 77 | { 78 | "attrName": [ 79 | "Question" 80 | ], 81 | "attrValue": "Why Custom Event name is shorter than Request name or dependency name?" 82 | } 83 | ], 84 | "fieldOrdinal": 20 85 | }, 86 | { 87 | "fieldModifier": "Optional", 88 | "fieldDefault": null, 89 | "fieldType": { 90 | "key": "string", 91 | "type": "map", 92 | "element": "string" 93 | }, 94 | "fieldName": "properties", 95 | "fieldAttributes": [ 96 | { 97 | "attrName": [ 98 | "Description" 99 | ], 100 | "attrValue": "Collection of custom properties." 101 | }, 102 | { 103 | "attrName": [ 104 | "MaxKeyLength" 105 | ], 106 | "attrValue": "150" 107 | }, 108 | { 109 | "attrName": [ 110 | "MaxValueLength" 111 | ], 112 | "attrValue": "8192" 113 | } 114 | ], 115 | "fieldOrdinal": 100 116 | }, 117 | { 118 | "fieldModifier": "Optional", 119 | "fieldDefault": null, 120 | "fieldType": { 121 | "key": "string", 122 | "type": "map", 123 | "element": "double" 124 | }, 125 | "fieldName": "measurements", 126 | "fieldAttributes": [ 127 | { 128 | "attrName": [ 129 | "Description" 130 | ], 131 | "attrValue": "Collection of custom measurements." 132 | }, 133 | { 134 | "attrName": [ 135 | "MaxKeyLength" 136 | ], 137 | "attrValue": "150" 138 | } 139 | ], 140 | "fieldOrdinal": 200 141 | } 142 | ], 143 | "declParams": [], 144 | "declNamespaces": [ 145 | { 146 | "name": [ 147 | "AI" 148 | ] 149 | } 150 | ], 151 | "declName": "EventData", 152 | "declAttributes": [ 153 | { 154 | "attrName": [ 155 | "Description" 156 | ], 157 | "attrValue": "Instances of Event represent structured event records that can be grouped and searched by their properties. Event data item also creates a metric of event count by name." 158 | } 159 | ] 160 | } 161 | ] 162 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/ExceptionDetails.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "StackFrame.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": null, 15 | "tag": "Struct", 16 | "structFields": [ 17 | { 18 | "fieldModifier": "Optional", 19 | "fieldDefault": null, 20 | "fieldType": "int32", 21 | "fieldName": "id", 22 | "fieldAttributes": [ 23 | { 24 | "attrName": [ 25 | "Description" 26 | ], 27 | "attrValue": "In case exception is nested (outer exception contains inner one), the id and outerId properties are used to represent the nesting." 28 | } 29 | ], 30 | "fieldOrdinal": 10 31 | }, 32 | { 33 | "fieldModifier": "Optional", 34 | "fieldDefault": null, 35 | "fieldType": "int32", 36 | "fieldName": "outerId", 37 | "fieldAttributes": [ 38 | { 39 | "attrName": [ 40 | "Description" 41 | ], 42 | "attrValue": "The value of outerId is a reference to an element in ExceptionDetails that represents the outer exception" 43 | } 44 | ], 45 | "fieldOrdinal": 20 46 | }, 47 | { 48 | "fieldModifier": "Required", 49 | "fieldDefault": null, 50 | "fieldType": "string", 51 | "fieldName": "typeName", 52 | "fieldAttributes": [ 53 | { 54 | "attrName": [ 55 | "Description" 56 | ], 57 | "attrValue": "Exception type name." 58 | }, 59 | { 60 | "attrName": [ 61 | "MaxStringLength" 62 | ], 63 | "attrValue": "1024" 64 | } 65 | ], 66 | "fieldOrdinal": 30 67 | }, 68 | { 69 | "fieldModifier": "Required", 70 | "fieldDefault": null, 71 | "fieldType": "string", 72 | "fieldName": "message", 73 | "fieldAttributes": [ 74 | { 75 | "attrName": [ 76 | "Description" 77 | ], 78 | "attrValue": "Exception message." 79 | }, 80 | { 81 | "attrName": [ 82 | "MaxStringLength" 83 | ], 84 | "attrValue": "32768" 85 | } 86 | ], 87 | "fieldOrdinal": 40 88 | }, 89 | { 90 | "fieldModifier": "Optional", 91 | "fieldDefault": { 92 | "value": true, 93 | "type": "bool" 94 | }, 95 | "fieldType": "bool", 96 | "fieldName": "hasFullStack", 97 | "fieldAttributes": [ 98 | { 99 | "attrName": [ 100 | "Description" 101 | ], 102 | "attrValue": "Indicates if full exception stack is provided in the exception. The stack may be trimmed, such as in the case of a StackOverflow exception." 103 | } 104 | ], 105 | "fieldOrdinal": 50 106 | }, 107 | { 108 | "fieldModifier": "Optional", 109 | "fieldDefault": null, 110 | "fieldType": "string", 111 | "fieldName": "stack", 112 | "fieldAttributes": [ 113 | { 114 | "attrName": [ 115 | "Description" 116 | ], 117 | "attrValue": "Text describing the stack. Either stack or parsedStack should have a value." 118 | }, 119 | { 120 | "attrName": [ 121 | "MaxStringLength" 122 | ], 123 | "attrValue": "32768" 124 | } 125 | ], 126 | "fieldOrdinal": 60 127 | }, 128 | { 129 | "fieldModifier": "Optional", 130 | "fieldDefault": null, 131 | "fieldType": { 132 | "type": "vector", 133 | "element": { 134 | "declaration": { 135 | "structBase": null, 136 | "tag": "Struct", 137 | "structFields": [ 138 | { 139 | "fieldModifier": "Required", 140 | "fieldDefault": null, 141 | "fieldType": "int32", 142 | "fieldName": "level", 143 | "fieldAttributes": [ 144 | { 145 | "attrName": [ 146 | "Description" 147 | ], 148 | "attrValue": "Level in the call stack. For the long stacks SDK may not report every function in a call stack." 149 | } 150 | ], 151 | "fieldOrdinal": 10 152 | }, 153 | { 154 | "fieldModifier": "Required", 155 | "fieldDefault": null, 156 | "fieldType": "string", 157 | "fieldName": "method", 158 | "fieldAttributes": [ 159 | { 160 | "attrName": [ 161 | "Description" 162 | ], 163 | "attrValue": "Method name." 164 | }, 165 | { 166 | "attrName": [ 167 | "MaxStringLength" 168 | ], 169 | "attrValue": "1024" 170 | } 171 | ], 172 | "fieldOrdinal": 20 173 | }, 174 | { 175 | "fieldModifier": "Optional", 176 | "fieldDefault": null, 177 | "fieldType": "string", 178 | "fieldName": "assembly", 179 | "fieldAttributes": [ 180 | { 181 | "attrName": [ 182 | "Description" 183 | ], 184 | "attrValue": "Name of the assembly (dll, jar, etc.) containing this function." 185 | }, 186 | { 187 | "attrName": [ 188 | "MaxStringLength" 189 | ], 190 | "attrValue": "1024" 191 | } 192 | ], 193 | "fieldOrdinal": 30 194 | }, 195 | { 196 | "fieldModifier": "Optional", 197 | "fieldDefault": null, 198 | "fieldType": "string", 199 | "fieldName": "fileName", 200 | "fieldAttributes": [ 201 | { 202 | "attrName": [ 203 | "Description" 204 | ], 205 | "attrValue": "File name or URL of the method implementation." 206 | }, 207 | { 208 | "attrName": [ 209 | "MaxStringLength" 210 | ], 211 | "attrValue": "1024" 212 | } 213 | ], 214 | "fieldOrdinal": 50 215 | }, 216 | { 217 | "fieldModifier": "Optional", 218 | "fieldDefault": null, 219 | "fieldType": "int32", 220 | "fieldName": "line", 221 | "fieldAttributes": [ 222 | { 223 | "attrName": [ 224 | "Description" 225 | ], 226 | "attrValue": "Line number of the code implementation." 227 | } 228 | ], 229 | "fieldOrdinal": 60 230 | } 231 | ], 232 | "declParams": [], 233 | "declNamespaces": [ 234 | { 235 | "name": [ 236 | "AI" 237 | ] 238 | } 239 | ], 240 | "declName": "StackFrame", 241 | "declAttributes": [ 242 | { 243 | "attrName": [ 244 | "Description" 245 | ], 246 | "attrValue": "Stack frame information." 247 | } 248 | ] 249 | }, 250 | "type": "user" 251 | } 252 | }, 253 | "fieldName": "parsedStack", 254 | "fieldAttributes": [ 255 | { 256 | "attrName": [ 257 | "Description" 258 | ], 259 | "attrValue": "List of stack frames. Either stack or parsedStack should have a value." 260 | } 261 | ], 262 | "fieldOrdinal": 70 263 | } 264 | ], 265 | "declParams": [], 266 | "declNamespaces": [ 267 | { 268 | "name": [ 269 | "AI" 270 | ] 271 | } 272 | ], 273 | "declName": "ExceptionDetails", 274 | "declAttributes": [ 275 | { 276 | "attrName": [ 277 | "Description" 278 | ], 279 | "attrValue": "Exception details of the exception in a chain." 280 | } 281 | ] 282 | } 283 | ] 284 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/MessageData.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "Domain.bond", 11 | "SeverityLevel.bond" 12 | ], 13 | "declarations": [ 14 | { 15 | "structBase": { 16 | "declaration": { 17 | "structBase": null, 18 | "tag": "Struct", 19 | "structFields": [], 20 | "declParams": [], 21 | "declNamespaces": [ 22 | { 23 | "name": [ 24 | "AI" 25 | ] 26 | } 27 | ], 28 | "declName": "Domain", 29 | "declAttributes": [ 30 | { 31 | "attrName": [ 32 | "Description" 33 | ], 34 | "attrValue": "The abstract common base of all domains." 35 | } 36 | ] 37 | }, 38 | "type": "user" 39 | }, 40 | "tag": "Struct", 41 | "structFields": [ 42 | { 43 | "fieldModifier": "Required", 44 | "fieldDefault": { 45 | "value": 2, 46 | "type": "integer" 47 | }, 48 | "fieldType": "int32", 49 | "fieldName": "ver", 50 | "fieldAttributes": [ 51 | { 52 | "attrName": [ 53 | "Description" 54 | ], 55 | "attrValue": "Schema version" 56 | } 57 | ], 58 | "fieldOrdinal": 10 59 | }, 60 | { 61 | "fieldModifier": "Required", 62 | "fieldDefault": null, 63 | "fieldType": "string", 64 | "fieldName": "message", 65 | "fieldAttributes": [ 66 | { 67 | "attrName": [ 68 | "MaxStringLength" 69 | ], 70 | "attrValue": "32768" 71 | }, 72 | { 73 | "attrName": [ 74 | "Description" 75 | ], 76 | "attrValue": "Trace message" 77 | } 78 | ], 79 | "fieldOrdinal": 20 80 | }, 81 | { 82 | "fieldModifier": "Optional", 83 | "fieldDefault": null, 84 | "fieldType": { 85 | "type": "nullable", 86 | "element": { 87 | "declaration": { 88 | "tag": "Enum", 89 | "enumConstants": [ 90 | { 91 | "constantValue": null, 92 | "constantName": "Verbose" 93 | }, 94 | { 95 | "constantValue": null, 96 | "constantName": "Information" 97 | }, 98 | { 99 | "constantValue": null, 100 | "constantName": "Warning" 101 | }, 102 | { 103 | "constantValue": null, 104 | "constantName": "Error" 105 | }, 106 | { 107 | "constantValue": null, 108 | "constantName": "Critical" 109 | } 110 | ], 111 | "declNamespaces": [ 112 | { 113 | "name": [ 114 | "AI" 115 | ] 116 | } 117 | ], 118 | "declName": "SeverityLevel", 119 | "declAttributes": [ 120 | { 121 | "attrName": [ 122 | "Description" 123 | ], 124 | "attrValue": "Defines the level of severity for the event." 125 | } 126 | ] 127 | }, 128 | "type": "user" 129 | } 130 | }, 131 | "fieldName": "severityLevel", 132 | "fieldAttributes": [ 133 | { 134 | "attrName": [ 135 | "Description" 136 | ], 137 | "attrValue": "Trace severity level." 138 | } 139 | ], 140 | "fieldOrdinal": 30 141 | }, 142 | { 143 | "fieldModifier": "Optional", 144 | "fieldDefault": null, 145 | "fieldType": { 146 | "key": "string", 147 | "type": "map", 148 | "element": "string" 149 | }, 150 | "fieldName": "properties", 151 | "fieldAttributes": [ 152 | { 153 | "attrName": [ 154 | "Description" 155 | ], 156 | "attrValue": "Collection of custom properties." 157 | }, 158 | { 159 | "attrName": [ 160 | "MaxKeyLength" 161 | ], 162 | "attrValue": "150" 163 | }, 164 | { 165 | "attrName": [ 166 | "MaxValueLength" 167 | ], 168 | "attrValue": "8192" 169 | } 170 | ], 171 | "fieldOrdinal": 100 172 | }, 173 | { 174 | "fieldModifier": "Optional", 175 | "fieldDefault": null, 176 | "fieldType": { 177 | "key": "string", 178 | "type": "map", 179 | "element": "double" 180 | }, 181 | "fieldName": "measurements", 182 | "fieldAttributes": [ 183 | { 184 | "attrName": [ 185 | "Description" 186 | ], 187 | "attrValue": "Collection of custom measurements." 188 | }, 189 | { 190 | "attrName": [ 191 | "MaxKeyLength" 192 | ], 193 | "attrValue": "150" 194 | } 195 | ], 196 | "fieldOrdinal": 200 197 | } 198 | ], 199 | "declParams": [], 200 | "declNamespaces": [ 201 | { 202 | "name": [ 203 | "AI" 204 | ] 205 | } 206 | ], 207 | "declName": "MessageData", 208 | "declAttributes": [ 209 | { 210 | "attrName": [ 211 | "Description" 212 | ], 213 | "attrValue": "Instances of Message represent printf-like trace statements that are text-searched. Log4Net, NLog and other text-based log file entries are translated into intances of this type. The message does not have measurements." 214 | } 215 | ] 216 | } 217 | ] 218 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/PageViewData.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "Domain.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": { 15 | "declaration": { 16 | "structBase": null, 17 | "tag": "Struct", 18 | "structFields": [], 19 | "declParams": [], 20 | "declNamespaces": [ 21 | { 22 | "name": [ 23 | "AI" 24 | ] 25 | } 26 | ], 27 | "declName": "Domain", 28 | "declAttributes": [ 29 | { 30 | "attrName": [ 31 | "Description" 32 | ], 33 | "attrValue": "The abstract common base of all domains." 34 | } 35 | ] 36 | }, 37 | "type": "user" 38 | }, 39 | "tag": "Struct", 40 | "structFields": [ 41 | { 42 | "fieldModifier": "Required", 43 | "fieldDefault": { 44 | "value": 2, 45 | "type": "integer" 46 | }, 47 | "fieldType": "int32", 48 | "fieldName": "ver", 49 | "fieldAttributes": [ 50 | { 51 | "attrName": [ 52 | "Description" 53 | ], 54 | "attrValue": "Schema version" 55 | } 56 | ], 57 | "fieldOrdinal": 10 58 | }, 59 | { 60 | "fieldModifier": "Required", 61 | "fieldDefault": null, 62 | "fieldType": "string", 63 | "fieldName": "name", 64 | "fieldAttributes": [ 65 | { 66 | "attrName": [ 67 | "MaxStringLength" 68 | ], 69 | "attrValue": "512" 70 | }, 71 | { 72 | "attrName": [ 73 | "Description" 74 | ], 75 | "attrValue": "Event name. Keep it low cardinality to allow proper grouping and useful metrics." 76 | }, 77 | { 78 | "attrName": [ 79 | "Question" 80 | ], 81 | "attrValue": "Why Custom Event name is shorter than Request name or dependency name?" 82 | } 83 | ], 84 | "fieldOrdinal": 20 85 | }, 86 | { 87 | "fieldModifier": "Optional", 88 | "fieldDefault": null, 89 | "fieldType": "string", 90 | "fieldName": "url", 91 | "fieldAttributes": [ 92 | { 93 | "attrName": [ 94 | "MaxStringLength" 95 | ], 96 | "attrValue": "2048" 97 | }, 98 | { 99 | "attrName": [ 100 | "Description" 101 | ], 102 | "attrValue": "Request URL with all query string parameters" 103 | } 104 | ], 105 | "fieldOrdinal": 30 106 | }, 107 | { 108 | "fieldModifier": "Optional", 109 | "fieldDefault": null, 110 | "fieldType": "string", 111 | "fieldName": "duration", 112 | "fieldAttributes": [ 113 | { 114 | "attrName": [ 115 | "CSType" 116 | ], 117 | "attrValue": "TimeSpan" 118 | }, 119 | { 120 | "attrName": [ 121 | "Description" 122 | ], 123 | "attrValue": "Request duration in format: DD.HH:MM:SS.MMMMMM. For a page view (PageViewData), this is the duration. For a page view with performance information (PageViewPerfData), this is the page load time. Must be less than 1000 days." 124 | } 125 | ], 126 | "fieldOrdinal": 40 127 | }, 128 | { 129 | "fieldModifier": "Optional", 130 | "fieldDefault": null, 131 | "fieldType": "string", 132 | "fieldName": "referrerUri", 133 | "fieldAttributes": [ 134 | { 135 | "attrName": [ 136 | "Description" 137 | ], 138 | "attrValue": "Fully qualified page URI or URL of the referring page; if unknown, leave blank" 139 | }, 140 | { 141 | "attrName": [ 142 | "MaxStringLength" 143 | ], 144 | "attrValue": "2048" 145 | } 146 | ], 147 | "fieldOrdinal": 50 148 | }, 149 | { 150 | "fieldModifier": "Required", 151 | "fieldDefault": null, 152 | "fieldType": "string", 153 | "fieldName": "id", 154 | "fieldAttributes": [ 155 | { 156 | "attrName": [ 157 | "MaxStringLength" 158 | ], 159 | "attrValue": "512" 160 | }, 161 | { 162 | "attrName": [ 163 | "ActAsRequired" 164 | ], 165 | "attrValue": "Required field for correct correlation." 166 | }, 167 | { 168 | "attrName": [ 169 | "Description" 170 | ], 171 | "attrValue": "Identifier of a page view instance. Used for correlation between page view and other telemetry items." 172 | } 173 | ], 174 | "fieldOrdinal": 70 175 | }, 176 | { 177 | "fieldModifier": "Optional", 178 | "fieldDefault": null, 179 | "fieldType": { 180 | "key": "string", 181 | "type": "map", 182 | "element": "string" 183 | }, 184 | "fieldName": "properties", 185 | "fieldAttributes": [ 186 | { 187 | "attrName": [ 188 | "Description" 189 | ], 190 | "attrValue": "Collection of custom properties." 191 | }, 192 | { 193 | "attrName": [ 194 | "MaxKeyLength" 195 | ], 196 | "attrValue": "150" 197 | }, 198 | { 199 | "attrName": [ 200 | "MaxValueLength" 201 | ], 202 | "attrValue": "8192" 203 | } 204 | ], 205 | "fieldOrdinal": 100 206 | }, 207 | { 208 | "fieldModifier": "Optional", 209 | "fieldDefault": null, 210 | "fieldType": { 211 | "key": "string", 212 | "type": "map", 213 | "element": "double" 214 | }, 215 | "fieldName": "measurements", 216 | "fieldAttributes": [ 217 | { 218 | "attrName": [ 219 | "Description" 220 | ], 221 | "attrValue": "Collection of custom measurements." 222 | }, 223 | { 224 | "attrName": [ 225 | "MaxKeyLength" 226 | ], 227 | "attrValue": "150" 228 | } 229 | ], 230 | "fieldOrdinal": 200 231 | } 232 | ], 233 | "declParams": [], 234 | "declNamespaces": [ 235 | { 236 | "name": [ 237 | "AI" 238 | ] 239 | } 240 | ], 241 | "declName": "PageViewData", 242 | "declAttributes": [ 243 | { 244 | "attrName": [ 245 | "Description" 246 | ], 247 | "attrValue": "An instance of PageView represents a generic action on a page like a button click. It is also the base type for PageView." 248 | }, 249 | { 250 | "attrName": [ 251 | "Alias" 252 | ], 253 | "attrValue": "PageviewData;PageEventData" 254 | } 255 | ] 256 | } 257 | ] 258 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/RemoteDependencyData.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "Domain.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": { 15 | "declaration": { 16 | "structBase": null, 17 | "tag": "Struct", 18 | "structFields": [], 19 | "declParams": [], 20 | "declNamespaces": [ 21 | { 22 | "name": [ 23 | "AI" 24 | ] 25 | } 26 | ], 27 | "declName": "Domain", 28 | "declAttributes": [ 29 | { 30 | "attrName": [ 31 | "Description" 32 | ], 33 | "attrValue": "The abstract common base of all domains." 34 | } 35 | ] 36 | }, 37 | "type": "user" 38 | }, 39 | "tag": "Struct", 40 | "structFields": [ 41 | { 42 | "fieldModifier": "Required", 43 | "fieldDefault": { 44 | "value": 2, 45 | "type": "integer" 46 | }, 47 | "fieldType": "int32", 48 | "fieldName": "ver", 49 | "fieldAttributes": [ 50 | { 51 | "attrName": [ 52 | "Description" 53 | ], 54 | "attrValue": "Schema version" 55 | } 56 | ], 57 | "fieldOrdinal": 10 58 | }, 59 | { 60 | "fieldModifier": "Required", 61 | "fieldDefault": null, 62 | "fieldType": "string", 63 | "fieldName": "name", 64 | "fieldAttributes": [ 65 | { 66 | "attrName": [ 67 | "MaxStringLength" 68 | ], 69 | "attrValue": "1024" 70 | }, 71 | { 72 | "attrName": [ 73 | "Description" 74 | ], 75 | "attrValue": "Name of the command initiated with this dependency call. Low cardinality value. Examples are stored procedure name and URL path template." 76 | } 77 | ], 78 | "fieldOrdinal": 20 79 | }, 80 | { 81 | "fieldModifier": "Optional", 82 | "fieldDefault": null, 83 | "fieldType": "string", 84 | "fieldName": "id", 85 | "fieldAttributes": [ 86 | { 87 | "attrName": [ 88 | "MaxStringLength" 89 | ], 90 | "attrValue": "512" 91 | }, 92 | { 93 | "attrName": [ 94 | "Description" 95 | ], 96 | "attrValue": "Identifier of a dependency call instance. Used for correlation with the request telemetry item corresponding to this dependency call." 97 | } 98 | ], 99 | "fieldOrdinal": 30 100 | }, 101 | { 102 | "fieldModifier": "Optional", 103 | "fieldDefault": null, 104 | "fieldType": "string", 105 | "fieldName": "resultCode", 106 | "fieldAttributes": [ 107 | { 108 | "attrName": [ 109 | "MaxStringLength" 110 | ], 111 | "attrValue": "1024" 112 | }, 113 | { 114 | "attrName": [ 115 | "Description" 116 | ], 117 | "attrValue": "Result code of a dependency call. Examples are SQL error code and HTTP status code." 118 | } 119 | ], 120 | "fieldOrdinal": 40 121 | }, 122 | { 123 | "fieldModifier": "Required", 124 | "fieldDefault": null, 125 | "fieldType": "string", 126 | "fieldName": "duration", 127 | "fieldAttributes": [ 128 | { 129 | "attrName": [ 130 | "CSType" 131 | ], 132 | "attrValue": "TimeSpan" 133 | }, 134 | { 135 | "attrName": [ 136 | "Description" 137 | ], 138 | "attrValue": "Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days." 139 | }, 140 | { 141 | "attrName": [ 142 | "ActAsRequired" 143 | ], 144 | "attrValue": "Renaming value to duration." 145 | } 146 | ], 147 | "fieldOrdinal": 61 148 | }, 149 | { 150 | "fieldModifier": "Optional", 151 | "fieldDefault": { 152 | "value": true, 153 | "type": "bool" 154 | }, 155 | "fieldType": "bool", 156 | "fieldName": "success", 157 | "fieldAttributes": [ 158 | { 159 | "attrName": [ 160 | "Description" 161 | ], 162 | "attrValue": "Indication of successfull or unsuccessfull call." 163 | } 164 | ], 165 | "fieldOrdinal": 120 166 | }, 167 | { 168 | "fieldModifier": "Optional", 169 | "fieldDefault": null, 170 | "fieldType": "string", 171 | "fieldName": "data", 172 | "fieldAttributes": [ 173 | { 174 | "attrName": [ 175 | "MaxStringLength" 176 | ], 177 | "attrValue": "8192" 178 | }, 179 | { 180 | "attrName": [ 181 | "Description" 182 | ], 183 | "attrValue": "Command initiated by this dependency call. Examples are SQL statement and HTTP URL's with all query parameters." 184 | } 185 | ], 186 | "fieldOrdinal": 151 187 | }, 188 | { 189 | "fieldModifier": "Optional", 190 | "fieldDefault": null, 191 | "fieldType": "string", 192 | "fieldName": "target", 193 | "fieldAttributes": [ 194 | { 195 | "attrName": [ 196 | "MaxStringLength" 197 | ], 198 | "attrValue": "1024" 199 | }, 200 | { 201 | "attrName": [ 202 | "Description" 203 | ], 204 | "attrValue": "Target site of a dependency call. Examples are server name, host address." 205 | } 206 | ], 207 | "fieldOrdinal": 161 208 | }, 209 | { 210 | "fieldModifier": "Optional", 211 | "fieldDefault": null, 212 | "fieldType": "string", 213 | "fieldName": "type", 214 | "fieldAttributes": [ 215 | { 216 | "attrName": [ 217 | "MaxStringLength" 218 | ], 219 | "attrValue": "1024" 220 | }, 221 | { 222 | "attrName": [ 223 | "Description" 224 | ], 225 | "attrValue": "Dependency type name. Very low cardinality value for logical grouping of dependencies and interpretation of other fields like commandName and resultCode. Examples are SQL, Azure table, and HTTP." 226 | } 227 | ], 228 | "fieldOrdinal": 162 229 | }, 230 | { 231 | "fieldModifier": "Optional", 232 | "fieldDefault": null, 233 | "fieldType": { 234 | "key": "string", 235 | "type": "map", 236 | "element": "string" 237 | }, 238 | "fieldName": "properties", 239 | "fieldAttributes": [ 240 | { 241 | "attrName": [ 242 | "Description" 243 | ], 244 | "attrValue": "Collection of custom properties." 245 | }, 246 | { 247 | "attrName": [ 248 | "MaxKeyLength" 249 | ], 250 | "attrValue": "150" 251 | }, 252 | { 253 | "attrName": [ 254 | "MaxValueLength" 255 | ], 256 | "attrValue": "8192" 257 | } 258 | ], 259 | "fieldOrdinal": 200 260 | }, 261 | { 262 | "fieldModifier": "Optional", 263 | "fieldDefault": null, 264 | "fieldType": { 265 | "key": "string", 266 | "type": "map", 267 | "element": "double" 268 | }, 269 | "fieldName": "measurements", 270 | "fieldAttributes": [ 271 | { 272 | "attrName": [ 273 | "Description" 274 | ], 275 | "attrValue": "Collection of custom measurements." 276 | }, 277 | { 278 | "attrName": [ 279 | "MaxKeyLength" 280 | ], 281 | "attrValue": "150" 282 | } 283 | ], 284 | "fieldOrdinal": 300 285 | } 286 | ], 287 | "declParams": [], 288 | "declNamespaces": [ 289 | { 290 | "name": [ 291 | "AI" 292 | ] 293 | } 294 | ], 295 | "declName": "RemoteDependencyData", 296 | "declAttributes": [ 297 | { 298 | "attrName": [ 299 | "Description" 300 | ], 301 | "attrValue": "An instance of Remote Dependency represents an interaction of the monitored component with a remote component/service like SQL or an HTTP endpoint." 302 | } 303 | ] 304 | } 305 | ] 306 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/RequestData.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [ 10 | "Domain.bond" 11 | ], 12 | "declarations": [ 13 | { 14 | "structBase": { 15 | "declaration": { 16 | "structBase": null, 17 | "tag": "Struct", 18 | "structFields": [], 19 | "declParams": [], 20 | "declNamespaces": [ 21 | { 22 | "name": [ 23 | "AI" 24 | ] 25 | } 26 | ], 27 | "declName": "Domain", 28 | "declAttributes": [ 29 | { 30 | "attrName": [ 31 | "Description" 32 | ], 33 | "attrValue": "The abstract common base of all domains." 34 | } 35 | ] 36 | }, 37 | "type": "user" 38 | }, 39 | "tag": "Struct", 40 | "structFields": [ 41 | { 42 | "fieldModifier": "Required", 43 | "fieldDefault": { 44 | "value": 2, 45 | "type": "integer" 46 | }, 47 | "fieldType": "int32", 48 | "fieldName": "ver", 49 | "fieldAttributes": [ 50 | { 51 | "attrName": [ 52 | "Description" 53 | ], 54 | "attrValue": "Schema version" 55 | } 56 | ], 57 | "fieldOrdinal": 10 58 | }, 59 | { 60 | "fieldModifier": "Required", 61 | "fieldDefault": null, 62 | "fieldType": "string", 63 | "fieldName": "id", 64 | "fieldAttributes": [ 65 | { 66 | "attrName": [ 67 | "MaxStringLength" 68 | ], 69 | "attrValue": "512" 70 | }, 71 | { 72 | "attrName": [ 73 | "Description" 74 | ], 75 | "attrValue": "Identifier of a request call instance. Used for correlation between request and other telemetry items." 76 | } 77 | ], 78 | "fieldOrdinal": 20 79 | }, 80 | { 81 | "fieldModifier": "Optional", 82 | "fieldDefault": null, 83 | "fieldType": "string", 84 | "fieldName": "source", 85 | "fieldAttributes": [ 86 | { 87 | "attrName": [ 88 | "MaxStringLength" 89 | ], 90 | "attrValue": "1024" 91 | }, 92 | { 93 | "attrName": [ 94 | "Description" 95 | ], 96 | "attrValue": "Source of the request. Examples are the instrumentation key of the caller or the ip address of the caller." 97 | } 98 | ], 99 | "fieldOrdinal": 29 100 | }, 101 | { 102 | "fieldModifier": "Optional", 103 | "fieldDefault": null, 104 | "fieldType": "string", 105 | "fieldName": "name", 106 | "fieldAttributes": [ 107 | { 108 | "attrName": [ 109 | "MaxStringLength" 110 | ], 111 | "attrValue": "1024" 112 | }, 113 | { 114 | "attrName": [ 115 | "Description" 116 | ], 117 | "attrValue": "Name of the request. Represents code path taken to process request. Low cardinality value to allow better grouping of requests. For HTTP requests it represents the HTTP method and URL path template like 'GET /values/{id}'." 118 | } 119 | ], 120 | "fieldOrdinal": 30 121 | }, 122 | { 123 | "fieldModifier": "Required", 124 | "fieldDefault": null, 125 | "fieldType": "string", 126 | "fieldName": "duration", 127 | "fieldAttributes": [ 128 | { 129 | "attrName": [ 130 | "CSType" 131 | ], 132 | "attrValue": "TimeSpan" 133 | }, 134 | { 135 | "attrName": [ 136 | "Description" 137 | ], 138 | "attrValue": "Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days." 139 | } 140 | ], 141 | "fieldOrdinal": 50 142 | }, 143 | { 144 | "fieldModifier": "Required", 145 | "fieldDefault": null, 146 | "fieldType": "string", 147 | "fieldName": "responseCode", 148 | "fieldAttributes": [ 149 | { 150 | "attrName": [ 151 | "MaxStringLength" 152 | ], 153 | "attrValue": "1024" 154 | }, 155 | { 156 | "attrName": [ 157 | "Description" 158 | ], 159 | "attrValue": "Result of a request execution. HTTP status code for HTTP requests." 160 | } 161 | ], 162 | "fieldOrdinal": 60 163 | }, 164 | { 165 | "fieldModifier": "Required", 166 | "fieldDefault": { 167 | "value": true, 168 | "type": "bool" 169 | }, 170 | "fieldType": "bool", 171 | "fieldName": "success", 172 | "fieldAttributes": [ 173 | { 174 | "attrName": [ 175 | "Description" 176 | ], 177 | "attrValue": "Indication of successfull or unsuccessfull call." 178 | } 179 | ], 180 | "fieldOrdinal": 70 181 | }, 182 | { 183 | "fieldModifier": "Optional", 184 | "fieldDefault": null, 185 | "fieldType": "string", 186 | "fieldName": "url", 187 | "fieldAttributes": [ 188 | { 189 | "attrName": [ 190 | "MaxStringLength" 191 | ], 192 | "attrValue": "2048" 193 | }, 194 | { 195 | "attrName": [ 196 | "Description" 197 | ], 198 | "attrValue": "Request URL with all query string parameters." 199 | } 200 | ], 201 | "fieldOrdinal": 90 202 | }, 203 | { 204 | "fieldModifier": "Optional", 205 | "fieldDefault": null, 206 | "fieldType": { 207 | "key": "string", 208 | "type": "map", 209 | "element": "string" 210 | }, 211 | "fieldName": "properties", 212 | "fieldAttributes": [ 213 | { 214 | "attrName": [ 215 | "Description" 216 | ], 217 | "attrValue": "Collection of custom properties." 218 | }, 219 | { 220 | "attrName": [ 221 | "MaxKeyLength" 222 | ], 223 | "attrValue": "150" 224 | }, 225 | { 226 | "attrName": [ 227 | "MaxValueLength" 228 | ], 229 | "attrValue": "8192" 230 | } 231 | ], 232 | "fieldOrdinal": 100 233 | }, 234 | { 235 | "fieldModifier": "Optional", 236 | "fieldDefault": null, 237 | "fieldType": { 238 | "key": "string", 239 | "type": "map", 240 | "element": "double" 241 | }, 242 | "fieldName": "measurements", 243 | "fieldAttributes": [ 244 | { 245 | "attrName": [ 246 | "Description" 247 | ], 248 | "attrValue": "Collection of custom measurements." 249 | }, 250 | { 251 | "attrName": [ 252 | "MaxKeyLength" 253 | ], 254 | "attrValue": "150" 255 | } 256 | ], 257 | "fieldOrdinal": 200 258 | } 259 | ], 260 | "declParams": [], 261 | "declNamespaces": [ 262 | { 263 | "name": [ 264 | "AI" 265 | ] 266 | } 267 | ], 268 | "declName": "RequestData", 269 | "declAttributes": [ 270 | { 271 | "attrName": [ 272 | "Description" 273 | ], 274 | "attrValue": "An instance of Request represents completion of an external request to the application to do work and contains a summary of that request execution and the results." 275 | } 276 | ] 277 | } 278 | ] 279 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/SeverityLevel.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [], 10 | "declarations": [ 11 | { 12 | "tag": "Enum", 13 | "enumConstants": [ 14 | { 15 | "constantValue": null, 16 | "constantName": "Verbose" 17 | }, 18 | { 19 | "constantValue": null, 20 | "constantName": "Information" 21 | }, 22 | { 23 | "constantValue": null, 24 | "constantName": "Warning" 25 | }, 26 | { 27 | "constantValue": null, 28 | "constantName": "Error" 29 | }, 30 | { 31 | "constantValue": null, 32 | "constantName": "Critical" 33 | } 34 | ], 35 | "declNamespaces": [ 36 | { 37 | "name": [ 38 | "AI" 39 | ] 40 | } 41 | ], 42 | "declName": "SeverityLevel", 43 | "declAttributes": [ 44 | { 45 | "attrName": [ 46 | "Description" 47 | ], 48 | "attrValue": "Defines the level of severity for the event." 49 | } 50 | ] 51 | } 52 | ] 53 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/schema/StackFrame.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespaces": [ 3 | { 4 | "name": [ 5 | "AI" 6 | ] 7 | } 8 | ], 9 | "imports": [], 10 | "declarations": [ 11 | { 12 | "structBase": null, 13 | "tag": "Struct", 14 | "structFields": [ 15 | { 16 | "fieldModifier": "Required", 17 | "fieldDefault": null, 18 | "fieldType": "int32", 19 | "fieldName": "level", 20 | "fieldAttributes": [ 21 | { 22 | "attrName": [ 23 | "Description" 24 | ], 25 | "attrValue": "Level in the call stack. For the long stacks SDK may not report every function in a call stack." 26 | } 27 | ], 28 | "fieldOrdinal": 10 29 | }, 30 | { 31 | "fieldModifier": "Required", 32 | "fieldDefault": null, 33 | "fieldType": "string", 34 | "fieldName": "method", 35 | "fieldAttributes": [ 36 | { 37 | "attrName": [ 38 | "Description" 39 | ], 40 | "attrValue": "Method name." 41 | }, 42 | { 43 | "attrName": [ 44 | "MaxStringLength" 45 | ], 46 | "attrValue": "1024" 47 | } 48 | ], 49 | "fieldOrdinal": 20 50 | }, 51 | { 52 | "fieldModifier": "Optional", 53 | "fieldDefault": null, 54 | "fieldType": "string", 55 | "fieldName": "assembly", 56 | "fieldAttributes": [ 57 | { 58 | "attrName": [ 59 | "Description" 60 | ], 61 | "attrValue": "Name of the assembly (dll, jar, etc.) containing this function." 62 | }, 63 | { 64 | "attrName": [ 65 | "MaxStringLength" 66 | ], 67 | "attrValue": "1024" 68 | } 69 | ], 70 | "fieldOrdinal": 30 71 | }, 72 | { 73 | "fieldModifier": "Optional", 74 | "fieldDefault": null, 75 | "fieldType": "string", 76 | "fieldName": "fileName", 77 | "fieldAttributes": [ 78 | { 79 | "attrName": [ 80 | "Description" 81 | ], 82 | "attrValue": "File name or URL of the method implementation." 83 | }, 84 | { 85 | "attrName": [ 86 | "MaxStringLength" 87 | ], 88 | "attrValue": "1024" 89 | } 90 | ], 91 | "fieldOrdinal": 50 92 | }, 93 | { 94 | "fieldModifier": "Optional", 95 | "fieldDefault": null, 96 | "fieldType": "int32", 97 | "fieldName": "line", 98 | "fieldAttributes": [ 99 | { 100 | "attrName": [ 101 | "Description" 102 | ], 103 | "attrValue": "Line number of the code implementation." 104 | } 105 | ], 106 | "fieldOrdinal": 60 107 | } 108 | ], 109 | "declParams": [], 110 | "declNamespaces": [ 111 | { 112 | "name": [ 113 | "AI" 114 | ] 115 | } 116 | ], 117 | "declName": "StackFrame", 118 | "declAttributes": [ 119 | { 120 | "attrName": [ 121 | "Description" 122 | ], 123 | "attrValue": "Stack frame information." 124 | } 125 | ] 126 | } 127 | ] 128 | } -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/attributes.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize, Debug, Clone)] 4 | #[serde(rename_all = "camelCase")] 5 | #[serde(deny_unknown_fields)] 6 | pub struct Attribute { 7 | attr_name: Vec, 8 | attr_value: String, 9 | } 10 | 11 | impl Attribute { 12 | pub fn names(&self) -> &Vec { 13 | &self.attr_name 14 | } 15 | 16 | pub fn value(&self) -> &str { 17 | &self.attr_value 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/enums.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::ast::{Attribute, Namespace}; 4 | 5 | #[derive(Serialize, Deserialize, Debug, Clone)] 6 | #[serde(rename_all = "camelCase")] 7 | #[serde(deny_unknown_fields)] 8 | pub struct Enum { 9 | enum_constants: Vec, 10 | decl_name: String, 11 | decl_attributes: Vec, 12 | decl_namespaces: Vec, 13 | } 14 | 15 | impl Enum { 16 | pub fn name(&self) -> &str { 17 | &self.decl_name 18 | } 19 | 20 | pub fn constants(&self) -> &Vec { 21 | &self.enum_constants 22 | } 23 | 24 | pub fn attributes(&self) -> &Vec { 25 | &self.decl_attributes 26 | } 27 | 28 | pub fn namespaces(&self) -> &Vec { 29 | &self.decl_namespaces 30 | } 31 | } 32 | 33 | #[derive(Serialize, Deserialize, Debug, Clone)] 34 | #[serde(rename_all = "camelCase")] 35 | #[serde(deny_unknown_fields)] 36 | pub struct EnumConstant { 37 | constant_value: Option, 38 | constant_name: String, 39 | } 40 | 41 | impl EnumConstant { 42 | pub fn name(&self) -> &str { 43 | &self.constant_name 44 | } 45 | 46 | pub fn value(&self) -> Option<&String> { 47 | self.constant_value.as_ref() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/fields.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use heck::ToSnakeCase; 4 | 5 | use crate::ast::{Attribute, Type}; 6 | 7 | #[derive(Serialize, Deserialize, Debug, Clone)] 8 | #[serde(rename_all = "camelCase")] 9 | #[serde(deny_unknown_fields)] 10 | pub struct Field { 11 | field_modifier: FieldModifier, 12 | field_default: Option, 13 | field_type: Type, 14 | field_name: String, 15 | field_attributes: Vec, 16 | field_ordinal: i32, 17 | } 18 | 19 | impl Field { 20 | pub fn is_required(&self) -> bool { 21 | self.field_modifier == FieldModifier::Required 22 | } 23 | 24 | pub fn type_(&self) -> &Type { 25 | &self.field_type 26 | } 27 | 28 | pub fn name(&self) -> String { 29 | let name = self.field_name.to_snake_case(); 30 | if RUST_KEYWORDS.contains(&name.as_str()) { 31 | format!("{}_", name) 32 | } else { 33 | name 34 | } 35 | } 36 | 37 | pub fn attributes(&self) -> &Vec { 38 | &self.field_attributes 39 | } 40 | 41 | pub fn default_value(&self) -> Option { 42 | match (&self.field_default, self.field_type.enum_()) { 43 | (Some(FieldDefault::Integer { value }), None) => Some(format!("{}", value)), 44 | (Some(FieldDefault::Float { value }), None) => Some(format!("{}.0", value)), 45 | (Some(FieldDefault::Bool { value }), None) => Some(format!("{}", value)), 46 | (Some(FieldDefault::String { value }), None) => Some(format!("String::from(\"{}\")", value)), 47 | (Some(FieldDefault::Enum { value }), Some(name)) => Some(format!("{}::{}", name, value)), 48 | (_, Some(_)) => panic!("Unsupported operation"), 49 | _ => None, 50 | } 51 | } 52 | 53 | pub fn optional(&self) -> Option<&Type> { 54 | if let Some(type_) = self.field_type.nullable() { 55 | Some(Field::unwrap_option(type_)) 56 | } else if self.field_modifier == FieldModifier::Optional { 57 | Some(Field::unwrap_option(&self.field_type)) 58 | } else { 59 | None 60 | } 61 | } 62 | 63 | fn unwrap_option(type_: &Type) -> &Type { 64 | type_.nullable().map_or(type_, |type_| Field::unwrap_option(type_)) 65 | } 66 | } 67 | 68 | const RUST_KEYWORDS: [&str; 1] = ["type"]; 69 | 70 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 71 | #[serde(deny_unknown_fields)] 72 | pub enum FieldModifier { 73 | Optional, 74 | Required, 75 | } 76 | 77 | #[derive(Serialize, Deserialize, Debug, Clone)] 78 | #[serde(rename_all = "camelCase")] 79 | #[serde(tag = "type")] 80 | #[serde(deny_unknown_fields)] 81 | pub enum FieldDefault { 82 | Integer { value: i32 }, 83 | Float { value: f32 }, 84 | Bool { value: bool }, 85 | String { value: String }, 86 | Enum { value: String }, 87 | } 88 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | mod attributes; 2 | mod enums; 3 | mod fields; 4 | mod namespaces; 5 | mod schemas; 6 | mod structs; 7 | mod types; 8 | 9 | pub use attributes::*; 10 | pub use enums::*; 11 | pub use fields::*; 12 | pub use namespaces::*; 13 | pub use schemas::*; 14 | pub use structs::*; 15 | pub use types::*; 16 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/namespaces.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize, Debug, Clone)] 4 | #[serde(rename_all = "camelCase")] 5 | #[serde(deny_unknown_fields)] 6 | pub struct Namespace { 7 | name: Vec, 8 | } 9 | 10 | impl Namespace { 11 | pub fn names(&self) -> &Vec { 12 | &self.name 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/schemas.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::ast::{Namespace, UserType}; 4 | 5 | #[derive(Serialize, Deserialize, Debug, Clone)] 6 | #[serde(rename_all = "camelCase")] 7 | #[serde(deny_unknown_fields)] 8 | pub struct Schema { 9 | namespaces: Vec, 10 | imports: Vec, 11 | declarations: Vec, 12 | } 13 | 14 | impl Schema { 15 | pub fn imports(&self) -> &Vec { 16 | &self.imports 17 | } 18 | 19 | pub fn namespaces(&self) -> &Vec { 20 | &self.namespaces 21 | } 22 | 23 | pub fn declarations(&self) -> &Vec { 24 | &self.declarations 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/structs.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::ast::{Attribute, Field, Namespace, Parameter, Type}; 4 | 5 | #[derive(Serialize, Deserialize, Debug, Clone)] 6 | #[serde(rename_all = "camelCase")] 7 | #[serde(deny_unknown_fields)] 8 | pub struct Struct { 9 | struct_base: Option, 10 | struct_fields: Vec, 11 | decl_namespaces: Vec, 12 | decl_params: Vec, 13 | decl_name: String, 14 | decl_attributes: Vec, 15 | } 16 | 17 | impl Struct { 18 | pub fn base(&self) -> &Option { 19 | &self.struct_base 20 | } 21 | 22 | pub fn fields(&self) -> &Vec { 23 | &self.struct_fields 24 | } 25 | 26 | pub fn namespaces(&self) -> &Vec { 27 | &self.decl_namespaces 28 | } 29 | 30 | pub fn params(&self) -> &Vec { 31 | &self.decl_params 32 | } 33 | 34 | pub fn name(&self) -> &str { 35 | &self.decl_name 36 | } 37 | 38 | pub fn attributes(&self) -> &Vec { 39 | &self.decl_attributes 40 | } 41 | 42 | pub fn is_telemetry_data(&self) -> bool { 43 | self.name().ends_with("Data") && self.name().len() > 4 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/ast/types.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::ast::{Enum, Struct}; 6 | 7 | #[derive(Serialize, Deserialize, Debug, Clone)] 8 | #[serde(rename_all = "lowercase")] 9 | #[serde(untagged)] 10 | #[serde(deny_unknown_fields)] 11 | pub enum Type { 12 | Basic(BasicType), 13 | Complex(ComplexType), 14 | } 15 | 16 | impl Type { 17 | pub fn nullable(&self) -> Option<&Type> { 18 | if let Type::Complex(ComplexType::Nullable { element }) = &self { 19 | Some(element) 20 | } else { 21 | None 22 | } 23 | } 24 | 25 | pub fn generic(&self) -> Option<&str> { 26 | if let Type::Complex(ComplexType::Parameter { value }) = &self { 27 | Some(&value.param_name) 28 | } else { 29 | None 30 | } 31 | } 32 | 33 | pub fn enum_(&self) -> Option<&str> { 34 | if let Type::Complex(ComplexType::User { declaration }) = &self { 35 | if let UserType::Enum(enum_) = &**declaration { 36 | Some(enum_.name()) 37 | } else { 38 | None 39 | } 40 | } else { 41 | None 42 | } 43 | } 44 | } 45 | 46 | impl FromStr for Type { 47 | type Err = String; 48 | 49 | fn from_str(s: &str) -> Result { 50 | match s.to_lowercase().as_str() { 51 | "double" => Ok(Self::Basic(BasicType::Double)), 52 | "string" => Ok(Self::Basic(BasicType::String)), 53 | _ => Err(format!("Unsupported type: {}", s)), 54 | } 55 | } 56 | } 57 | 58 | #[derive(Serialize, Deserialize, Debug, Clone)] 59 | #[serde(rename_all = "lowercase")] 60 | #[serde(deny_unknown_fields)] 61 | pub enum BasicType { 62 | Bool, 63 | UInt8, 64 | UInt16, 65 | UInt32, 66 | UInt64, 67 | Int8, 68 | Int16, 69 | Int32, 70 | Int64, 71 | Float, 72 | Double, 73 | String, 74 | WString, 75 | } 76 | 77 | #[derive(Serialize, Deserialize, Debug, Clone)] 78 | #[serde(rename_all = "lowercase")] 79 | #[serde(tag = "type")] 80 | #[serde(deny_unknown_fields)] 81 | pub enum ComplexType { 82 | Map { key: String, element: String }, 83 | Parameter { value: Parameter }, 84 | Vector { element: Box }, 85 | Nullable { element: Box }, 86 | User { declaration: Box }, 87 | } 88 | 89 | #[derive(Serialize, Deserialize, Debug, Clone)] 90 | #[serde(tag = "tag")] 91 | #[serde(deny_unknown_fields)] 92 | pub enum UserType { 93 | Struct(Struct), 94 | Enum(Enum), 95 | } 96 | 97 | #[derive(Serialize, Deserialize, Debug, Clone)] 98 | #[serde(rename_all = "camelCase")] 99 | #[serde(deny_unknown_fields)] 100 | pub struct Parameter { 101 | param_constraint: Option, 102 | param_name: String, 103 | } 104 | 105 | impl Parameter { 106 | pub fn constraint(&self) -> &Option { 107 | &self.param_constraint 108 | } 109 | 110 | pub fn name(&self) -> &str { 111 | &self.param_name 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/generator/enums.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{Attribute, EnumConstant}; 2 | use crate::compiler::Visitor; 3 | 4 | pub struct EnumGenerator { 5 | declaration: codegen::Enum, 6 | } 7 | 8 | impl EnumGenerator { 9 | pub fn new(name: &str) -> Self { 10 | let mut declaration = codegen::Enum::new(name); 11 | declaration 12 | .derive("Debug") 13 | .derive("Clone") 14 | .derive("Serialize") 15 | .vis("pub"); 16 | 17 | Self { declaration } 18 | } 19 | 20 | pub fn push_into(self, module: &mut codegen::Scope) { 21 | module.push_enum(self.declaration); 22 | } 23 | } 24 | 25 | impl Visitor for EnumGenerator { 26 | fn visit_enum_constant(&mut self, constant: &EnumConstant) { 27 | self.declaration.new_variant(constant.name()); 28 | 29 | if constant.value().is_some() { 30 | panic!("enum value is not supported: {:#?}", constant) 31 | } 32 | } 33 | 34 | fn visit_enum_attribute(&mut self, attribute: &Attribute) { 35 | if attribute.names().iter().any(|name| name == "Description") { 36 | self.declaration.doc(attribute.value()); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/generator/mod.rs: -------------------------------------------------------------------------------- 1 | mod enums; 2 | mod packages; 3 | mod schemas; 4 | mod structs; 5 | mod types; 6 | 7 | pub use enums::EnumGenerator; 8 | pub use packages::PackageGenerator; 9 | pub use schemas::SchemaGenerator; 10 | pub use structs::{BuilderGenerator, StructGenerator, TelemetryDataTraitGenerator}; 11 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/generator/packages.rs: -------------------------------------------------------------------------------- 1 | use crate::compiler::Module; 2 | 3 | pub struct PackageGenerator { 4 | modules: Vec, 5 | usages: Vec, 6 | } 7 | 8 | impl PackageGenerator { 9 | pub fn new() -> Self { 10 | Self { 11 | modules: Vec::default(), 12 | usages: Vec::default(), 13 | } 14 | } 15 | 16 | pub fn visit_module(&mut self, module: &Module) { 17 | self.modules.push(format!("mod {};", module.name())); 18 | self.usages.push(format!("pub use {}::*;", module.name())); 19 | } 20 | } 21 | 22 | impl ToString for PackageGenerator { 23 | fn to_string(&self) -> String { 24 | codegen::Scope::new() 25 | .raw("// NOTE: This file was automatically generated.") 26 | .raw("#![allow(unused_variables, dead_code, unused_imports)]") 27 | .raw(&self.modules.join("\n")) 28 | .raw(&self.usages.join("\n")) 29 | .push_trait(telemetry_data_trait()) 30 | .to_string() 31 | } 32 | } 33 | 34 | fn telemetry_data_trait() -> codegen::Trait { 35 | let mut telemetry_data = codegen::Trait::new("TelemetryData"); 36 | telemetry_data 37 | .vis("pub") 38 | .doc("Common interface implemented by telemetry data contacts.") 39 | .new_fn("envelope_name") 40 | .doc(&format!( 41 | "Returns the name used when this is embedded within an [{name}](trait.{name}.html) container.", 42 | name = "Envelope" 43 | )) 44 | .arg_ref_self() 45 | .arg("key", "&str") 46 | .ret("String") 47 | .line("let mut name = self.base_type();") 48 | .line("name.truncate(name.len() - 4);") 49 | .line("") 50 | .push_block({ 51 | let mut block = codegen::Block::new("if key.is_empty()"); 52 | block.line(r#"format!("Microsoft.ApplicationInsights.{}.{}", key, name)"#); 53 | block 54 | }) 55 | .push_block({ 56 | let mut block = codegen::Block::new("else"); 57 | block.line(r#"format!("Microsoft.ApplicationInsights.{}", name)"#); 58 | block 59 | }); 60 | 61 | telemetry_data 62 | .new_fn("base_type") 63 | .doc(&format!( 64 | "Returns the base type when placed within an [{name}](trait.{name}.html) container.", 65 | name = "Data" 66 | )) 67 | .arg_ref_self() 68 | .ret("String"); 69 | 70 | telemetry_data 71 | } 72 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/generator/schemas.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{Enum, Schema, Struct}; 2 | use crate::compiler::generator::{BuilderGenerator, EnumGenerator, StructGenerator, TelemetryDataTraitGenerator}; 3 | use crate::compiler::Visitor; 4 | 5 | pub struct SchemaGenerator { 6 | body: codegen::Scope, 7 | } 8 | 9 | impl SchemaGenerator { 10 | pub fn new() -> Self { 11 | Self { 12 | body: codegen::Scope::new(), 13 | } 14 | } 15 | } 16 | 17 | impl Visitor for SchemaGenerator { 18 | fn visit_schema(&mut self, schema: &Schema) { 19 | self.body.raw("// NOTE: This file was automatically generated."); 20 | self.body.import("serde", "Serialize"); 21 | self.body.import("crate::contracts", "*"); 22 | 23 | self.visit_declarations(schema.declarations()); 24 | } 25 | 26 | fn visit_struct(&mut self, declaration: &Struct) { 27 | // generate struct declaration 28 | let mut struct_generator = StructGenerator::new(declaration.name()); 29 | struct_generator.visit_struct(declaration); 30 | struct_generator.push_into(&mut self.body); 31 | 32 | // generate struct builder declaration 33 | let mut builder_generator = BuilderGenerator::new(declaration.name()); 34 | builder_generator.visit_struct(declaration); 35 | builder_generator.push_into(&mut self.body); 36 | 37 | // assume that if struct name ends with Data and it is not "Data" 38 | // so it required TelemetryData trait implemented for this type 39 | if declaration.is_telemetry_data() { 40 | let mut telemetry_data_generator = TelemetryDataTraitGenerator::new(declaration.name()); 41 | telemetry_data_generator.visit_struct(declaration); 42 | telemetry_data_generator.push_into(&mut self.body); 43 | } 44 | } 45 | 46 | fn visit_enum(&mut self, declaration: &Enum) { 47 | let mut enum_generator = EnumGenerator::new(declaration.name()); 48 | enum_generator.visit_enum(declaration); 49 | enum_generator.push_into(&mut self.body); 50 | } 51 | } 52 | 53 | impl ToString for SchemaGenerator { 54 | fn to_string(&self) -> String { 55 | self.body.to_string() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/generator/types.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::ast::{BasicType, ComplexType, Field, Type, UserType}; 4 | 5 | impl From for codegen::Type { 6 | fn from(field: Field) -> Self { 7 | let field_type = field.type_().clone(); 8 | 9 | if field_type.nullable().is_some() || field.is_required() { 10 | codegen::Type::from(field_type) 11 | } else { 12 | let mut type_ = codegen::Type::new("Option"); 13 | type_.generic(codegen::Type::from(field_type)); 14 | type_ 15 | } 16 | } 17 | } 18 | 19 | impl From for codegen::Type { 20 | fn from(type_: Type) -> Self { 21 | match type_ { 22 | Type::Basic(type_) => type_.into(), 23 | Type::Complex(type_) => type_.into(), 24 | } 25 | } 26 | } 27 | 28 | impl From for codegen::Type { 29 | fn from(type_: BasicType) -> codegen::Type { 30 | let name = match type_ { 31 | BasicType::Bool => "bool", 32 | BasicType::UInt8 => "u8", 33 | BasicType::UInt16 => "u16", 34 | BasicType::UInt32 => "u32", 35 | BasicType::UInt64 => "u64", 36 | BasicType::Int8 => "i8", 37 | BasicType::Int16 => "i16", 38 | BasicType::Int32 => "i32", 39 | BasicType::Int64 => "i64", 40 | BasicType::Float => "f32", 41 | BasicType::Double => "f64", 42 | BasicType::String => "String", 43 | BasicType::WString => "String", 44 | }; 45 | 46 | codegen::Type::new(name) 47 | } 48 | } 49 | 50 | impl From for codegen::Type { 51 | fn from(type_: ComplexType) -> codegen::Type { 52 | match type_ { 53 | ComplexType::Map { key, element } => { 54 | let mut type_ = codegen::Type::new("std::collections::BTreeMap"); 55 | 56 | let key = Type::from_str(&key).expect("unexpected type: key"); 57 | type_.generic(key); 58 | 59 | let element = Type::from_str(&element).expect("unexpected type: element"); 60 | type_.generic(element); 61 | type_ 62 | } 63 | ComplexType::Parameter { value } => codegen::Type::new(value.name()), 64 | ComplexType::Vector { element } => { 65 | let type_: Type = *element; 66 | type_.into() 67 | } 68 | ComplexType::Nullable { element } => { 69 | let mut type_ = codegen::Type::new("Option"); 70 | let element = *element; 71 | type_.generic(element); 72 | type_ 73 | } 74 | ComplexType::User { declaration } => { 75 | let name = match *declaration { 76 | UserType::Struct(struct_) => struct_.name().to_string(), 77 | UserType::Enum(enum_) => enum_.name().to_string(), 78 | }; 79 | codegen::Type::new(&name) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/mod.rs: -------------------------------------------------------------------------------- 1 | mod generator; 2 | mod module; 3 | mod visitor; 4 | 5 | pub use module::Module; 6 | pub use visitor::Visitor; 7 | 8 | use std::convert::TryFrom; 9 | use std::fs; 10 | use std::path::{Path, PathBuf}; 11 | 12 | use crate::compiler::generator::{PackageGenerator, SchemaGenerator}; 13 | use crate::parser::Parser; 14 | use crate::Result; 15 | 16 | pub fn compile_all(input_dir: PathBuf, output_dir: PathBuf) -> Result<()> { 17 | let mut modules: Vec<_> = fs::read_dir(&input_dir)? 18 | .filter_map(|entry| entry.ok().map(|entry| entry.path())) 19 | .map(|path| Module::try_from((path, output_dir.clone())).expect("unable to read module path")) 20 | .collect(); 21 | modules.sort_by(|a, b| a.file_name().cmp(b.file_name())); 22 | 23 | compile_files(modules.iter())?; 24 | compile_package(modules.iter(), &output_dir.join("mod.rs"))?; 25 | 26 | Ok(()) 27 | } 28 | 29 | fn compile_files<'a>(modules: impl Iterator) -> Result<()> { 30 | for module in modules { 31 | if let Err(err) = compile(module) { 32 | eprintln!("{}: {}", module.file_name(), err); 33 | } else { 34 | println!("{}: ok", module.file_name()); 35 | } 36 | } 37 | 38 | Ok(()) 39 | } 40 | 41 | fn compile(module: &Module) -> Result<()> { 42 | let parser = Parser::default(); 43 | let schema = parser.parse(module.source_path())?; 44 | 45 | let mut generator = SchemaGenerator::new(); 46 | generator.visit_schema(&schema); 47 | 48 | fs::write(&module.path(), generator.to_string())?; 49 | Ok(()) 50 | } 51 | 52 | fn compile_package<'a>(modules: impl Iterator, path: &Path) -> Result<()> { 53 | let mut generator = PackageGenerator::new(); 54 | for module in modules { 55 | generator.visit_module(module); 56 | } 57 | 58 | fs::write(path, generator.to_string())?; 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/module.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::path::{Path, PathBuf}; 3 | 4 | pub struct Module { 5 | name: String, 6 | file_name: String, 7 | source_path: PathBuf, 8 | path: PathBuf, 9 | } 10 | 11 | impl Module { 12 | pub fn name(&self) -> &str { 13 | &self.name 14 | } 15 | pub fn file_name(&self) -> &str { 16 | &self.file_name 17 | } 18 | pub fn source_path(&self) -> &Path { 19 | &self.source_path 20 | } 21 | pub fn path(&self) -> &Path { 22 | &self.path 23 | } 24 | } 25 | 26 | impl TryFrom<(PathBuf, PathBuf)> for Module { 27 | type Error = &'static str; 28 | 29 | fn try_from((source_path, destination_dir): (PathBuf, PathBuf)) -> std::result::Result { 30 | let name = source_path 31 | .file_stem() 32 | .and_then(|stem| stem.to_str()) 33 | .map(|stem| stem.to_lowercase()) 34 | .ok_or("Unable to get a module name")?; 35 | 36 | let file_name = format!("{}.rs", name); 37 | let path = destination_dir.join(&file_name); 38 | 39 | Ok(Self { 40 | name, 41 | file_name, 42 | source_path, 43 | path, 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/compiler/visitor.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | 3 | #[allow(unused_variables)] 4 | pub trait Visitor { 5 | fn visit_schema(&mut self, schema: &Schema) { 6 | self.visit_declarations(schema.declarations()) 7 | } 8 | 9 | fn visit_declarations(&mut self, declarations: &[UserType]) { 10 | for declaration in declarations { 11 | match &declaration { 12 | UserType::Struct(declaration) => { 13 | self.visit_struct(declaration); 14 | } 15 | UserType::Enum(declaration) => { 16 | self.visit_enum(declaration); 17 | } 18 | }; 19 | } 20 | } 21 | 22 | fn visit_struct(&mut self, declaration: &Struct) { 23 | if let Some(base) = declaration.base() { 24 | self.visit_base(base); 25 | } 26 | 27 | self.visit_struct_attributes(declaration.attributes()); 28 | self.visit_fields(declaration.fields()); 29 | } 30 | 31 | fn visit_struct_attributes(&mut self, attributes: &[Attribute]) { 32 | for attribute in attributes { 33 | self.visit_struct_attribute(attribute); 34 | } 35 | } 36 | 37 | fn visit_struct_attribute(&mut self, attribute: &Attribute) {} 38 | 39 | fn visit_base(&mut self, declaration: &Type) { 40 | if let Type::Complex(ComplexType::User { declaration }) = &declaration { 41 | if let UserType::Struct(declaration) = &**declaration { 42 | self.visit_struct(declaration); 43 | } else { 44 | panic!("Unsupported struct base type: {:?}", declaration) 45 | } 46 | } else { 47 | panic!("Unsupported struct base type: {:?}", declaration) 48 | } 49 | } 50 | 51 | fn visit_fields(&mut self, fields: &[Field]) { 52 | for field in fields { 53 | self.visit_field(field); 54 | } 55 | } 56 | 57 | fn visit_field(&mut self, field: &Field) { 58 | self.visit_field_attributes(field.attributes()); 59 | } 60 | 61 | fn visit_field_attributes(&mut self, attributes: &[Attribute]) { 62 | for attribute in attributes { 63 | self.visit_field_attribute(attribute); 64 | } 65 | } 66 | 67 | fn visit_field_attribute(&mut self, attribute: &Attribute) {} 68 | 69 | fn visit_enum(&mut self, declaration: &Enum) { 70 | self.visit_enum_constants(declaration.constants()); 71 | self.visit_enum_attributes(declaration.attributes()); 72 | } 73 | 74 | fn visit_enum_constants(&mut self, constants: &[EnumConstant]) { 75 | for constant in constants { 76 | self.visit_enum_constant(constant); 77 | } 78 | } 79 | 80 | fn visit_enum_constant(&mut self, constant: &EnumConstant) {} 81 | 82 | fn visit_enum_attributes(&mut self, attributes: &[Attribute]) { 83 | for attribute in attributes { 84 | self.visit_enum_attribute(attribute); 85 | } 86 | } 87 | 88 | fn visit_enum_attribute(&mut self, attribute: &Attribute) {} 89 | } 90 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | pub mod ast; 4 | pub mod compiler; 5 | pub mod parser; 6 | 7 | pub type Result = std::result::Result>; 8 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use structopt::StructOpt; 4 | 5 | use appinsights_contracts_codegen::compiler; 6 | 7 | fn main() { 8 | let opts = Opt::from_args(); 9 | if let Err(err) = compiler::compile_all(opts.input_dir, opts.output_dir) { 10 | eprintln!("{}", err) 11 | } 12 | } 13 | 14 | #[derive(StructOpt, Debug)] 15 | #[structopt(rename_all = "kebab-case")] 16 | pub struct Opt { 17 | /// A path to directory with all schema files 18 | #[structopt(parse(from_os_str), short = "i", long = "input-dir")] 19 | input_dir: PathBuf, 20 | 21 | /// A path to directory to output generate data contract files to 22 | #[structopt(parse(from_os_str), short = "o", long = "output-dir")] 23 | output_dir: PathBuf, 24 | } 25 | -------------------------------------------------------------------------------- /appinsights-contracts-codegen/src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::Path; 3 | 4 | use crate::ast::Schema; 5 | use crate::Result; 6 | 7 | #[derive(Default)] 8 | pub struct Parser; 9 | 10 | impl Parser { 11 | pub fn parse(&self, path: &Path) -> Result { 12 | let schema = serde_json::from_reader(File::open(&path)?)?; 13 | Ok(schema) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /appinsights/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "appinsights" 3 | version = "0.2.3" 4 | authors = ["dmolokanov "] 5 | edition = "2018" 6 | description = "Application Insights SDK for Rust" 7 | license = "MIT" 8 | documentation = "https://docs.rs/appinsights" 9 | repository = "https://github.com/dmolokanov/appinsights-rs" 10 | readme = "../README.md" 11 | keywords = ["logging", "tracing", "metrics", "APM"] 12 | categories = [ 13 | "development-tools::debugging", 14 | "development-tools::profiling" 15 | ] 16 | 17 | [package.metadata.docs.rs] 18 | all-features = true 19 | rustdoc-args = ["--cfg", "docsrs"] 20 | 21 | [lib] 22 | doctest = false 23 | 24 | [features] 25 | default = ["reqwest/default-tls"] 26 | rustls = ["reqwest/rustls-tls"] 27 | blocking = [] 28 | 29 | [dependencies] 30 | serde = { version = "1.0", features = ["derive"], default-features = false } 31 | serde_json = "1.0" 32 | chrono = { version = "0.4", features = ["clock"], default-features = false } 33 | http = "0.2" 34 | uuid = { version = "1.2", features = ["v4"], default-features = false } 35 | reqwest = { version = "0.11", features = ["json"], default-features = false } 36 | log = "0.4" 37 | sm = "0.9" 38 | tokio = { version = "1", features = ["rt"], default-features = false } 39 | paste = "1.0" 40 | hostname = "0.3" 41 | futures-util = { version = "0.3", default-features = false } 42 | futures-channel = "0.3" 43 | crossbeam-queue = "0.3" 44 | async-trait = "0.1.51" 45 | 46 | [dev-dependencies] 47 | test-case = "2.2" 48 | env_logger = "0.9" 49 | lazy_static = "1.4" 50 | matches = "0.1" 51 | hyper = { version = "0.14", features = ["server"], default-features = false } 52 | tokio = { version = "1.21", features = ["macros", "rt-multi-thread"], default-features = false } 53 | parking_lot = "0.12" 54 | 55 | [[example]] 56 | name = "blocking" 57 | required-features = ["blocking"] 58 | -------------------------------------------------------------------------------- /appinsights/examples/blocking.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::time::Duration; 3 | 4 | use appinsights::blocking::TelemetryClient; 5 | use log::LevelFilter; 6 | 7 | fn main() { 8 | env_logger::builder().filter_level(LevelFilter::Debug).init(); 9 | 10 | let i_key = env::var("APPINSIGHTS_INSTRUMENTATIONKEY").expect("Set APPINSIGHTS_INSTRUMENTATIONKEY first"); 11 | 12 | let ai = TelemetryClient::new(i_key); 13 | 14 | for x in 1..=25 { 15 | ai.track_event(format!("Client connected: {}", x)); 16 | std::thread::sleep(Duration::from_millis(300)); 17 | 18 | if x == 2 { 19 | ai.flush_channel(); 20 | } 21 | } 22 | 23 | ai.close_channel(); 24 | } 25 | -------------------------------------------------------------------------------- /appinsights/examples/default.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::time::Duration; 3 | 4 | use appinsights::TelemetryClient; 5 | use log::LevelFilter; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | env_logger::builder().filter_level(LevelFilter::Debug).init(); 10 | 11 | let i_key = env::var("APPINSIGHTS_INSTRUMENTATIONKEY").expect("Set APPINSIGHTS_INSTRUMENTATIONKEY first"); 12 | 13 | let ai = TelemetryClient::new(i_key); 14 | 15 | for x in 1..=25 { 16 | ai.track_event(format!("Client connected: {}", x)); 17 | std::thread::sleep(Duration::from_millis(300)); 18 | 19 | if x == 2 { 20 | ai.flush_channel(); 21 | } 22 | } 23 | 24 | ai.close_channel().await; 25 | } 26 | -------------------------------------------------------------------------------- /appinsights/src/channel/command.rs: -------------------------------------------------------------------------------- 1 | /// Describes command to be sent to internal channel. 2 | #[derive(Debug, Clone, PartialEq)] 3 | pub enum Command { 4 | /// A command to tear down the submission, close internal channels. All pending telemetry items to be discarded. 5 | Terminate, 6 | 7 | /// A command to force all pending telemetry items to be submitted. 8 | Flush, 9 | 10 | /// A command to tear down the submission, close internal channels and wait until all pending telemetry items to be sent. 11 | Close, 12 | } 13 | 14 | impl std::fmt::Display for Command { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | let label = match self { 17 | Command::Flush => "flush", 18 | Command::Terminate => "terminate", 19 | Command::Close => "close", 20 | }; 21 | write!(f, "{}", label) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /appinsights/src/channel/memory.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use async_trait::async_trait; 4 | use crossbeam_queue::SegQueue; 5 | use futures_channel::mpsc::UnboundedSender; 6 | use log::{debug, trace, warn}; 7 | use tokio::task::JoinHandle; 8 | 9 | use crate::{ 10 | channel::{command::Command, state::Worker, TelemetryChannel}, 11 | contracts::Envelope, 12 | transmitter::Transmitter, 13 | TelemetryConfig, 14 | }; 15 | 16 | /// A telemetry channel that stores events exclusively in memory. 17 | pub struct InMemoryChannel { 18 | items: Arc>, 19 | command_sender: Option>, 20 | join: Option>, 21 | } 22 | 23 | impl InMemoryChannel { 24 | /// Creates a new instance of in-memory channel and starts a submission routine. 25 | pub fn new(config: &TelemetryConfig) -> Self { 26 | let items = Arc::new(SegQueue::new()); 27 | 28 | let (command_sender, command_receiver) = futures_channel::mpsc::unbounded(); 29 | let worker = Worker::new( 30 | Transmitter::new(config.endpoint()), 31 | items.clone(), 32 | command_receiver, 33 | config.interval(), 34 | ); 35 | 36 | let handle = tokio::spawn(worker.run()); 37 | 38 | Self { 39 | items, 40 | command_sender: Some(command_sender), 41 | join: Some(handle), 42 | } 43 | } 44 | 45 | async fn shutdown(&mut self, command: Command) { 46 | // send shutdown command 47 | if let Some(sender) = self.command_sender.take() { 48 | send_command(&sender, command); 49 | } 50 | 51 | // wait until worker is finished 52 | if let Some(handle) = self.join.take() { 53 | debug!("Shutting down worker"); 54 | handle.await.unwrap(); 55 | } 56 | } 57 | } 58 | 59 | #[async_trait] 60 | impl TelemetryChannel for InMemoryChannel { 61 | fn send(&self, envelop: Envelope) { 62 | trace!("Sending telemetry to channel"); 63 | self.items.push(envelop); 64 | } 65 | 66 | fn flush(&self) { 67 | if let Some(sender) = &self.command_sender { 68 | send_command(sender, Command::Flush); 69 | } 70 | } 71 | 72 | async fn close(&mut self) { 73 | self.shutdown(Command::Close).await 74 | } 75 | 76 | async fn terminate(&mut self) { 77 | self.shutdown(Command::Terminate).await; 78 | } 79 | } 80 | 81 | fn send_command(sender: &UnboundedSender, command: Command) { 82 | debug!("Sending {} command to channel", command); 83 | if let Err(err) = sender.unbounded_send(command.clone()) { 84 | warn!("Unable to send {} command to channel: {}", command, err); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /appinsights/src/channel/mod.rs: -------------------------------------------------------------------------------- 1 | mod command; 2 | 3 | mod memory; 4 | pub use memory::InMemoryChannel; 5 | 6 | mod retry; 7 | 8 | mod state; 9 | 10 | use async_trait::async_trait; 11 | 12 | use crate::contracts::Envelope; 13 | 14 | /// An implementation of [TelemetryChannel](trait.TelemetryChannel.html) is responsible for queueing 15 | /// and periodically submitting telemetry events. 16 | #[async_trait] 17 | pub trait TelemetryChannel: Send + Sync { 18 | /// Queues a single telemetry item. 19 | fn send(&self, envelop: Envelope); 20 | 21 | /// Forces all pending telemetry items to be submitted. The current task will not be blocked. 22 | fn flush(&self); 23 | 24 | /// Flushes and tears down the submission flow and closes internal channels. 25 | /// It blocks the current task until all pending telemetry items have been submitted and it is safe to 26 | /// shutdown without losing telemetry. 27 | async fn close(&mut self); 28 | 29 | /// Flushes and tears down the submission flow and closes internal channels. 30 | /// It blocks the current task until all pending telemetry items have been submitted and it is safe to 31 | /// shutdown without losing telemetry. 32 | /// Tears down the submission flow and closes internal channels. Any telemetry waiting to be sent is discarded. 33 | /// This is a more abrupt version of [close](#method.close). 34 | async fn terminate(&mut self); 35 | } 36 | -------------------------------------------------------------------------------- /appinsights/src/channel/retry.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | /// Encapsulates retry logic for submit telemetry items operation. 4 | #[derive(Default, Debug)] 5 | pub struct Retry(Vec); 6 | 7 | impl Retry { 8 | pub fn exponential() -> Self { 9 | let timeouts = vec![Duration::from_secs(16), Duration::from_secs(4), Duration::from_secs(2)]; 10 | Self(timeouts) 11 | } 12 | 13 | pub fn once() -> Self { 14 | Self::default() 15 | } 16 | 17 | pub fn next(&mut self) -> Option { 18 | self.0.pop() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /appinsights/src/channel/state.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, sync::Arc, time::Duration}; 2 | 3 | use crossbeam_queue::SegQueue; 4 | use futures_channel::mpsc::UnboundedReceiver; 5 | use futures_util::{Future, Stream, StreamExt}; 6 | use log::{debug, error, trace}; 7 | use sm::{sm, Event}; 8 | 9 | use crate::{ 10 | channel::command::Command, 11 | channel::retry::Retry, 12 | channel::state::worker::{Variant::*, *}, 13 | contracts::Envelope, 14 | timeout, 15 | transmitter::{Response, Transmitter}, 16 | }; 17 | 18 | sm! { 19 | worker { 20 | InitialStates { Receiving } 21 | 22 | TimeoutExpired { 23 | Receiving => Sending, 24 | Waiting => Sending 25 | } 26 | 27 | FlushRequested { 28 | Receiving => Sending 29 | } 30 | 31 | CloseRequested { 32 | Receiving => Sending, 33 | Waiting => Stopped 34 | } 35 | 36 | ItemsSentAndContinue { 37 | Sending => Receiving 38 | } 39 | 40 | ItemsSentAndStop { 41 | Sending => Stopped 42 | } 43 | 44 | RetryRequested { 45 | Sending => Waiting 46 | } 47 | 48 | RetryExhausted { 49 | Waiting => Receiving 50 | } 51 | 52 | TerminateRequested { 53 | Receiving => Stopped, 54 | Sending => Stopped, 55 | Waiting => Stopped 56 | } 57 | } 58 | } 59 | 60 | pub struct Worker { 61 | transmitter: Transmitter, 62 | items: Arc>, 63 | command_receiver: UnboundedReceiver, 64 | interval: Duration, 65 | } 66 | 67 | impl Worker { 68 | pub fn new( 69 | transmitter: Transmitter, 70 | items: Arc>, 71 | command_receiver: UnboundedReceiver, 72 | interval: Duration, 73 | ) -> Self { 74 | Self { 75 | transmitter, 76 | items, 77 | command_receiver, 78 | interval, 79 | } 80 | } 81 | 82 | pub async fn run(mut self) { 83 | let mut state = Machine::new(Receiving).as_enum(); 84 | 85 | let mut items: Vec = Default::default(); 86 | let mut retry = Retry::default(); 87 | 88 | loop { 89 | state = match state { 90 | InitialReceiving(m) => self.handle_receiving(m, &mut items).await, 91 | ReceivingByItemsSentAndContinue(m) => self.handle_receiving(m, &mut items).await, 92 | ReceivingByRetryExhausted(m) => self.handle_receiving(m, &mut items).await, 93 | SendingByTimeoutExpired(m) => self.handle_sending_with_retry(m, &mut items, &mut retry).await, 94 | SendingByFlushRequested(m) => self.handle_sending_with_retry(m, &mut items, &mut retry).await, 95 | SendingByCloseRequested(m) => self.handle_sending_once_and_terminate(m, &mut items, &mut retry).await, 96 | WaitingByRetryRequested(m) => self.handle_waiting(m, &mut retry).await, 97 | StoppedByItemsSentAndStop(_) => break, 98 | StoppedByCloseRequested(_) => break, 99 | StoppedByTerminateRequested(_) => break, 100 | } 101 | } 102 | } 103 | 104 | async fn handle_receiving(&mut self, m: Machine, items: &mut Vec) -> Variant { 105 | debug!("Receiving messages triggered by {:?}", m.trigger()); 106 | 107 | let timeout = timeout::sleep(self.interval); 108 | items.clear(); 109 | 110 | loop { 111 | tokio::select! { 112 | command = self.command_receiver.next() => { 113 | match command { 114 | Some(command) => { 115 | trace!("Command received: {}", command); 116 | match command { 117 | Command::Flush => return m.transition(FlushRequested).as_enum(), 118 | Command::Terminate => return m.transition(TerminateRequested).as_enum(), 119 | Command::Close => return m.transition(CloseRequested).as_enum(), 120 | } 121 | }, 122 | None => { 123 | error!("commands channel closed"); 124 | return m.transition(TerminateRequested).as_enum() 125 | }, 126 | } 127 | }, 128 | _ = timeout => { 129 | debug!("Timeout expired"); 130 | return m.transition(TimeoutExpired).as_enum() 131 | }, 132 | } 133 | } 134 | } 135 | 136 | async fn handle_sending_with_retry( 137 | &mut self, 138 | m: Machine, 139 | items: &mut Vec, 140 | retry: &mut Retry, 141 | ) -> Variant { 142 | *retry = Retry::exponential(); 143 | self.handle_sending(m, items).await 144 | } 145 | 146 | async fn handle_sending_once_and_terminate( 147 | &mut self, 148 | m: Machine, 149 | items: &mut Vec, 150 | retry: &mut Retry, 151 | ) -> Variant { 152 | *retry = Retry::once(); 153 | let cloned = m.clone(); // clone here 154 | self.handle_sending(m, items).await; 155 | cloned.transition(TerminateRequested).as_enum() 156 | } 157 | 158 | async fn handle_sending(&mut self, m: Machine, items: &mut Vec) -> Variant { 159 | // read pending items from a channel 160 | while let Some(item) = self.items.pop() { 161 | items.push(item); 162 | } 163 | 164 | debug!( 165 | "Sending {} telemetry items triggered by {:?}", 166 | items.len(), 167 | m.trigger().unwrap() 168 | ); 169 | 170 | // submit items to the server if any 171 | if items.is_empty() { 172 | debug!("Nothing to send. Continue to wait"); 173 | m.transition(ItemsSentAndContinue).as_enum() 174 | } else { 175 | // attempt to send items 176 | match self.transmitter.send(mem::take(items)).await { 177 | Ok(Response::Success) => m.transition(ItemsSentAndContinue).as_enum(), 178 | Ok(Response::Retry(retry_items)) => { 179 | *items = retry_items; 180 | m.transition(RetryRequested).as_enum() 181 | } 182 | Ok(Response::Throttled(_retry_after, retry_items)) => { 183 | *items = retry_items; 184 | // TODO implement throttling instead 185 | m.transition(RetryRequested).as_enum() 186 | } 187 | Ok(Response::NoRetry) => m.transition(ItemsSentAndContinue).as_enum(), 188 | Err(err) => { 189 | debug!("Error occurred during sending telemetry items: {}", err); 190 | m.transition(RetryRequested).as_enum() 191 | } 192 | } 193 | } 194 | } 195 | 196 | async fn handle_waiting(&mut self, m: Machine, retry: &mut Retry) -> Variant { 197 | if let Some(timeout) = retry.next() { 198 | debug!( 199 | "Waiting for retry timeout {:?} or stop command triggered by {:?}", 200 | timeout, 201 | m.state() 202 | ); 203 | // sleep until next sending attempt 204 | let timeout = timeout::sleep(timeout); 205 | 206 | // wait for either retry timeout expired or stop command received 207 | tokio::select! { 208 | command = skip_flush(&mut self.command_receiver) => { 209 | match command { 210 | Some(Command::Terminate) => m.transition(TerminateRequested).as_enum(), 211 | Some(Command::Close) => m.transition(CloseRequested).as_enum(), 212 | Some(Command::Flush) => panic!("whoops Flush is not supported here"), 213 | None => { 214 | error!("commands channel closed"); 215 | m.transition(TerminateRequested).as_enum() 216 | } 217 | } 218 | }, 219 | _ = timeout => { 220 | debug!("Retry timeout expired"); 221 | m.transition(TimeoutExpired).as_enum() 222 | }, 223 | } 224 | } else { 225 | debug!("All retries exhausted by {:?}", m.state()); 226 | m.transition(RetryExhausted).as_enum() 227 | } 228 | } 229 | } 230 | 231 | fn skip_flush(stream: &mut St) -> SkipFlush<'_, St> { 232 | SkipFlush { stream } 233 | } 234 | 235 | struct SkipFlush<'a, St: ?Sized> { 236 | stream: &'a mut St, 237 | } 238 | 239 | impl + Unpin> Future for SkipFlush<'_, St> { 240 | type Output = Option; 241 | 242 | fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { 243 | match self.stream.poll_next_unpin(cx) { 244 | std::task::Poll::Ready(Some(Command::Flush)) => std::task::Poll::Pending, 245 | std::task::Poll::Ready(command) => std::task::Poll::Ready(command), 246 | std::task::Poll::Pending => std::task::Poll::Pending, 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /appinsights/src/config.rs: -------------------------------------------------------------------------------- 1 | //! Module for telemetry client configuration. 2 | use std::time::Duration; 3 | 4 | /// Configuration data used to initialize a new [`TelemetryClient`](../struct.TelemetryClient.html) with. 5 | /// 6 | /// # Examples 7 | /// 8 | /// Creating a telemetry client configuration with default settings 9 | /// ```rust 10 | /// # use appinsights::TelemetryConfig; 11 | /// let config = TelemetryConfig::new("".to_string()); 12 | /// ``` 13 | /// 14 | /// Creating a telemetry client configuration with custom settings 15 | /// ```rust 16 | /// # use std::time::Duration; 17 | /// # use appinsights::TelemetryConfig; 18 | /// let config = TelemetryConfig::builder() 19 | /// .i_key("") 20 | /// .interval(Duration::from_secs(5)) 21 | /// .build(); 22 | /// ``` 23 | #[derive(Clone, Debug, PartialEq)] 24 | pub struct TelemetryConfig { 25 | /// Instrumentation key for the client. 26 | i_key: String, 27 | 28 | /// Endpoint URL where data will be sent. 29 | endpoint: String, 30 | 31 | /// Maximum time to wait until send a batch of telemetry. 32 | interval: Duration, 33 | } 34 | 35 | impl TelemetryConfig { 36 | /// Creates a new telemetry configuration with specified instrumentation key and default values. 37 | pub fn new(i_key: String) -> Self { 38 | TelemetryConfig::builder().i_key(i_key).build() 39 | } 40 | 41 | /// Creates a new telemetry configuration builder with default parameters. 42 | pub fn builder() -> DefaultTelemetryConfigBuilder { 43 | DefaultTelemetryConfigBuilder::default() 44 | } 45 | 46 | /// Returns an instrumentation key for the client. 47 | pub fn i_key(&self) -> &str { 48 | &self.i_key 49 | } 50 | 51 | /// Returns endpoint URL where data will be sent. 52 | pub fn endpoint(&self) -> &str { 53 | &self.endpoint 54 | } 55 | 56 | /// Returns maximum time to wait until send a batch of telemetry. 57 | pub fn interval(&self) -> Duration { 58 | self.interval 59 | } 60 | } 61 | 62 | /// Constructs a new instance of a [`TelemetryConfig`](struct.TelemetryConfig.html) with required 63 | /// instrumentation key and custom settings. 64 | #[derive(Default)] 65 | pub struct DefaultTelemetryConfigBuilder; 66 | 67 | impl DefaultTelemetryConfigBuilder { 68 | /// Initializes a builder with an instrumentation key for the client. 69 | pub fn i_key(self, i_key: I) -> TelemetryConfigBuilder 70 | where 71 | I: Into, 72 | { 73 | TelemetryConfigBuilder { 74 | i_key: i_key.into(), 75 | endpoint: "https://dc.services.visualstudio.com/v2/track".into(), 76 | interval: Duration::from_secs(2), 77 | } 78 | } 79 | } 80 | 81 | /// Constructs a new instance of a [`TelemetryConfig`](struct.TelemetryConfig.html) with custom settings. 82 | pub struct TelemetryConfigBuilder { 83 | i_key: String, 84 | endpoint: String, 85 | interval: Duration, 86 | } 87 | 88 | impl TelemetryConfigBuilder { 89 | /// Initializes a builder with an instrumentation key for the client. 90 | pub fn i_key(mut self, i_key: I) -> Self 91 | where 92 | I: Into, 93 | { 94 | self.i_key = i_key.into(); 95 | self 96 | } 97 | 98 | /// Initializes a builder with an endpoint URL where data will be sent. 99 | pub fn endpoint(mut self, endpoint: E) -> Self 100 | where 101 | E: Into, 102 | { 103 | self.endpoint = endpoint.into(); 104 | self 105 | } 106 | 107 | /// Initializes a builder with a maximum time to wait until send a batch of telemetry. 108 | pub fn interval(mut self, interval: Duration) -> Self { 109 | self.interval = interval; 110 | self 111 | } 112 | 113 | /// Constructs a new instance of a [`TelemetryConfig`](struct.TelemetryConfig.html) with custom settings. 114 | pub fn build(self) -> TelemetryConfig { 115 | TelemetryConfig { 116 | i_key: self.i_key, 117 | endpoint: self.endpoint, 118 | interval: self.interval, 119 | } 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use super::*; 126 | 127 | #[test] 128 | fn it_creates_config_with_default_values() { 129 | let config = TelemetryConfig::new("instrumentation key".into()); 130 | 131 | assert_eq!( 132 | TelemetryConfig { 133 | i_key: "instrumentation key".into(), 134 | endpoint: "https://dc.services.visualstudio.com/v2/track".into(), 135 | interval: Duration::from_secs(2) 136 | }, 137 | config 138 | ) 139 | } 140 | 141 | #[test] 142 | fn it_builds_config_with_custom_parameters() { 143 | let config = TelemetryConfig::builder() 144 | .i_key("instrumentation key") 145 | .endpoint("https://google.com") 146 | .interval(Duration::from_micros(100)) 147 | .build(); 148 | 149 | assert_eq!( 150 | TelemetryConfig { 151 | i_key: "instrumentation key".into(), 152 | endpoint: "https://google.com".into(), 153 | interval: Duration::from_micros(100) 154 | }, 155 | config 156 | ); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /appinsights/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | telemetry::{ContextTags, Properties}, 3 | TelemetryConfig, 4 | }; 5 | 6 | /// Encapsulates contextual data common to all telemetry submitted through a telemetry client. 7 | /// # Examples 8 | /// ```rust 9 | /// use appinsights::telemetry::{ContextTags, Properties}; 10 | /// use appinsights::TelemetryContext; 11 | /// 12 | /// let mut properties = Properties::default(); 13 | /// properties.insert("Resource Group".to_string(), "my-rg".to_string()); 14 | /// 15 | /// let mut tags = ContextTags::default(); 16 | /// tags.insert("account_id".to_string(), "123-345-777".to_string()); 17 | /// 18 | /// let context = TelemetryContext::new("instrumentation".to_string(), tags, properties); 19 | /// 20 | /// assert_eq!(context.properties().get("Resource Group"), Some(&"my-rg".to_string())); 21 | /// assert_eq!(context.tags().get("account_id"), Some(&"123-345-777".to_string())); 22 | /// ``` 23 | #[derive(Debug, Clone)] 24 | pub struct TelemetryContext { 25 | /// An instrumentation key. 26 | pub(crate) i_key: String, 27 | 28 | // A collection of tags to attach to telemetry event. 29 | pub(crate) tags: ContextTags, 30 | 31 | // A collection of common properties to attach to telemetry event. 32 | pub(crate) properties: Properties, 33 | } 34 | 35 | impl TelemetryContext { 36 | /// Creates a new instance of telemetry context from config 37 | pub fn from_config(config: &TelemetryConfig) -> Self { 38 | let i_key = config.i_key().into(); 39 | 40 | let sdk_version = format!("rust:{}", env!("CARGO_PKG_VERSION")); 41 | let os_version = if cfg!(target_os = "linux") { 42 | "linux" 43 | } else if cfg!(target_os = "windows") { 44 | "windows" 45 | } else if cfg!(target_os = "macos") { 46 | "macos" 47 | } else { 48 | "unknown" 49 | }; 50 | 51 | let mut tags = ContextTags::default(); 52 | tags.internal_mut().set_sdk_version(sdk_version); 53 | tags.device_mut().set_os_version(os_version.into()); 54 | 55 | if let Ok(Ok(host)) = &hostname::get().map(|host| host.into_string()) { 56 | tags.device_mut().set_id(host.into()); 57 | tags.cloud_mut().set_role_instance(host.into()); 58 | } 59 | 60 | let properties = Properties::default(); 61 | Self::new(i_key, tags, properties) 62 | } 63 | 64 | /// Creates a new instance of telemetry context. 65 | pub fn new(i_key: String, tags: ContextTags, properties: Properties) -> Self { 66 | Self { 67 | i_key, 68 | tags, 69 | properties, 70 | } 71 | } 72 | 73 | /// Returns mutable reference to a collection of common properties to attach to telemetry event. 74 | pub fn properties_mut(&mut self) -> &mut Properties { 75 | &mut self.properties 76 | } 77 | 78 | /// Returns immutable reference to a collection of common properties to attach to telemetry event. 79 | pub fn properties(&self) -> &Properties { 80 | &self.properties 81 | } 82 | 83 | /// Returns mutable reference to a collection of common tags to attach to telemetry event. 84 | pub fn tags_mut(&mut self) -> &mut ContextTags { 85 | &mut self.tags 86 | } 87 | 88 | /// Returns immutable reference to a collection of common tags to attach to telemetry event. 89 | pub fn tags(&self) -> &ContextTags { 90 | &self.tags 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use matches::assert_matches; 97 | 98 | use super::*; 99 | 100 | #[test] 101 | fn it_updates_common_properties() { 102 | let config = TelemetryConfig::new("instrumentation".into()); 103 | let mut context = TelemetryContext::from_config(&config); 104 | context.properties_mut().insert("Resource Group".into(), "my-rg".into()); 105 | 106 | assert_eq!(context.properties().len(), 1); 107 | assert_eq!(context.properties().get("Resource Group"), Some(&"my-rg".to_string())); 108 | } 109 | 110 | #[test] 111 | fn it_creates_a_context_with_default_values() { 112 | let config = TelemetryConfig::new("instrumentation".into()); 113 | 114 | let context = TelemetryContext::from_config(&config); 115 | 116 | assert_eq!(&context.i_key, "instrumentation"); 117 | assert_matches!(&context.tags().internal().sdk_version(), Some(_)); 118 | assert_matches!(&context.tags().device().os_version(), Some(_)); 119 | assert_matches!(&context.tags().device().id(), Some(_)); 120 | assert_matches!(&context.tags().cloud().role_instance(), Some(_)); 121 | assert!(context.properties().is_empty()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /appinsights/src/contracts/availability_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Instances of AvailabilityData represent the result of executing an availability test. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct AvailabilityData { 10 | pub ver: i32, 11 | pub id: String, 12 | pub name: String, 13 | pub duration: String, 14 | pub success: bool, 15 | pub run_location: Option, 16 | pub message: Option, 17 | pub properties: Option>, 18 | pub measurements: Option>, 19 | } 20 | 21 | impl Default for AvailabilityData { 22 | fn default() -> Self { 23 | Self { 24 | ver: 2, 25 | id: String::default(), 26 | name: String::default(), 27 | duration: String::default(), 28 | success: bool::default(), 29 | run_location: Option::default(), 30 | message: Option::default(), 31 | properties: Option::default(), 32 | measurements: Option::default(), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /appinsights/src/contracts/base.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Data struct to contain only C section with custom fields. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(untagged)] 9 | #[serde(rename_all = "camelCase")] 10 | pub enum Base { 11 | Data(Data), 12 | } 13 | -------------------------------------------------------------------------------- /appinsights/src/contracts/data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Data struct to contain both B and C sections. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(tag = "baseType", content = "baseData")] 9 | pub enum Data { 10 | AvailabilityData(AvailabilityData), 11 | EventData(EventData), 12 | ExceptionData(ExceptionData), 13 | MessageData(MessageData), 14 | MetricData(MetricData), 15 | PageViewData(PageViewData), 16 | RemoteDependencyData(RemoteDependencyData), 17 | RequestData(RequestData), 18 | } 19 | -------------------------------------------------------------------------------- /appinsights/src/contracts/data_point.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Metric data single measurement. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct DataPoint { 10 | pub ns: Option, 11 | pub name: String, 12 | pub kind: Option, 13 | pub value: f64, 14 | pub count: Option, 15 | pub min: Option, 16 | pub max: Option, 17 | pub std_dev: Option, 18 | } 19 | 20 | impl Default for DataPoint { 21 | fn default() -> Self { 22 | Self { 23 | ns: None, 24 | name: String::default(), 25 | kind: Some(DataPointType::Measurement), 26 | value: f64::default(), 27 | count: None, 28 | min: None, 29 | max: None, 30 | std_dev: None, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /appinsights/src/contracts/data_point_type.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Type of the metric data measurement. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | pub enum DataPointType { 9 | Measurement, 10 | Aggregation, 11 | } 12 | -------------------------------------------------------------------------------- /appinsights/src/contracts/envelope.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// System variables for a telemetry item. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct Envelope { 10 | pub ver: Option, 11 | pub name: String, 12 | pub time: String, 13 | pub sample_rate: Option, 14 | pub seq: Option, 15 | pub i_key: Option, 16 | pub flags: Option, 17 | pub tags: Option>, 18 | pub data: Option, 19 | } 20 | 21 | impl Default for Envelope { 22 | fn default() -> Self { 23 | Self { 24 | ver: Some(1), 25 | name: String::default(), 26 | time: String::default(), 27 | sample_rate: Some(100.0), 28 | seq: Option::default(), 29 | i_key: Option::default(), 30 | flags: Option::default(), 31 | tags: Option::default(), 32 | data: Option::default(), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /appinsights/src/contracts/event_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Instances of Event represent structured event records that can be grouped and searched by their properties. Event data item also creates a metric of event count by name. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct EventData { 10 | pub ver: i32, 11 | pub name: String, 12 | pub properties: Option>, 13 | pub measurements: Option>, 14 | } 15 | 16 | impl Default for EventData { 17 | fn default() -> Self { 18 | Self { 19 | ver: 2, 20 | name: String::default(), 21 | properties: Option::default(), 22 | measurements: Option::default(), 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /appinsights/src/contracts/exception_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// An instance of Exception represents a handled or unhandled exception that occurred during execution of the monitored application. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct ExceptionData { 10 | pub ver: i32, 11 | pub exceptions: ExceptionDetails, 12 | pub severity_level: Option, 13 | pub problem_id: Option, 14 | pub properties: Option>, 15 | pub measurements: Option>, 16 | } 17 | 18 | impl Default for ExceptionData { 19 | fn default() -> Self { 20 | Self { 21 | ver: 2, 22 | exceptions: ExceptionDetails::default(), 23 | severity_level: Option::default(), 24 | problem_id: Option::default(), 25 | properties: Option::default(), 26 | measurements: Option::default(), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /appinsights/src/contracts/exception_details.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Exception details of the exception in a chain. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct ExceptionDetails { 10 | id: Option, 11 | outer_id: Option, 12 | type_name: String, 13 | message: String, 14 | has_full_stack: Option, 15 | stack: Option, 16 | parsed_stack: Option, 17 | } 18 | 19 | impl Default for ExceptionDetails { 20 | fn default() -> Self { 21 | Self { 22 | id: Option::default(), 23 | outer_id: Option::default(), 24 | type_name: String::default(), 25 | message: String::default(), 26 | has_full_stack: Some(true), 27 | stack: Option::default(), 28 | parsed_stack: Option::default(), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /appinsights/src/contracts/message_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Instances of Message represent printf-like trace statements that are text-searched. Log4Net, NLog and other text-based log file entries are translated into intances of this type. The message does not have measurements. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct MessageData { 10 | pub ver: i32, 11 | pub message: String, 12 | pub severity_level: Option, 13 | pub properties: Option>, 14 | pub measurements: Option>, 15 | } 16 | 17 | impl Default for MessageData { 18 | fn default() -> Self { 19 | Self { 20 | ver: 2, 21 | message: String::default(), 22 | severity_level: Option::default(), 23 | properties: Option::default(), 24 | measurements: Option::default(), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /appinsights/src/contracts/metric_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// An instance of the Metric item is a list of measurements (single data points) and/or aggregations. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct MetricData { 10 | pub ver: i32, 11 | pub metrics: Vec, 12 | pub properties: Option>, 13 | } 14 | 15 | impl Default for MetricData { 16 | fn default() -> Self { 17 | Self { 18 | ver: 2, 19 | metrics: Vec::default(), 20 | properties: Option::default(), 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /appinsights/src/contracts/mod.rs: -------------------------------------------------------------------------------- 1 | // NOTE: This file was automatically generated. 2 | 3 | #![allow(unused_imports)] 4 | 5 | mod availability_data; 6 | mod base; 7 | mod data; 8 | mod data_point; 9 | mod data_point_type; 10 | mod envelope; 11 | mod event_data; 12 | mod exception_data; 13 | mod exception_details; 14 | mod message_data; 15 | mod metric_data; 16 | mod page_view_data; 17 | mod remote_dependency_data; 18 | mod request_data; 19 | mod response; 20 | mod severity_level; 21 | mod stack_frame; 22 | 23 | pub use availability_data::*; 24 | pub use base::*; 25 | pub use data::*; 26 | pub use data_point::*; 27 | pub use data_point_type::*; 28 | pub use envelope::*; 29 | pub use event_data::*; 30 | pub use exception_data::*; 31 | pub use exception_details::*; 32 | pub use message_data::*; 33 | pub use metric_data::*; 34 | pub use page_view_data::*; 35 | pub use remote_dependency_data::*; 36 | pub use request_data::*; 37 | pub use response::*; 38 | pub use severity_level::*; 39 | pub use stack_frame::*; 40 | -------------------------------------------------------------------------------- /appinsights/src/contracts/page_view_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// An instance of PageView represents a generic action on a page like a button click. It is also the base type for PageView. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct PageViewData { 10 | pub ver: i32, 11 | pub name: String, 12 | pub url: Option, 13 | pub duration: Option, 14 | pub referrer_uri: Option, 15 | pub id: String, 16 | pub properties: Option>, 17 | pub measurements: Option>, 18 | } 19 | 20 | impl Default for PageViewData { 21 | fn default() -> Self { 22 | Self { 23 | ver: 2, 24 | name: String::default(), 25 | url: Option::default(), 26 | duration: Option::default(), 27 | referrer_uri: Option::default(), 28 | id: String::default(), 29 | properties: Option::default(), 30 | measurements: Option::default(), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /appinsights/src/contracts/remote_dependency_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// An instance of Remote Dependency represents an interaction of the monitored component with a remote component/service like SQL or an HTTP endpoint. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct RemoteDependencyData { 10 | pub ver: i32, 11 | pub name: String, 12 | pub id: Option, 13 | pub result_code: Option, 14 | pub duration: String, 15 | pub success: Option, 16 | pub data: Option, 17 | pub target: Option, 18 | pub type_: Option, 19 | pub properties: Option>, 20 | pub measurements: Option>, 21 | } 22 | 23 | impl Default for RemoteDependencyData { 24 | fn default() -> Self { 25 | Self { 26 | ver: 2, 27 | name: String::default(), 28 | id: Option::default(), 29 | result_code: Option::default(), 30 | duration: String::default(), 31 | success: Some(true), 32 | data: Option::default(), 33 | target: Option::default(), 34 | type_: Option::default(), 35 | properties: Option::default(), 36 | measurements: Option::default(), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /appinsights/src/contracts/request_data.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// An instance of Request represents completion of an external request to the application to do work and contains a summary of that request execution and the results. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct RequestData { 10 | pub ver: i32, 11 | pub id: String, 12 | pub source: Option, 13 | pub name: Option, 14 | pub duration: String, 15 | pub response_code: String, 16 | pub success: bool, 17 | pub url: Option, 18 | pub properties: Option>, 19 | pub measurements: Option>, 20 | } 21 | 22 | impl Default for RequestData { 23 | fn default() -> Self { 24 | Self { 25 | ver: 2, 26 | id: String::default(), 27 | source: Option::default(), 28 | name: Option::default(), 29 | duration: String::default(), 30 | response_code: String::default(), 31 | success: true, 32 | url: Option::default(), 33 | properties: Option::default(), 34 | measurements: Option::default(), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /appinsights/src/contracts/response.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Debug, Deserialize)] 4 | #[serde(rename_all = "camelCase")] 5 | pub struct Transmission { 6 | pub items_received: usize, 7 | pub items_accepted: usize, 8 | pub errors: Vec, 9 | } 10 | 11 | #[derive(Debug, Deserialize)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct TransmissionItem { 14 | pub index: usize, 15 | pub status_code: u16, 16 | pub message: String, 17 | } 18 | -------------------------------------------------------------------------------- /appinsights/src/contracts/severity_level.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Defines the level of severity for the event. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | pub enum SeverityLevel { 9 | Verbose, 10 | Information, 11 | Warning, 12 | Error, 13 | Critical, 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use serde_json::to_string; 19 | 20 | use super::*; 21 | 22 | #[test] 23 | fn it_json_serializes_valid_constants() { 24 | // The JSON-serialized values must match the value of `constantName` in 25 | // `schema/SeverityLevel.json`. 26 | // 27 | // Regression test for appinsights-rs#18. 28 | assert_eq!(to_string(&SeverityLevel::Verbose).unwrap(), r#""Verbose""#); 29 | assert_eq!(to_string(&SeverityLevel::Information).unwrap(), r#""Information""#); 30 | assert_eq!(to_string(&SeverityLevel::Warning).unwrap(), r#""Warning""#); 31 | assert_eq!(to_string(&SeverityLevel::Error).unwrap(), r#""Error""#); 32 | assert_eq!(to_string(&SeverityLevel::Critical).unwrap(), r#""Critical""#); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /appinsights/src/contracts/stack_frame.rs: -------------------------------------------------------------------------------- 1 | use crate::contracts::*; 2 | use serde::Serialize; 3 | 4 | // NOTE: This file was automatically generated. 5 | 6 | /// Stack frame information. 7 | #[derive(Debug, Clone, PartialEq, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct StackFrame { 10 | level: i32, 11 | method: String, 12 | assembly: Option, 13 | file_name: Option, 14 | line: Option, 15 | } 16 | 17 | impl Default for StackFrame { 18 | fn default() -> Self { 19 | Self { 20 | level: i32::default(), 21 | method: String::default(), 22 | assembly: Option::default(), 23 | file_name: Option::default(), 24 | line: Option::default(), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/availability.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration as StdDuration; 2 | 3 | use chrono::{DateTime, SecondsFormat, Utc}; 4 | 5 | use crate::{ 6 | context::TelemetryContext, 7 | contracts::{AvailabilityData, Base, Data, Envelope}, 8 | telemetry::{ContextTags, Measurements, Properties, Telemetry}, 9 | time::{self, Duration}, 10 | uuid::Uuid, 11 | }; 12 | 13 | /// Represents the result of executing an availability test. 14 | /// 15 | /// # Examples 16 | /// ```rust, no_run 17 | /// # use appinsights::TelemetryClient; 18 | /// # let client = TelemetryClient::new("".to_string()); 19 | /// use appinsights::telemetry::{Telemetry, AvailabilityTelemetry}; 20 | /// use std::time::Duration; 21 | /// 22 | /// // create a telemetry item 23 | /// let mut telemetry = AvailabilityTelemetry::new( 24 | /// "PING https://api.github.com/dmolokanov/appinsights-rs", 25 | /// Duration::from_secs(2), 26 | /// true, 27 | /// ); 28 | /// 29 | /// // attach custom properties, measurements and context tags 30 | /// telemetry.properties_mut().insert("component".to_string(), "data_processor".to_string()); 31 | /// telemetry.tags_mut().insert("os_version".to_string(), "linux x86_64".to_string()); 32 | /// telemetry.measurements_mut().insert("body_size".to_string(), 115.0); 33 | /// 34 | /// // submit telemetry item to server 35 | /// client.track(telemetry); 36 | /// ``` 37 | #[derive(Debug)] 38 | pub struct AvailabilityTelemetry { 39 | /// Identifier of a test run. 40 | /// It is used to correlate steps of test run and telemetry generated by the service. 41 | id: Option, 42 | 43 | /// Name of the test that this result represents. 44 | name: String, 45 | 46 | /// Duration of the test run. 47 | duration: Duration, 48 | 49 | /// Indication of successful or unsuccessful call. 50 | success: bool, 51 | 52 | /// The time stamp when this telemetry was measured. 53 | timestamp: DateTime, 54 | 55 | /// Name of the location where the test was run. 56 | run_location: Option, 57 | 58 | /// Diagnostic message for the result. 59 | message: Option, 60 | 61 | /// Custom properties. 62 | properties: Properties, 63 | 64 | /// Telemetry context containing extra, optional tags. 65 | tags: ContextTags, 66 | 67 | /// Custom measurements. 68 | measurements: Measurements, 69 | } 70 | 71 | impl AvailabilityTelemetry { 72 | /// Creates a new availability telemetry item with the specified test name, duration and success code. 73 | pub fn new(name: impl Into, duration: StdDuration, success: bool) -> Self { 74 | Self { 75 | id: Option::default(), 76 | name: name.into(), 77 | duration: duration.into(), 78 | run_location: Option::default(), 79 | message: Option::default(), 80 | success, 81 | timestamp: time::now(), 82 | properties: Properties::default(), 83 | tags: ContextTags::default(), 84 | measurements: Measurements::default(), 85 | } 86 | } 87 | 88 | /// Returns custom measurements to submit with the telemetry item. 89 | pub fn measurements(&self) -> &Measurements { 90 | &self.measurements 91 | } 92 | 93 | /// Returns mutable reference to custom measurements. 94 | pub fn measurements_mut(&mut self) -> &mut Measurements { 95 | &mut self.measurements 96 | } 97 | } 98 | 99 | impl Telemetry for AvailabilityTelemetry { 100 | /// Returns the time when this telemetry was measured. 101 | fn timestamp(&self) -> DateTime { 102 | self.timestamp 103 | } 104 | 105 | /// Returns custom properties to submit with the telemetry item. 106 | fn properties(&self) -> &Properties { 107 | &self.properties 108 | } 109 | 110 | /// Returns mutable reference to custom properties. 111 | fn properties_mut(&mut self) -> &mut Properties { 112 | &mut self.properties 113 | } 114 | 115 | /// Returns context data containing extra, optional tags. Overrides values found on client telemetry context. 116 | fn tags(&self) -> &ContextTags { 117 | &self.tags 118 | } 119 | 120 | /// Returns mutable reference to custom tags. 121 | fn tags_mut(&mut self) -> &mut ContextTags { 122 | &mut self.tags 123 | } 124 | } 125 | 126 | impl From<(TelemetryContext, AvailabilityTelemetry)> for Envelope { 127 | fn from((context, telemetry): (TelemetryContext, AvailabilityTelemetry)) -> Self { 128 | Self { 129 | name: "Microsoft.ApplicationInsights.Availability".into(), 130 | time: telemetry.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), 131 | i_key: Some(context.i_key), 132 | tags: Some(ContextTags::combine(context.tags, telemetry.tags).into()), 133 | data: Some(Base::Data(Data::AvailabilityData(AvailabilityData { 134 | id: telemetry 135 | .id 136 | .map(|id| id.as_hyphenated().to_string()) 137 | .unwrap_or_default(), 138 | name: telemetry.name, 139 | duration: telemetry.duration.to_string(), 140 | success: telemetry.success, 141 | run_location: telemetry.run_location, 142 | message: telemetry.message, 143 | properties: Some(Properties::combine(context.properties, telemetry.properties).into()), 144 | measurements: Some(telemetry.measurements.into()), 145 | ..AvailabilityData::default() 146 | }))), 147 | ..Envelope::default() 148 | } 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use std::collections::BTreeMap; 155 | 156 | use chrono::TimeZone; 157 | 158 | use super::*; 159 | 160 | #[test] 161 | fn it_overrides_properties_from_context() { 162 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 800)); 163 | 164 | let mut context = 165 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 166 | context.properties_mut().insert("test".into(), "ok".into()); 167 | context.properties_mut().insert("no-write".into(), "fail".into()); 168 | 169 | let mut telemetry = 170 | AvailabilityTelemetry::new("GET https://example.com/main.html", StdDuration::from_secs(2), true); 171 | telemetry.properties_mut().insert("no-write".into(), "ok".into()); 172 | telemetry.measurements_mut().insert("latency".into(), 200.0); 173 | 174 | let envelop = Envelope::from((context, telemetry)); 175 | 176 | let expected = Envelope { 177 | name: "Microsoft.ApplicationInsights.Availability".into(), 178 | time: "2019-01-02T03:04:05.800Z".into(), 179 | i_key: Some("instrumentation".into()), 180 | tags: Some(BTreeMap::default()), 181 | data: Some(Base::Data(Data::AvailabilityData(AvailabilityData { 182 | name: "GET https://example.com/main.html".into(), 183 | duration: "0.00:00:02.0000000".into(), 184 | success: true, 185 | message: None, 186 | properties: Some({ 187 | let mut properties = BTreeMap::default(); 188 | properties.insert("test".into(), "ok".into()); 189 | properties.insert("no-write".into(), "ok".into()); 190 | properties 191 | }), 192 | measurements: Some({ 193 | let mut measurement = BTreeMap::default(); 194 | measurement.insert("latency".into(), 200.0); 195 | measurement 196 | }), 197 | ..AvailabilityData::default() 198 | }))), 199 | ..Envelope::default() 200 | }; 201 | 202 | assert_eq!(envelop, expected) 203 | } 204 | 205 | #[test] 206 | fn it_overrides_tags_from_context() { 207 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 700)); 208 | 209 | let mut context = 210 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 211 | context.tags_mut().insert("test".into(), "ok".into()); 212 | context.tags_mut().insert("no-write".into(), "fail".into()); 213 | 214 | let mut telemetry = 215 | AvailabilityTelemetry::new("GET https://example.com/main.html", StdDuration::from_secs(2), true); 216 | telemetry.tags_mut().insert("no-write".into(), "ok".into()); 217 | 218 | let envelop = Envelope::from((context, telemetry)); 219 | let expected = Envelope { 220 | name: "Microsoft.ApplicationInsights.Availability".into(), 221 | time: "2019-01-02T03:04:05.700Z".into(), 222 | i_key: Some("instrumentation".into()), 223 | tags: Some({ 224 | let mut tags = BTreeMap::default(); 225 | tags.insert("test".into(), "ok".into()); 226 | tags.insert("no-write".into(), "ok".into()); 227 | tags 228 | }), 229 | data: Some(Base::Data(Data::AvailabilityData(AvailabilityData { 230 | name: "GET https://example.com/main.html".into(), 231 | duration: "0.00:00:02.0000000".into(), 232 | success: true, 233 | message: None, 234 | properties: Some(BTreeMap::default()), 235 | measurements: Some(BTreeMap::default()), 236 | ..AvailabilityData::default() 237 | }))), 238 | ..Envelope::default() 239 | }; 240 | 241 | assert_eq!(envelop, expected) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/event.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, SecondsFormat, Utc}; 2 | 3 | use crate::{ 4 | context::TelemetryContext, 5 | contracts::{Base, Data, Envelope, EventData}, 6 | telemetry::{ContextTags, Measurements, Properties, Telemetry}, 7 | time, 8 | }; 9 | 10 | /// Represents structured event records. 11 | /// 12 | /// # Examples 13 | /// ```rust, no_run 14 | /// # use appinsights::TelemetryClient; 15 | /// # let client = TelemetryClient::new("".to_string()); 16 | /// use appinsights::telemetry::{Telemetry, EventTelemetry}; 17 | /// 18 | /// // create a telemetry item 19 | /// let mut telemetry = EventTelemetry::new("Starting data processing"); 20 | /// 21 | /// // attach custom properties, measurements and context tags 22 | /// telemetry.properties_mut().insert("component".to_string(), "data_processor".to_string()); 23 | /// telemetry.tags_mut().insert("os_version".to_string(), "linux x86_64".to_string()); 24 | /// telemetry.measurements_mut().insert("records_count".to_string(), 115.0); 25 | /// 26 | /// // submit telemetry item to server 27 | /// client.track(telemetry); 28 | /// ``` 29 | #[derive(Debug)] 30 | pub struct EventTelemetry { 31 | /// Event name. 32 | name: String, 33 | 34 | /// The time stamp when this telemetry was measured. 35 | timestamp: DateTime, 36 | 37 | /// Custom properties. 38 | properties: Properties, 39 | 40 | /// Telemetry context containing extra, optional tags. 41 | tags: ContextTags, 42 | 43 | /// Custom measurements. 44 | measurements: Measurements, 45 | } 46 | 47 | impl EventTelemetry { 48 | /// Creates an event telemetry item with specified name. 49 | pub fn new(name: impl Into) -> Self { 50 | Self { 51 | name: name.into(), 52 | timestamp: time::now(), 53 | properties: Properties::default(), 54 | tags: ContextTags::default(), 55 | measurements: Measurements::default(), 56 | } 57 | } 58 | 59 | /// Returns custom measurements to submit with the telemetry item. 60 | pub fn measurements(&self) -> &Measurements { 61 | &self.measurements 62 | } 63 | 64 | /// Returns mutable reference to custom measurements. 65 | pub fn measurements_mut(&mut self) -> &mut Measurements { 66 | &mut self.measurements 67 | } 68 | } 69 | 70 | impl Telemetry for EventTelemetry { 71 | /// Returns the time when this telemetry was measured. 72 | fn timestamp(&self) -> DateTime { 73 | self.timestamp 74 | } 75 | 76 | /// Returns custom properties to submit with the telemetry item. 77 | fn properties(&self) -> &Properties { 78 | &self.properties 79 | } 80 | 81 | /// Returns mutable reference to custom properties. 82 | fn properties_mut(&mut self) -> &mut Properties { 83 | &mut self.properties 84 | } 85 | 86 | /// Returns context data containing extra, optional tags. Overrides values found on client telemetry context. 87 | fn tags(&self) -> &ContextTags { 88 | &self.tags 89 | } 90 | 91 | /// Returns mutable reference to custom tags. 92 | fn tags_mut(&mut self) -> &mut ContextTags { 93 | &mut self.tags 94 | } 95 | } 96 | 97 | impl From<(TelemetryContext, EventTelemetry)> for Envelope { 98 | fn from((context, telemetry): (TelemetryContext, EventTelemetry)) -> Self { 99 | Self { 100 | name: "Microsoft.ApplicationInsights.Event".into(), 101 | time: telemetry.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), 102 | i_key: Some(context.i_key), 103 | tags: Some(ContextTags::combine(context.tags, telemetry.tags).into()), 104 | data: Some(Base::Data(Data::EventData(EventData { 105 | name: telemetry.name, 106 | properties: Some(Properties::combine(context.properties, telemetry.properties).into()), 107 | measurements: Some(telemetry.measurements.into()), 108 | ..EventData::default() 109 | }))), 110 | ..Envelope::default() 111 | } 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use std::collections::BTreeMap; 118 | 119 | use chrono::TimeZone; 120 | 121 | use super::*; 122 | use crate::time; 123 | 124 | #[test] 125 | fn it_overrides_properties_from_context() { 126 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 600)); 127 | 128 | let mut context = 129 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 130 | context.properties_mut().insert("test".into(), "ok".into()); 131 | context.properties_mut().insert("no-write".into(), "fail".into()); 132 | 133 | let mut telemetry = EventTelemetry::new("test"); 134 | telemetry.properties_mut().insert("no-write".into(), "ok".into()); 135 | telemetry.measurements_mut().insert("value".into(), 5.0); 136 | 137 | let envelop = Envelope::from((context, telemetry)); 138 | 139 | let expected = Envelope { 140 | name: "Microsoft.ApplicationInsights.Event".into(), 141 | time: "2019-01-02T03:04:05.600Z".into(), 142 | i_key: Some("instrumentation".into()), 143 | tags: Some(BTreeMap::default()), 144 | data: Some(Base::Data(Data::EventData(EventData { 145 | name: "test".into(), 146 | properties: Some({ 147 | let mut properties = BTreeMap::default(); 148 | properties.insert("test".into(), "ok".into()); 149 | properties.insert("no-write".into(), "ok".into()); 150 | properties 151 | }), 152 | measurements: Some({ 153 | let mut measurements = BTreeMap::default(); 154 | measurements.insert("value".into(), 5.0); 155 | measurements 156 | }), 157 | ..EventData::default() 158 | }))), 159 | ..Envelope::default() 160 | }; 161 | 162 | assert_eq!(envelop, expected) 163 | } 164 | 165 | #[test] 166 | fn it_overrides_tags_from_context() { 167 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 700)); 168 | 169 | let mut context = 170 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 171 | context.tags_mut().insert("test".into(), "ok".into()); 172 | context.tags_mut().insert("no-write".into(), "fail".into()); 173 | 174 | let mut telemetry = EventTelemetry::new("test"); 175 | telemetry.tags_mut().insert("no-write".into(), "ok".into()); 176 | 177 | let envelop = Envelope::from((context, telemetry)); 178 | 179 | let expected = Envelope { 180 | name: "Microsoft.ApplicationInsights.Event".into(), 181 | time: "2019-01-02T03:04:05.700Z".into(), 182 | i_key: Some("instrumentation".into()), 183 | tags: Some({ 184 | let mut tags = BTreeMap::default(); 185 | tags.insert("test".into(), "ok".into()); 186 | tags.insert("no-write".into(), "ok".into()); 187 | tags 188 | }), 189 | data: Some(Base::Data(Data::EventData(EventData { 190 | name: "test".into(), 191 | properties: Some(BTreeMap::default()), 192 | measurements: Some(BTreeMap::default()), 193 | ..EventData::default() 194 | }))), 195 | ..Envelope::default() 196 | }; 197 | 198 | assert_eq!(envelop, expected) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/exception.rs: -------------------------------------------------------------------------------- 1 | // TODO implement exception collection telemetry item 2 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/measurements.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | 6 | /// Contains all measurements for telemetry to submit. 7 | #[derive(Debug, Clone, Default)] 8 | pub struct Measurements(BTreeMap); 9 | 10 | impl From for BTreeMap { 11 | fn from(measurements: Measurements) -> Self { 12 | measurements.0 13 | } 14 | } 15 | 16 | impl Deref for Measurements { 17 | type Target = BTreeMap; 18 | 19 | fn deref(&self) -> &Self::Target { 20 | &self.0 21 | } 22 | } 23 | 24 | impl DerefMut for Measurements { 25 | fn deref_mut(&mut self) -> &mut Self::Target { 26 | &mut self.0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/metric/measurement.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, SecondsFormat, Utc}; 2 | 3 | use crate::{ 4 | context::TelemetryContext, 5 | contracts::{Base, Data, DataPoint, DataPointType, Envelope, MetricData}, 6 | telemetry::{ContextTags, Properties, Telemetry}, 7 | time, 8 | }; 9 | 10 | /// Metric telemetry item that represents a single data point. 11 | /// 12 | /// # Examples 13 | /// ```rust, no_run 14 | /// # use appinsights::TelemetryClient; 15 | /// # let client = TelemetryClient::new("".to_string()); 16 | /// use appinsights::telemetry::{Telemetry, MetricTelemetry}; 17 | /// 18 | /// // create a telemetry item 19 | /// let mut telemetry = MetricTelemetry::new("temp_sensor", 55.0); 20 | /// 21 | /// // assign custom properties and context tags 22 | /// telemetry.properties_mut().insert("component".to_string(), "external_device".to_string()); 23 | /// telemetry.tags_mut().insert("os_version".to_string(), "linux x86_64".to_string()); 24 | /// 25 | /// // submit telemetry item to server 26 | /// client.track(telemetry); 27 | /// ``` 28 | #[derive(Debug)] 29 | pub struct MetricTelemetry { 30 | /// Metric name. 31 | name: String, 32 | 33 | /// Sampled value. 34 | value: f64, 35 | 36 | /// The time stamp when this telemetry was measured. 37 | timestamp: DateTime, 38 | 39 | /// Custom properties. 40 | properties: Properties, 41 | 42 | /// Telemetry context containing extra, optional tags. 43 | tags: ContextTags, 44 | } 45 | 46 | impl MetricTelemetry { 47 | /// Creates a metric telemetry item with specified name and value. 48 | pub fn new(name: impl Into, value: f64) -> Self { 49 | Self { 50 | name: name.into(), 51 | value, 52 | timestamp: time::now(), 53 | properties: Properties::default(), 54 | tags: ContextTags::default(), 55 | } 56 | } 57 | } 58 | 59 | impl Telemetry for MetricTelemetry { 60 | /// Returns the time when this telemetry was measured. 61 | fn timestamp(&self) -> DateTime { 62 | self.timestamp 63 | } 64 | 65 | /// Returns custom properties to submit with the telemetry item. 66 | fn properties(&self) -> &Properties { 67 | &self.properties 68 | } 69 | 70 | /// Returns mutable reference to custom properties. 71 | fn properties_mut(&mut self) -> &mut Properties { 72 | &mut self.properties 73 | } 74 | 75 | /// Returns context data containing extra, optional tags. Overrides values found on client telemetry context. 76 | fn tags(&self) -> &ContextTags { 77 | &self.tags 78 | } 79 | 80 | /// Returns mutable reference to custom tags. 81 | fn tags_mut(&mut self) -> &mut ContextTags { 82 | &mut self.tags 83 | } 84 | } 85 | 86 | impl From<(TelemetryContext, MetricTelemetry)> for Envelope { 87 | fn from((context, telemetry): (TelemetryContext, MetricTelemetry)) -> Self { 88 | Self { 89 | name: "Microsoft.ApplicationInsights.Metric".into(), 90 | time: telemetry.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), 91 | i_key: Some(context.i_key), 92 | tags: Some(ContextTags::combine(context.tags, telemetry.tags).into()), 93 | data: Some(Base::Data(Data::MetricData(MetricData { 94 | metrics: vec![DataPoint { 95 | name: telemetry.name, 96 | kind: Some(DataPointType::Measurement), 97 | value: telemetry.value, 98 | count: Some(1), 99 | ..DataPoint::default() 100 | }], 101 | properties: Some(Properties::combine(context.properties, telemetry.properties).into()), 102 | ..MetricData::default() 103 | }))), 104 | ..Envelope::default() 105 | } 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use std::collections::BTreeMap; 112 | 113 | use chrono::TimeZone; 114 | 115 | use super::*; 116 | use crate::time; 117 | 118 | #[test] 119 | fn it_overrides_properties_from_context() { 120 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 100)); 121 | 122 | let mut context = 123 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 124 | context.properties_mut().insert("test".into(), "ok".into()); 125 | context.properties_mut().insert("no-write".into(), "fail".into()); 126 | 127 | let mut telemetry = MetricTelemetry::new("test", 123.0); 128 | telemetry.properties_mut().insert("no-write".into(), "ok".into()); 129 | 130 | let envelop = Envelope::from((context, telemetry)); 131 | 132 | let expected = Envelope { 133 | name: "Microsoft.ApplicationInsights.Metric".into(), 134 | time: "2019-01-02T03:04:05.100Z".into(), 135 | i_key: Some("instrumentation".into()), 136 | tags: Some(BTreeMap::default()), 137 | data: Some(Base::Data(Data::MetricData(MetricData { 138 | metrics: vec![DataPoint { 139 | name: "test".into(), 140 | kind: Some(DataPointType::Measurement), 141 | value: 123.0, 142 | count: Some(1), 143 | ..DataPoint::default() 144 | }], 145 | properties: Some({ 146 | let mut properties = BTreeMap::default(); 147 | properties.insert("test".into(), "ok".into()); 148 | properties.insert("no-write".into(), "ok".into()); 149 | properties 150 | }), 151 | ..MetricData::default() 152 | }))), 153 | ..Envelope::default() 154 | }; 155 | 156 | assert_eq!(envelop, expected) 157 | } 158 | 159 | #[test] 160 | fn it_overrides_tags_from_context() { 161 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 101)); 162 | 163 | let mut context = 164 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 165 | context.tags_mut().insert("test".into(), "ok".into()); 166 | context.tags_mut().insert("no-write".into(), "fail".into()); 167 | 168 | let mut telemetry = MetricTelemetry::new("test", 123.0); 169 | telemetry.tags_mut().insert("no-write".into(), "ok".into()); 170 | 171 | let envelop = Envelope::from((context, telemetry)); 172 | 173 | let expected = Envelope { 174 | name: "Microsoft.ApplicationInsights.Metric".into(), 175 | time: "2019-01-02T03:04:05.101Z".into(), 176 | i_key: Some("instrumentation".into()), 177 | tags: Some({ 178 | let mut tags = BTreeMap::default(); 179 | tags.insert("test".into(), "ok".into()); 180 | tags.insert("no-write".into(), "ok".into()); 181 | tags 182 | }), 183 | data: Some(Base::Data(Data::MetricData(MetricData { 184 | metrics: vec![DataPoint { 185 | name: "test".into(), 186 | kind: Some(DataPointType::Measurement), 187 | value: 123.0, 188 | count: Some(1), 189 | ..DataPoint::default() 190 | }], 191 | properties: Some(BTreeMap::default()), 192 | ..MetricData::default() 193 | }))), 194 | ..Envelope::default() 195 | }; 196 | 197 | assert_eq!(envelop, expected) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/metric/mod.rs: -------------------------------------------------------------------------------- 1 | mod aggregation; 2 | mod measurement; 3 | mod stats; 4 | 5 | pub use aggregation::*; 6 | pub use measurement::*; 7 | pub use stats::*; 8 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/metric/stats.rs: -------------------------------------------------------------------------------- 1 | /// Stores statistics for aggregated metric. 2 | #[derive(Debug, PartialEq, Default)] 3 | pub struct Stats { 4 | /// Sampled value. 5 | pub(crate) value: f64, 6 | 7 | /// Minimum value of the aggregated metric. 8 | pub(crate) min: f64, 9 | 10 | /// Maximum value of the aggregated metric. 11 | pub(crate) max: f64, 12 | 13 | /// Count of measurements in the sample. 14 | pub(crate) count: i32, 15 | 16 | /// Standard deviation of the aggregated metric. 17 | pub(crate) std_dev: f64, 18 | } 19 | 20 | impl Stats { 21 | /// Adds data points to the aggregate totals included in this telemetry item. 22 | /// This can be used for all the data at once or incrementally. Calculates 23 | /// min, max, sum, count, and std_dev (by way of variance). 24 | pub fn add_data(&mut self, values: &[f64]) { 25 | let variance_sum = if self.std_dev != 0.0 { 26 | self.std_dev * self.std_dev * self.count as f64 27 | } else { 28 | 0.0 29 | }; 30 | 31 | let variance_sum = self.add_values(values, variance_sum); 32 | if self.count > 0 { 33 | let variance = variance_sum / self.count as f64; 34 | self.std_dev = f64::sqrt(variance); 35 | } 36 | } 37 | 38 | /// Adds sampled data points to the aggregate totals included in this telemetry item. 39 | /// This can be used for all the data at once or incrementally. Differs from [add_data](#method.add_data) 40 | /// in how it calculates standard deviation, and should not be used interchangeably 41 | /// with [add_data](#method.add_data) 42 | pub fn add_sampled_data(&mut self, values: &[f64]) { 43 | let variance_sum = if self.std_dev != 0.0 { 44 | self.std_dev * self.std_dev * self.count as f64 45 | } else { 46 | 0.0 47 | }; 48 | 49 | let variance_sum = self.add_values(values, variance_sum); 50 | if self.count > 1 { 51 | let variance = variance_sum / (self.count - 1) as f64; 52 | self.std_dev = f64::sqrt(variance); 53 | } 54 | } 55 | 56 | fn add_values(&mut self, values: &[f64], variance_sum: f64) -> f64 { 57 | let mut variance_sum = variance_sum; 58 | if !values.is_empty() { 59 | // running tally of the mean is important for incremental variance computation 60 | let mut mean = 0.0; 61 | if self.count == 0 { 62 | self.min = values[0]; 63 | self.max = values[0]; 64 | } else { 65 | mean = self.value / self.count as f64; 66 | } 67 | 68 | self.min = values.iter().fold(std::f64::NAN, |x, min| min.min(x)); 69 | self.max = values.iter().fold(std::f64::NAN, |x, max| max.max(x)); 70 | 71 | // Welford's algorithm to compute variance. The divide occurs in the caller. 72 | let mut value = self.value; 73 | let mut count = self.count; 74 | for x in values { 75 | count += 1; 76 | value += *x; 77 | let new_mean = value / count as f64; 78 | variance_sum += (x - mean) * (x - new_mean); 79 | mean = new_mean; 80 | } 81 | self.count = count; 82 | self.value = value; 83 | } 84 | 85 | variance_sum 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use test_case::test_case; 92 | 93 | use super::*; 94 | 95 | #[test_case(&[], 0.0, 0.0, 0.0 ; "for empty collection")] 96 | #[test_case(&[0.0], 0.0, 0.0, 0.0 ; "for single zero value")] 97 | #[test_case(&[50.0], 0.0, 50.0, 50.0 ; "for single non-zero value")] 98 | #[test_case(&[50.0, 50.0], 0.0, 50.0, 50.0 ; "for two equal non-zero values")] 99 | #[test_case(&[50.0, 60.0], 5.0, 50.0, 60.0 ; "for two non-equal non-zero values")] 100 | #[test_case(&[9.0, 10.0, 11.0, 7.0, 13.0], 2.0, 7.0, 13.0 ; "for several values")] 101 | fn it_calculates_stats(values: &[f64], std_dev: f64, min: f64, max: f64) { 102 | let mut stats = Stats::default(); 103 | stats.add_data(values); 104 | 105 | assert_eq!( 106 | stats, 107 | Stats { 108 | value: values.iter().sum(), 109 | min, 110 | max, 111 | count: values.len() as i32, 112 | std_dev, 113 | } 114 | ) 115 | } 116 | 117 | #[test_case(&[], 0.0, 0.0, 0.0 ; "for empty collection")] 118 | #[test_case(&[0.0], 0.0, 0.0, 0.0 ; "for single zero value")] 119 | #[test_case(&[50.0], 0.0, 50.0, 50.0 ; "for single non-zero value")] 120 | #[test_case(&[50.0, 50.0], 0.0, 50.0, 50.0 ; "for two equal non-zero values")] 121 | #[test_case(&[50.0, 60.0], 7.0710678118654755, 50.0, 60.0 ; "for two non-equal non-zero values")] 122 | #[test_case(&[9.0, 10.0, 11.0, 7.0, 13.0], 2.23606797749979, 7.0, 13.0 ; "for several values")] 123 | fn it_calculates_sampled_stats(values: &[f64], std_dev: f64, min: f64, max: f64) { 124 | let mut stats = Stats::default(); 125 | stats.add_sampled_data(values); 126 | 127 | assert_eq!( 128 | stats, 129 | Stats { 130 | value: values.iter().sum(), 131 | min, 132 | max, 133 | count: values.len() as i32, 134 | std_dev, 135 | } 136 | ) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module for Application Insights telemetry items. 2 | mod availability; 3 | mod event; 4 | mod exception; 5 | mod measurements; 6 | mod metric; 7 | mod page_view; 8 | mod properties; 9 | mod remote_dependency; 10 | mod request; 11 | mod tags; 12 | mod trace; 13 | 14 | pub use availability::AvailabilityTelemetry; 15 | pub use event::EventTelemetry; 16 | pub use measurements::Measurements; 17 | pub use metric::{AggregateMetricTelemetry, MetricTelemetry, Stats}; 18 | pub use page_view::PageViewTelemetry; 19 | pub use properties::Properties; 20 | pub use remote_dependency::RemoteDependencyTelemetry; 21 | pub use request::RequestTelemetry; 22 | pub use tags::{ 23 | ApplicationTags, CloudTags, ContextTags, DeviceTags, InternalTags, LocationTags, OperationTags, SessionTags, 24 | UserTags, 25 | }; 26 | pub use trace::{SeverityLevel, TraceTelemetry}; 27 | 28 | use chrono::{DateTime, Utc}; 29 | 30 | /// A trait that provides Application Insights telemetry items. 31 | pub trait Telemetry { 32 | /// Returns the time when this telemetry was measured. 33 | fn timestamp(&self) -> DateTime; 34 | 35 | /// Returns custom properties to submit with the telemetry item. 36 | fn properties(&self) -> &Properties; 37 | 38 | /// Returns mutable reference to custom properties. 39 | fn properties_mut(&mut self) -> &mut Properties; 40 | 41 | /// Returns context data containing extra, optional tags. Overrides values found on client telemetry context. 42 | fn tags(&self) -> &ContextTags; 43 | 44 | /// Returns mutable reference to custom tags. 45 | fn tags_mut(&mut self) -> &mut ContextTags; 46 | } 47 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/page_view.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, SecondsFormat, Utc}; 2 | use http::Uri; 3 | 4 | use crate::{ 5 | context::TelemetryContext, 6 | contracts::{Base, Data, Envelope, PageViewData}, 7 | telemetry::{ContextTags, Measurements, Properties, Telemetry}, 8 | time::{self, Duration}, 9 | uuid::Uuid, 10 | }; 11 | 12 | /// Represents generic actions on a page like a button click. 13 | /// 14 | /// # Examples 15 | /// ```rust, no_run 16 | /// # use appinsights::TelemetryClient; 17 | /// # let client = TelemetryClient::new("".to_string()); 18 | /// use appinsights::telemetry::{Telemetry, PageViewTelemetry}; 19 | /// use http::Uri; 20 | /// use std::time::Duration; 21 | /// 22 | /// // create a telemetry item 23 | /// let mut telemetry = PageViewTelemetry::new( 24 | /// "check github repo page", 25 | /// "https://github.com/dmolokanov/appinsights-rs".parse::().unwrap(), 26 | /// ); 27 | /// 28 | /// // attach custom properties, measurements and context tags 29 | /// telemetry.properties_mut().insert("component".to_string(), "data_processor".to_string()); 30 | /// telemetry.tags_mut().insert("os_version".to_string(), "linux x86_64".to_string()); 31 | /// telemetry.measurements_mut().insert("body_size".to_string(), 115.0); 32 | /// 33 | /// // submit telemetry item to server 34 | /// client.track(telemetry); 35 | /// ``` 36 | #[derive(Debug)] 37 | pub struct PageViewTelemetry { 38 | /// Identifier of a generic action on a page. 39 | /// It is used to correlate a generic action on a page and telemetry generated by the service. 40 | id: Option, 41 | 42 | /// Event name. 43 | name: String, 44 | 45 | /// Request URL with all query string parameters. 46 | uri: Uri, 47 | 48 | /// Request duration. 49 | duration: Option, 50 | 51 | /// The time stamp when this telemetry was measured. 52 | timestamp: DateTime, 53 | 54 | /// Custom properties. 55 | properties: Properties, 56 | 57 | /// Telemetry context containing extra, optional tags. 58 | tags: ContextTags, 59 | 60 | /// Custom measurements. 61 | measurements: Measurements, 62 | } 63 | 64 | impl PageViewTelemetry { 65 | /// Creates a new page view telemetry item with the specified name and url. 66 | pub fn new(name: impl Into, uri: Uri) -> Self { 67 | Self { 68 | id: Option::default(), 69 | name: name.into(), 70 | uri, 71 | duration: Option::default(), 72 | timestamp: time::now(), 73 | properties: Properties::default(), 74 | tags: ContextTags::default(), 75 | measurements: Measurements::default(), 76 | } 77 | } 78 | 79 | /// Returns custom measurements to submit with the telemetry item. 80 | pub fn measurements(&self) -> &Measurements { 81 | &self.measurements 82 | } 83 | 84 | /// Returns mutable reference to custom measurements. 85 | pub fn measurements_mut(&mut self) -> &mut Measurements { 86 | &mut self.measurements 87 | } 88 | } 89 | 90 | impl Telemetry for PageViewTelemetry { 91 | /// Returns the time when this telemetry was measured. 92 | fn timestamp(&self) -> DateTime { 93 | self.timestamp 94 | } 95 | 96 | /// Returns custom properties to submit with the telemetry item. 97 | fn properties(&self) -> &Properties { 98 | &self.properties 99 | } 100 | 101 | /// Returns mutable reference to custom properties. 102 | fn properties_mut(&mut self) -> &mut Properties { 103 | &mut self.properties 104 | } 105 | 106 | /// Returns context data containing extra, optional tags. Overrides values found on client telemetry context. 107 | fn tags(&self) -> &ContextTags { 108 | &self.tags 109 | } 110 | 111 | /// Returns mutable reference to custom tags. 112 | fn tags_mut(&mut self) -> &mut ContextTags { 113 | &mut self.tags 114 | } 115 | } 116 | 117 | impl From<(TelemetryContext, PageViewTelemetry)> for Envelope { 118 | fn from((context, telemetry): (TelemetryContext, PageViewTelemetry)) -> Self { 119 | Self { 120 | name: "Microsoft.ApplicationInsights.PageView".into(), 121 | time: telemetry.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), 122 | i_key: Some(context.i_key), 123 | tags: Some(ContextTags::combine(context.tags, telemetry.tags).into()), 124 | data: Some(Base::Data(Data::PageViewData(PageViewData { 125 | name: telemetry.name, 126 | url: Some(telemetry.uri.to_string()), 127 | duration: telemetry.duration.map(|duration| duration.to_string()), 128 | referrer_uri: None, 129 | id: telemetry 130 | .id 131 | .map(|id| id.as_hyphenated().to_string()) 132 | .unwrap_or_default(), 133 | properties: Some(Properties::combine(context.properties, telemetry.properties).into()), 134 | measurements: Some(telemetry.measurements.into()), 135 | ..PageViewData::default() 136 | }))), 137 | ..Envelope::default() 138 | } 139 | } 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use std::collections::BTreeMap; 145 | 146 | use chrono::TimeZone; 147 | 148 | use super::*; 149 | 150 | #[test] 151 | fn it_overrides_properties_from_context() { 152 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 800)); 153 | 154 | let mut context = 155 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 156 | context.properties_mut().insert("test".into(), "ok".into()); 157 | context.properties_mut().insert("no-write".into(), "fail".into()); 158 | 159 | let mut telemetry = PageViewTelemetry::new("page updated", "https://example.com/main.html".parse().unwrap()); 160 | telemetry.properties_mut().insert("no-write".into(), "ok".into()); 161 | telemetry.measurements_mut().insert("latency".into(), 200.0); 162 | 163 | let envelop = Envelope::from((context, telemetry)); 164 | 165 | let expected = Envelope { 166 | name: "Microsoft.ApplicationInsights.PageView".into(), 167 | time: "2019-01-02T03:04:05.800Z".into(), 168 | i_key: Some("instrumentation".into()), 169 | tags: Some(BTreeMap::default()), 170 | data: Some(Base::Data(Data::PageViewData(PageViewData { 171 | name: "page updated".into(), 172 | url: Some("https://example.com/main.html".into()), 173 | properties: Some({ 174 | let mut properties = BTreeMap::default(); 175 | properties.insert("test".into(), "ok".into()); 176 | properties.insert("no-write".into(), "ok".into()); 177 | properties 178 | }), 179 | measurements: Some({ 180 | let mut measurement = BTreeMap::default(); 181 | measurement.insert("latency".into(), 200.0); 182 | measurement 183 | }), 184 | ..PageViewData::default() 185 | }))), 186 | ..Envelope::default() 187 | }; 188 | 189 | assert_eq!(envelop, expected) 190 | } 191 | 192 | #[test] 193 | fn it_overrides_tags_from_context() { 194 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 700)); 195 | 196 | let mut context = 197 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 198 | context.tags_mut().insert("test".into(), "ok".into()); 199 | context.tags_mut().insert("no-write".into(), "fail".into()); 200 | 201 | let mut telemetry = PageViewTelemetry::new("page updated", "https://example.com/main.html".parse().unwrap()); 202 | telemetry.tags_mut().insert("no-write".into(), "ok".into()); 203 | 204 | let envelop = Envelope::from((context, telemetry)); 205 | 206 | let expected = Envelope { 207 | name: "Microsoft.ApplicationInsights.PageView".into(), 208 | time: "2019-01-02T03:04:05.700Z".into(), 209 | i_key: Some("instrumentation".into()), 210 | tags: Some({ 211 | let mut tags = BTreeMap::default(); 212 | tags.insert("test".into(), "ok".into()); 213 | tags.insert("no-write".into(), "ok".into()); 214 | tags 215 | }), 216 | data: Some(Base::Data(Data::PageViewData(PageViewData { 217 | name: "page updated".into(), 218 | url: Some("https://example.com/main.html".into()), 219 | properties: Some(BTreeMap::default()), 220 | measurements: Some(BTreeMap::default()), 221 | ..PageViewData::default() 222 | }))), 223 | ..Envelope::default() 224 | }; 225 | 226 | assert_eq!(envelop, expected) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/properties.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | 6 | /// Contains all properties for telemetry to submit. 7 | #[derive(Debug, Clone, Default)] 8 | pub struct Properties(BTreeMap); 9 | 10 | impl Properties { 11 | /// Combines all properties from two objects. It can override some properties with values found 12 | /// in the second properties bag. 13 | pub fn combine(a: Properties, b: Properties) -> Self { 14 | let items = a.0.into_iter().chain(b.0).collect(); 15 | Self(items) 16 | } 17 | } 18 | 19 | impl From for BTreeMap { 20 | fn from(properties: Properties) -> Self { 21 | properties.0 22 | } 23 | } 24 | 25 | impl Deref for Properties { 26 | type Target = BTreeMap; 27 | 28 | fn deref(&self) -> &Self::Target { 29 | &self.0 30 | } 31 | } 32 | 33 | impl DerefMut for Properties { 34 | fn deref_mut(&mut self) -> &mut Self::Target { 35 | &mut self.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /appinsights/src/telemetry/trace.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, SecondsFormat, Utc}; 2 | 3 | use crate::{ 4 | context::TelemetryContext, 5 | contracts::{SeverityLevel as ContractsSeverityLevel, *}, 6 | telemetry::{ContextTags, Measurements, Properties, Telemetry}, 7 | time, 8 | }; 9 | 10 | /// Represents printf-like trace statements that can be text searched. A trace telemetry items have 11 | /// a message and an associated [`SeverityLevel`](enum.SeverityLevel.html). 12 | /// 13 | /// # Examples 14 | /// ```rust, no_run 15 | /// # use appinsights::TelemetryClient; 16 | /// # let client = TelemetryClient::new("".to_string()); 17 | /// use appinsights::telemetry::{TraceTelemetry, SeverityLevel, Telemetry}; 18 | /// 19 | /// // create a telemetry item 20 | /// let mut telemetry = TraceTelemetry::new("Starting data processing", SeverityLevel::Information); 21 | /// 22 | /// // attach custom properties, measurements and context tags 23 | /// telemetry.properties_mut().insert("component".to_string(), "data_processor".to_string()); 24 | /// telemetry.tags_mut().insert("os_version".to_string(), "linux x86_64".to_string()); 25 | /// telemetry.measurements_mut().insert("records_count".to_string(), 115.0); 26 | /// 27 | /// // submit telemetry item to server 28 | /// client.track(telemetry); 29 | /// ``` 30 | #[derive(Debug)] 31 | pub struct TraceTelemetry { 32 | /// A trace message. 33 | message: String, 34 | 35 | /// Severity level. 36 | severity: SeverityLevel, 37 | 38 | /// The time stamp when this telemetry was measured. 39 | timestamp: DateTime, 40 | 41 | /// Custom properties. 42 | properties: Properties, 43 | 44 | /// Telemetry context containing extra, optional tags. 45 | tags: ContextTags, 46 | 47 | /// Custom measurements. 48 | measurements: Measurements, 49 | } 50 | 51 | impl TraceTelemetry { 52 | /// Creates an event telemetry item with specified name. 53 | pub fn new(message: impl Into, severity: SeverityLevel) -> Self { 54 | Self { 55 | message: message.into(), 56 | severity, 57 | timestamp: time::now(), 58 | properties: Properties::default(), 59 | tags: ContextTags::default(), 60 | measurements: Measurements::default(), 61 | } 62 | } 63 | 64 | /// Returns custom measurements to submit with the telemetry item. 65 | pub fn measurements(&self) -> &Measurements { 66 | &self.measurements 67 | } 68 | 69 | /// Returns mutable reference to custom measurements. 70 | pub fn measurements_mut(&mut self) -> &mut Measurements { 71 | &mut self.measurements 72 | } 73 | } 74 | 75 | impl Telemetry for TraceTelemetry { 76 | /// Returns the time when this telemetry was measured. 77 | fn timestamp(&self) -> DateTime { 78 | self.timestamp 79 | } 80 | 81 | /// Returns custom properties to submit with the telemetry item. 82 | fn properties(&self) -> &Properties { 83 | &self.properties 84 | } 85 | 86 | /// Returns mutable reference to custom properties. 87 | fn properties_mut(&mut self) -> &mut Properties { 88 | &mut self.properties 89 | } 90 | 91 | /// Returns context data containing extra, optional tags. Overrides values found on client telemetry context. 92 | fn tags(&self) -> &ContextTags { 93 | &self.tags 94 | } 95 | 96 | /// Returns mutable reference to custom tags. 97 | fn tags_mut(&mut self) -> &mut ContextTags { 98 | &mut self.tags 99 | } 100 | } 101 | 102 | impl From<(TelemetryContext, TraceTelemetry)> for Envelope { 103 | fn from((context, telemetry): (TelemetryContext, TraceTelemetry)) -> Self { 104 | Self { 105 | name: "Microsoft.ApplicationInsights.Message".into(), 106 | time: telemetry.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), 107 | i_key: Some(context.i_key), 108 | tags: Some(ContextTags::combine(context.tags, telemetry.tags).into()), 109 | data: Some(Base::Data(Data::MessageData(MessageData { 110 | message: telemetry.message, 111 | severity_level: Some(telemetry.severity.into()), 112 | properties: Some(Properties::combine(context.properties, telemetry.properties).into()), 113 | measurements: Some(telemetry.measurements.into()), 114 | ..MessageData::default() 115 | }))), 116 | ..Envelope::default() 117 | } 118 | } 119 | } 120 | 121 | /// Defines the level of severity for the event. 122 | #[derive(Debug)] 123 | pub enum SeverityLevel { 124 | /// Verbose severity level. 125 | Verbose, 126 | 127 | /// Information severity level. 128 | Information, 129 | 130 | /// Warning severity level. 131 | Warning, 132 | 133 | /// Error severity level. 134 | Error, 135 | 136 | /// Critical severity level. 137 | Critical, 138 | } 139 | 140 | impl From for ContractsSeverityLevel { 141 | fn from(severity: SeverityLevel) -> Self { 142 | match severity { 143 | SeverityLevel::Verbose => ContractsSeverityLevel::Verbose, 144 | SeverityLevel::Information => ContractsSeverityLevel::Information, 145 | SeverityLevel::Warning => ContractsSeverityLevel::Warning, 146 | SeverityLevel::Error => ContractsSeverityLevel::Error, 147 | SeverityLevel::Critical => ContractsSeverityLevel::Critical, 148 | } 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use std::collections::BTreeMap; 155 | 156 | use chrono::{TimeZone, Utc}; 157 | 158 | use super::{SeverityLevel, TraceTelemetry}; 159 | use crate::{ 160 | contracts::{Base, Data, Envelope, MessageData}, 161 | telemetry::{ContextTags, Properties, Telemetry}, 162 | time, TelemetryContext, 163 | }; 164 | 165 | #[test] 166 | fn it_overrides_properties_from_context() { 167 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 800)); 168 | 169 | let mut context = 170 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 171 | context.properties_mut().insert("test".into(), "ok".into()); 172 | context.properties_mut().insert("no-write".into(), "fail".into()); 173 | 174 | let mut telemetry = TraceTelemetry::new("message", SeverityLevel::Information); 175 | telemetry.properties_mut().insert("no-write".into(), "ok".into()); 176 | telemetry.measurements_mut().insert("value".into(), 5.0); 177 | 178 | let envelop = Envelope::from((context, telemetry)); 179 | 180 | let expected = Envelope { 181 | name: "Microsoft.ApplicationInsights.Message".into(), 182 | time: "2019-01-02T03:04:05.800Z".into(), 183 | i_key: Some("instrumentation".into()), 184 | tags: Some(BTreeMap::default()), 185 | data: Some(Base::Data(Data::MessageData(MessageData { 186 | message: "message".into(), 187 | severity_level: Some(crate::contracts::SeverityLevel::Information), 188 | properties: Some({ 189 | let mut properties = BTreeMap::default(); 190 | properties.insert("test".into(), "ok".into()); 191 | properties.insert("no-write".into(), "ok".into()); 192 | properties 193 | }), 194 | measurements: Some({ 195 | let mut measurements = BTreeMap::default(); 196 | measurements.insert("value".into(), 5.0); 197 | measurements 198 | }), 199 | ..MessageData::default() 200 | }))), 201 | ..Envelope::default() 202 | }; 203 | 204 | assert_eq!(envelop, expected) 205 | } 206 | 207 | #[test] 208 | fn it_overrides_tags_from_context() { 209 | time::set(Utc.ymd(2019, 1, 2).and_hms_milli(3, 4, 5, 700)); 210 | 211 | let mut context = 212 | TelemetryContext::new("instrumentation".into(), ContextTags::default(), Properties::default()); 213 | context.tags_mut().insert("test".into(), "ok".into()); 214 | context.tags_mut().insert("no-write".into(), "fail".into()); 215 | 216 | let mut telemetry = TraceTelemetry::new("message", SeverityLevel::Information); 217 | telemetry.tags_mut().insert("no-write".into(), "ok".into()); 218 | 219 | let envelop = Envelope::from((context, telemetry)); 220 | 221 | let expected = Envelope { 222 | name: "Microsoft.ApplicationInsights.Message".into(), 223 | time: "2019-01-02T03:04:05.700Z".into(), 224 | i_key: Some("instrumentation".into()), 225 | tags: Some({ 226 | let mut tags = BTreeMap::default(); 227 | tags.insert("test".into(), "ok".into()); 228 | tags.insert("no-write".into(), "ok".into()); 229 | tags 230 | }), 231 | data: Some(Base::Data(Data::MessageData(MessageData { 232 | message: "message".into(), 233 | severity_level: Some(crate::contracts::SeverityLevel::Information), 234 | properties: Some(BTreeMap::default()), 235 | measurements: Some(BTreeMap::default()), 236 | ..MessageData::default() 237 | }))), 238 | ..Envelope::default() 239 | }; 240 | 241 | assert_eq!(envelop, expected) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /appinsights/src/time.rs: -------------------------------------------------------------------------------- 1 | pub use imp::*; 2 | 3 | use std::{ 4 | fmt::{Display, Formatter}, 5 | ops::Deref, 6 | time::Duration as StdDuration, 7 | }; 8 | 9 | #[cfg(not(test))] 10 | mod imp { 11 | use chrono::{DateTime, Utc}; 12 | 13 | /// Returns a DateTime which corresponds to a current date. 14 | pub fn now() -> DateTime { 15 | Utc::now() 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod imp { 21 | use std::cell::RefCell; 22 | 23 | use chrono::{DateTime, Utc}; 24 | 25 | thread_local!(static NOW: RefCell>> = RefCell::new(None)); 26 | 27 | /// Returns a DateTime which corresponds to a current date or the value user set in advance. 28 | pub fn now() -> DateTime { 29 | NOW.with(|ts| if let Some(now) = *ts.borrow() { now } else { Utc::now() }) 30 | } 31 | 32 | /// Sets known DateTime value as now to assert test against it. 33 | pub fn set(now: DateTime) { 34 | NOW.with(|ts| *ts.borrow_mut() = Some(now)) 35 | } 36 | 37 | /// Resets pre-defined DateTime value to use Utc::now() instead. 38 | pub fn reset() { 39 | NOW.with(|ts| *ts.borrow_mut() = None) 40 | } 41 | } 42 | 43 | /// Provides dotnet duration aware formatting rules. 44 | #[derive(Debug)] 45 | pub struct Duration(StdDuration); 46 | 47 | impl From for Duration { 48 | fn from(duration: StdDuration) -> Self { 49 | Duration(duration) 50 | } 51 | } 52 | 53 | impl Display for Duration { 54 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 55 | let nanoseconds = self.0.as_nanos(); 56 | let ticks = nanoseconds / 100 % 10_000_000; 57 | let total_seconds = nanoseconds / 1_000_000_000; 58 | let seconds = total_seconds % 60; 59 | let minutes = total_seconds / 60 % 60; 60 | let hours = total_seconds / 3600 % 24; 61 | let days = total_seconds / 86400; 62 | 63 | write!( 64 | f, 65 | "{}.{:0>2}:{:0>2}:{:0>2}.{:0>7}", 66 | days, hours, minutes, seconds, ticks 67 | ) 68 | } 69 | } 70 | 71 | impl Deref for Duration { 72 | type Target = StdDuration; 73 | 74 | fn deref(&self) -> &Self::Target { 75 | &self.0 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use chrono::{TimeZone, Utc}; 82 | use test_case::test_case; 83 | 84 | use super::*; 85 | 86 | #[test_case(StdDuration::from_secs(3600).into(), "0.01:00:00.0000000" ; "hour")] 87 | #[test_case(StdDuration::from_secs(60).into(), "0.00:01:00.0000000" ; "minute")] 88 | #[test_case(StdDuration::from_secs(1).into(), "0.00:00:01.0000000" ; "second")] 89 | #[test_case(StdDuration::from_millis(1).into(), "0.00:00:00.0010000" ; "millisecond")] 90 | #[test_case(StdDuration::from_nanos(100).into(), "0.00:00:00.0000001" ; "tick")] 91 | #[test_case((Utc.ymd(2019, 1, 3).and_hms(1, 2, 3) - Utc.ymd(2019, 1, 1).and_hms(0, 0, 0)).to_std().unwrap().into(), "2.01:02:03.0000000" ; "custom")] 92 | fn it_converts_duration_to_string(duration: Duration, expected: &'static str) { 93 | assert_eq!(duration.to_string(), expected.to_string()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /appinsights/src/timeout.rs: -------------------------------------------------------------------------------- 1 | pub use imp::*; 2 | 3 | #[cfg(not(test))] 4 | mod imp { 5 | use std::time::Duration; 6 | 7 | use tokio::time::{self, Instant}; 8 | 9 | /// Creates a receiver that reliably delivers only one message when given interval expires. 10 | pub async fn sleep(duration: Duration) { 11 | let timeout = Instant::now() + duration; 12 | time::sleep_until(timeout).await; 13 | } 14 | } 15 | 16 | #[cfg(test)] 17 | mod imp { 18 | use std::{sync::Arc, time::Duration}; 19 | 20 | use lazy_static::lazy_static; 21 | use parking_lot::Mutex; 22 | use tokio::{sync::Notify, time::Instant}; 23 | 24 | lazy_static! { 25 | static ref CHANNEL: Mutex>> = Mutex::new(None); 26 | } 27 | 28 | /// Initializes a channel which emulates timeout expiration event. External code should run 29 | /// [`expire`](#method.expire) method in order to emulate timeout expiration. 30 | pub fn init() { 31 | let mut channel = CHANNEL.lock(); 32 | *channel = Some(Arc::new(Notify::new())); 33 | } 34 | 35 | /// Creates a copy of a receiver that delivers a current time stamp in order to emulate 36 | /// timeout expiration for tests. 37 | pub async fn sleep(duration: Duration) { 38 | let maybe_notify = CHANNEL.lock().clone(); 39 | 40 | if let Some(notify) = maybe_notify { 41 | notify.notified().await; 42 | } else { 43 | let timeout = Instant::now() + duration; 44 | tokio::time::sleep_until(timeout).await; 45 | } 46 | } 47 | 48 | /// Emulates timeout expiration event. 49 | /// It sends a current time stamp to receiver in order to trigger an action if a channel was 50 | /// initialized in advance. Does nothing otherwise. 51 | pub fn expire() { 52 | if let Some(notify) = CHANNEL.lock().clone() { 53 | log::error!("notify_one"); 54 | notify.notify_one(); 55 | } 56 | } 57 | 58 | /// Resets a channel that emulates timeout expiration event with default 59 | /// timer base timeout expiration instead. 60 | pub fn reset() { 61 | let mut channel = CHANNEL.lock(); 62 | *channel = None; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /appinsights/src/uuid.rs: -------------------------------------------------------------------------------- 1 | pub use imp::*; 2 | pub use uuid::Uuid; 3 | 4 | #[cfg(not(test))] 5 | mod imp { 6 | use uuid::Uuid; 7 | 8 | /// Generates a new instance of unique identifier. 9 | pub fn new() -> Uuid { 10 | Uuid::new_v4() 11 | } 12 | } 13 | 14 | #[cfg(test)] 15 | mod imp { 16 | use std::cell::RefCell; 17 | 18 | use uuid::Uuid; 19 | 20 | thread_local!(static ID: RefCell> = RefCell::new(None)); 21 | 22 | /// Generates a new instance of unique identifier or predefined value to test against it. 23 | pub fn new() -> Uuid { 24 | ID.with(|is| { 25 | if let Some(id) = *is.borrow() { 26 | id 27 | } else { 28 | Uuid::new_v4() 29 | } 30 | }) 31 | } 32 | 33 | /// Sets known Uuid value as now to assert test against it. 34 | pub fn set(uuid: Uuid) { 35 | ID.with(|is| *is.borrow_mut() = Some(uuid)) 36 | } 37 | 38 | /// Resets pre-defined Uuid value to use Uuid::new_v4() instead. 39 | pub fn reset() { 40 | ID.with(|is| *is.borrow_mut() = None) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /appinsights/tests/logger.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::{Arc, RwLock}, 3 | time::Duration, 4 | }; 5 | 6 | use chrono::Utc; 7 | use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError}; 8 | 9 | pub async fn wait_until(entries: &Arc>>, msg: &str, panic_after: Duration) { 10 | let panic_after = Utc::now() + chrono::Duration::from_std(panic_after).unwrap(); 11 | loop { 12 | let entries = entries.read().unwrap(); 13 | if entries.iter().any(|entry| entry.contains(msg)) { 14 | break; 15 | } 16 | drop(entries); 17 | 18 | if Utc::now() > panic_after { 19 | panic!("Test took too long to finish"); 20 | } 21 | tokio::time::sleep(Duration::from_millis(100)).await 22 | } 23 | } 24 | 25 | pub fn wait_until_blocking(entries: &Arc>>, msg: &str, panic_after: Duration) { 26 | let panic_after = Utc::now() + chrono::Duration::from_std(panic_after).unwrap(); 27 | loop { 28 | let entries = entries.read().unwrap(); 29 | if entries.iter().any(|entry| entry.contains(msg)) { 30 | break; 31 | } 32 | drop(entries); 33 | 34 | if Utc::now() > panic_after { 35 | panic!("Test took too long to finish"); 36 | } 37 | std::thread::sleep(Duration::from_millis(100)) 38 | } 39 | } 40 | 41 | pub fn init(entries: Arc>>) { 42 | builder(entries).init() 43 | } 44 | 45 | pub fn builder(entries: Arc>>) -> Builder { 46 | Builder::new(entries) 47 | } 48 | 49 | pub struct Builder { 50 | entries: Arc>>, 51 | level: Level, 52 | output: bool, 53 | } 54 | 55 | impl Builder { 56 | pub fn new(entries: Arc>>) -> Self { 57 | Self { 58 | entries, 59 | level: Level::Debug, 60 | output: false, 61 | } 62 | } 63 | 64 | pub fn level(&mut self, level: Level) -> &mut Self { 65 | self.level = level; 66 | self 67 | } 68 | 69 | pub fn output(&mut self, output: bool) -> &mut Self { 70 | self.output = output; 71 | self 72 | } 73 | 74 | pub fn init(&mut self) { 75 | self.try_init().expect("Builder::init failed") 76 | } 77 | 78 | pub fn try_init(&mut self) -> Result<(), SetLoggerError> { 79 | let logger = MemoryLogger { 80 | entries: self.entries.clone(), 81 | level: self.level, 82 | output: self.output, 83 | }; 84 | 85 | log::set_boxed_logger(Box::new(logger))?; 86 | log::set_max_level(LevelFilter::Trace); 87 | Ok(()) 88 | } 89 | } 90 | 91 | struct MemoryLogger { 92 | entries: Arc>>, 93 | level: Level, 94 | output: bool, 95 | } 96 | 97 | impl Log for MemoryLogger { 98 | fn enabled(&self, metadata: &Metadata) -> bool { 99 | metadata.level() <= self.level 100 | } 101 | 102 | fn log(&self, record: &Record) { 103 | if !self.enabled(record.metadata()) { 104 | return; 105 | } 106 | 107 | let entry = format!( 108 | "{} {:<5} [{}] {}", 109 | chrono::Utc::now().to_rfc3339(), 110 | record.level(), 111 | if !record.target().is_empty() { 112 | record.target() 113 | } else { 114 | record.module_path().unwrap_or_default() 115 | }, 116 | record.args() 117 | ); 118 | 119 | if self.output { 120 | println!("{}", entry); 121 | } 122 | 123 | let mut entries = self.entries.write().unwrap(); 124 | entries.push(entry); 125 | } 126 | 127 | fn flush(&self) {} 128 | } 129 | -------------------------------------------------------------------------------- /appinsights/tests/telemetry.rs: -------------------------------------------------------------------------------- 1 | mod logger; 2 | 3 | use std::{ 4 | env, 5 | sync::{Arc, RwLock}, 6 | time::Duration, 7 | }; 8 | 9 | use appinsights::{telemetry::SeverityLevel, TelemetryClient}; 10 | use hyper::{Method, Uri}; 11 | 12 | #[tokio::test] 13 | async fn it_tracks_all_telemetry_items() { 14 | let entries = Arc::new(RwLock::new(Vec::new())); 15 | logger::builder(entries.clone()).output(true).init(); 16 | 17 | let i_key = env::var("APPINSIGHTS_INSTRUMENTATIONKEY").expect("Set APPINSIGHTS_INSTRUMENTATIONKEY first"); 18 | let ai = TelemetryClient::new(i_key); 19 | 20 | ai.track_event("event happened"); 21 | ai.track_trace("Unable to connect to a gateway", SeverityLevel::Warning); 22 | ai.track_metric("gateway_latency_ms", 113.0); 23 | ai.track_request( 24 | Method::GET, 25 | "https://api.github.com/dmolokanov/appinsights-rs" 26 | .parse::() 27 | .unwrap(), 28 | Duration::from_millis(100), 29 | "200".to_string(), 30 | ); 31 | ai.track_remote_dependency( 32 | "GET https://api.github.com/dmolokanov/appinsights-rs", 33 | "HTTP", 34 | "api.github.com", 35 | true, 36 | ); 37 | ai.track_availability( 38 | "GET https://api.github.com/dmolokanov/appinsights-rs", 39 | Duration::from_secs(2), 40 | true, 41 | ); 42 | 43 | ai.close_channel().await; 44 | 45 | logger::wait_until(&entries, "Successfully sent 6 items", Duration::from_secs(10)).await; 46 | } 47 | -------------------------------------------------------------------------------- /appinsights/tests/telemetry_blocking.rs: -------------------------------------------------------------------------------- 1 | mod logger; 2 | 3 | use std::{ 4 | env, 5 | sync::{Arc, RwLock}, 6 | time::Duration, 7 | }; 8 | 9 | use appinsights::{blocking::TelemetryClient, telemetry::SeverityLevel}; 10 | use hyper::{Method, Uri}; 11 | 12 | #[test] 13 | fn it_tracks_all_telemetry_items() { 14 | let entries = Arc::new(RwLock::new(Vec::new())); 15 | logger::builder(entries.clone()).output(true).init(); 16 | 17 | let i_key = env::var("APPINSIGHTS_INSTRUMENTATIONKEY").expect("Set APPINSIGHTS_INSTRUMENTATIONKEY first"); 18 | let ai = TelemetryClient::new(i_key); 19 | 20 | ai.track_event("event happened"); 21 | ai.track_trace("Unable to connect to a gateway", SeverityLevel::Warning); 22 | ai.track_metric("gateway_latency_ms", 113.0); 23 | ai.track_request( 24 | Method::GET, 25 | "https://api.github.com/dmolokanov/appinsights-rs" 26 | .parse::() 27 | .unwrap(), 28 | Duration::from_millis(100), 29 | "200".to_string(), 30 | ); 31 | ai.track_remote_dependency( 32 | "GET https://api.github.com/dmolokanov/appinsights-rs", 33 | "HTTP", 34 | "api.github.com", 35 | true, 36 | ); 37 | ai.track_availability( 38 | "GET https://api.github.com/dmolokanov/appinsights-rs", 39 | Duration::from_secs(2), 40 | true, 41 | ); 42 | 43 | ai.close_channel(); 44 | 45 | logger::wait_until_blocking(&entries, "Successfully sent 6 items", Duration::from_secs(10)); 46 | } 47 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 --------------------------------------------------------------------------------