├── .github └── workflows │ └── build.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── builder.rs ├── layer.rs └── shutdown.rs ├── loki-api ├── CHANGELOG.md ├── Cargo.toml ├── generate │ ├── Cargo.toml │ ├── build.rs │ ├── proto │ │ ├── github.com │ │ │ └── gogo │ │ │ │ └── protobuf │ │ │ │ └── gogoproto │ │ │ │ └── gogo.proto │ │ ├── logproto.proto │ │ └── pkg │ │ │ └── logqlmodel │ │ │ └── stats │ │ │ └── stats.proto │ └── src │ │ └── main.rs └── src │ ├── lib.rs │ ├── logproto.rs │ └── stats.rs └── src ├── builder.rs ├── labels.rs ├── level_map.rs ├── lib.rs ├── log_support.rs └── no_subscriber.rs /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build and test 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | rust: 13 | - stable 14 | - nightly 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: Swatinem/rust-cache@v2 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: ${{ matrix.rust }} 22 | override: true 23 | - if: ${{ matrix.rust == 'stable' }} 24 | run: cargo fmt -- --color=always --check 25 | - run: cargo build 26 | - run: cargo test 27 | - run: cargo bench 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Recent changes (tracing-loki) 2 | ============================= 3 | 4 | 0.2.4 (2023-08-01) 5 | ------------------ 6 | 7 | - Use explicitly specified `parent` for events by @gdesmott. 8 | 9 | 0.2.3 (2023-06-16) 10 | ------------------ 11 | 12 | - Allow clean shutdown using the `BackgroundTaskController` obtained from 13 | `Builder::build_controller_url`. Check `examples/shutdown.rs` for an example. 14 | 15 | 0.2.2 (2023-03-08) 16 | ------------------ 17 | 18 | - Change to a builder API for configuring the logging. 19 | - Allow specifying paths in the Loki URL by @kellerkindt. 20 | - Allow setting HTTP headers for Loki requests. This allows setting a tenant ID 21 | via the `X-Scope-OrgID` header. Idea and initial implementation by 22 | @TheSamabo. 23 | 24 | 0.2.1 (2022-07-02) 25 | ------------------ 26 | 27 | - Allow to select reqwest backend using feature flags by @greaka. 28 | 29 | 0.2.0 (2022-05-12) 30 | ------------------ 31 | 32 | - Add span fields to the serialized events by @chrismanning 33 | - Change level to string mapping by @juumixx (each level is now differently 34 | colored by Loki, previously: "debug", "informational", "notice", "warning", 35 | "error"; now "trace", "debug", "info", "warn", "error" like the `tracing` 36 | levels. 37 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "anyhow" 25 | version = "1.0.56" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.1.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 34 | 35 | [[package]] 36 | name = "base64" 37 | version = "0.13.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 40 | 41 | [[package]] 42 | name = "bitflags" 43 | version = "1.3.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 46 | 47 | [[package]] 48 | name = "bumpalo" 49 | version = "3.9.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 52 | 53 | [[package]] 54 | name = "bytes" 55 | version = "1.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 58 | 59 | [[package]] 60 | name = "cc" 61 | version = "1.0.73" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 64 | 65 | [[package]] 66 | name = "cfg-if" 67 | version = "1.0.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 70 | 71 | [[package]] 72 | name = "core-foundation" 73 | version = "0.9.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 76 | dependencies = [ 77 | "core-foundation-sys", 78 | "libc", 79 | ] 80 | 81 | [[package]] 82 | name = "core-foundation-sys" 83 | version = "0.8.3" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 86 | 87 | [[package]] 88 | name = "either" 89 | version = "1.6.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 92 | 93 | [[package]] 94 | name = "encoding_rs" 95 | version = "0.8.30" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" 98 | dependencies = [ 99 | "cfg-if", 100 | ] 101 | 102 | [[package]] 103 | name = "fastrand" 104 | version = "1.7.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 107 | dependencies = [ 108 | "instant", 109 | ] 110 | 111 | [[package]] 112 | name = "fixedbitset" 113 | version = "0.4.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" 116 | 117 | [[package]] 118 | name = "fnv" 119 | version = "1.0.7" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 122 | 123 | [[package]] 124 | name = "foreign-types" 125 | version = "0.3.2" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 128 | dependencies = [ 129 | "foreign-types-shared", 130 | ] 131 | 132 | [[package]] 133 | name = "foreign-types-shared" 134 | version = "0.1.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 137 | 138 | [[package]] 139 | name = "form_urlencoded" 140 | version = "1.0.1" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 143 | dependencies = [ 144 | "matches", 145 | "percent-encoding", 146 | ] 147 | 148 | [[package]] 149 | name = "futures-channel" 150 | version = "0.3.21" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 153 | dependencies = [ 154 | "futures-core", 155 | ] 156 | 157 | [[package]] 158 | name = "futures-core" 159 | version = "0.3.21" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 162 | 163 | [[package]] 164 | name = "futures-sink" 165 | version = "0.3.21" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 168 | 169 | [[package]] 170 | name = "futures-task" 171 | version = "0.3.21" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 174 | 175 | [[package]] 176 | name = "futures-util" 177 | version = "0.3.21" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 180 | dependencies = [ 181 | "futures-core", 182 | "futures-task", 183 | "pin-project-lite", 184 | "pin-utils", 185 | ] 186 | 187 | [[package]] 188 | name = "h2" 189 | version = "0.3.12" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b" 192 | dependencies = [ 193 | "bytes", 194 | "fnv", 195 | "futures-core", 196 | "futures-sink", 197 | "futures-util", 198 | "http", 199 | "indexmap", 200 | "slab", 201 | "tokio", 202 | "tokio-util", 203 | "tracing", 204 | ] 205 | 206 | [[package]] 207 | name = "hashbrown" 208 | version = "0.11.2" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 211 | 212 | [[package]] 213 | name = "heck" 214 | version = "0.3.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 217 | dependencies = [ 218 | "unicode-segmentation", 219 | ] 220 | 221 | [[package]] 222 | name = "hermit-abi" 223 | version = "0.1.19" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 226 | dependencies = [ 227 | "libc", 228 | ] 229 | 230 | [[package]] 231 | name = "http" 232 | version = "0.2.6" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" 235 | dependencies = [ 236 | "bytes", 237 | "fnv", 238 | "itoa", 239 | ] 240 | 241 | [[package]] 242 | name = "http-body" 243 | version = "0.4.4" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" 246 | dependencies = [ 247 | "bytes", 248 | "http", 249 | "pin-project-lite", 250 | ] 251 | 252 | [[package]] 253 | name = "httparse" 254 | version = "1.6.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" 257 | 258 | [[package]] 259 | name = "httpdate" 260 | version = "1.0.2" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 263 | 264 | [[package]] 265 | name = "hyper" 266 | version = "0.14.17" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" 269 | dependencies = [ 270 | "bytes", 271 | "futures-channel", 272 | "futures-core", 273 | "futures-util", 274 | "h2", 275 | "http", 276 | "http-body", 277 | "httparse", 278 | "httpdate", 279 | "itoa", 280 | "pin-project-lite", 281 | "socket2", 282 | "tokio", 283 | "tower-service", 284 | "tracing", 285 | "want", 286 | ] 287 | 288 | [[package]] 289 | name = "hyper-rustls" 290 | version = "0.23.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" 293 | dependencies = [ 294 | "http", 295 | "hyper", 296 | "rustls", 297 | "tokio", 298 | "tokio-rustls", 299 | ] 300 | 301 | [[package]] 302 | name = "hyper-tls" 303 | version = "0.5.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 306 | dependencies = [ 307 | "bytes", 308 | "hyper", 309 | "native-tls", 310 | "tokio", 311 | "tokio-native-tls", 312 | ] 313 | 314 | [[package]] 315 | name = "idna" 316 | version = "0.2.3" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 319 | dependencies = [ 320 | "matches", 321 | "unicode-bidi", 322 | "unicode-normalization", 323 | ] 324 | 325 | [[package]] 326 | name = "indexmap" 327 | version = "1.8.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 330 | dependencies = [ 331 | "autocfg", 332 | "hashbrown", 333 | ] 334 | 335 | [[package]] 336 | name = "instant" 337 | version = "0.1.12" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 340 | dependencies = [ 341 | "cfg-if", 342 | ] 343 | 344 | [[package]] 345 | name = "ipnet" 346 | version = "2.4.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c" 349 | 350 | [[package]] 351 | name = "itertools" 352 | version = "0.10.3" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 355 | dependencies = [ 356 | "either", 357 | ] 358 | 359 | [[package]] 360 | name = "itoa" 361 | version = "1.0.1" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 364 | 365 | [[package]] 366 | name = "js-sys" 367 | version = "0.3.56" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" 370 | dependencies = [ 371 | "wasm-bindgen", 372 | ] 373 | 374 | [[package]] 375 | name = "lazy_static" 376 | version = "1.4.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 379 | 380 | [[package]] 381 | name = "libc" 382 | version = "0.2.120" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09" 385 | 386 | [[package]] 387 | name = "log" 388 | version = "0.4.14" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 391 | dependencies = [ 392 | "cfg-if", 393 | ] 394 | 395 | [[package]] 396 | name = "loki-api" 397 | version = "0.1.3" 398 | dependencies = [ 399 | "prost", 400 | "prost-types", 401 | ] 402 | 403 | [[package]] 404 | name = "loki-api-generate" 405 | version = "0.0.1" 406 | dependencies = [ 407 | "prost-build", 408 | ] 409 | 410 | [[package]] 411 | name = "matches" 412 | version = "0.1.9" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 415 | 416 | [[package]] 417 | name = "memchr" 418 | version = "2.4.1" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 421 | 422 | [[package]] 423 | name = "mime" 424 | version = "0.3.16" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 427 | 428 | [[package]] 429 | name = "mio" 430 | version = "0.8.2" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" 433 | dependencies = [ 434 | "libc", 435 | "log", 436 | "miow", 437 | "ntapi", 438 | "wasi", 439 | "winapi", 440 | ] 441 | 442 | [[package]] 443 | name = "miow" 444 | version = "0.3.7" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 447 | dependencies = [ 448 | "winapi", 449 | ] 450 | 451 | [[package]] 452 | name = "multimap" 453 | version = "0.8.3" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" 456 | 457 | [[package]] 458 | name = "native-tls" 459 | version = "0.2.8" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" 462 | dependencies = [ 463 | "lazy_static", 464 | "libc", 465 | "log", 466 | "openssl", 467 | "openssl-probe", 468 | "openssl-sys", 469 | "schannel", 470 | "security-framework", 471 | "security-framework-sys", 472 | "tempfile", 473 | ] 474 | 475 | [[package]] 476 | name = "ntapi" 477 | version = "0.3.7" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 480 | dependencies = [ 481 | "winapi", 482 | ] 483 | 484 | [[package]] 485 | name = "num_cpus" 486 | version = "1.13.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 489 | dependencies = [ 490 | "hermit-abi", 491 | "libc", 492 | ] 493 | 494 | [[package]] 495 | name = "once_cell" 496 | version = "1.10.0" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" 499 | 500 | [[package]] 501 | name = "openssl" 502 | version = "0.10.38" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" 505 | dependencies = [ 506 | "bitflags", 507 | "cfg-if", 508 | "foreign-types", 509 | "libc", 510 | "once_cell", 511 | "openssl-sys", 512 | ] 513 | 514 | [[package]] 515 | name = "openssl-probe" 516 | version = "0.1.5" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 519 | 520 | [[package]] 521 | name = "openssl-sys" 522 | version = "0.9.72" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" 525 | dependencies = [ 526 | "autocfg", 527 | "cc", 528 | "libc", 529 | "pkg-config", 530 | "vcpkg", 531 | ] 532 | 533 | [[package]] 534 | name = "percent-encoding" 535 | version = "2.1.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 538 | 539 | [[package]] 540 | name = "petgraph" 541 | version = "0.6.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" 544 | dependencies = [ 545 | "fixedbitset", 546 | "indexmap", 547 | ] 548 | 549 | [[package]] 550 | name = "pin-project-lite" 551 | version = "0.2.8" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 554 | 555 | [[package]] 556 | name = "pin-utils" 557 | version = "0.1.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 560 | 561 | [[package]] 562 | name = "pkg-config" 563 | version = "0.3.24" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 566 | 567 | [[package]] 568 | name = "proc-macro2" 569 | version = "1.0.36" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 572 | dependencies = [ 573 | "unicode-xid", 574 | ] 575 | 576 | [[package]] 577 | name = "prost" 578 | version = "0.9.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" 581 | dependencies = [ 582 | "bytes", 583 | "prost-derive", 584 | ] 585 | 586 | [[package]] 587 | name = "prost-build" 588 | version = "0.9.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" 591 | dependencies = [ 592 | "bytes", 593 | "heck", 594 | "itertools", 595 | "lazy_static", 596 | "log", 597 | "multimap", 598 | "petgraph", 599 | "prost", 600 | "prost-types", 601 | "regex", 602 | "tempfile", 603 | "which", 604 | ] 605 | 606 | [[package]] 607 | name = "prost-derive" 608 | version = "0.9.0" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" 611 | dependencies = [ 612 | "anyhow", 613 | "itertools", 614 | "proc-macro2", 615 | "quote", 616 | "syn", 617 | ] 618 | 619 | [[package]] 620 | name = "prost-types" 621 | version = "0.9.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" 624 | dependencies = [ 625 | "bytes", 626 | "prost", 627 | ] 628 | 629 | [[package]] 630 | name = "quote" 631 | version = "1.0.16" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" 634 | dependencies = [ 635 | "proc-macro2", 636 | ] 637 | 638 | [[package]] 639 | name = "redox_syscall" 640 | version = "0.2.11" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" 643 | dependencies = [ 644 | "bitflags", 645 | ] 646 | 647 | [[package]] 648 | name = "regex" 649 | version = "1.5.5" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" 652 | dependencies = [ 653 | "aho-corasick", 654 | "memchr", 655 | "regex-syntax", 656 | ] 657 | 658 | [[package]] 659 | name = "regex-syntax" 660 | version = "0.6.25" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 663 | 664 | [[package]] 665 | name = "remove_dir_all" 666 | version = "0.5.3" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 669 | dependencies = [ 670 | "winapi", 671 | ] 672 | 673 | [[package]] 674 | name = "reqwest" 675 | version = "0.11.10" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" 678 | dependencies = [ 679 | "base64", 680 | "bytes", 681 | "encoding_rs", 682 | "futures-core", 683 | "futures-util", 684 | "h2", 685 | "http", 686 | "http-body", 687 | "hyper", 688 | "hyper-rustls", 689 | "hyper-tls", 690 | "ipnet", 691 | "js-sys", 692 | "lazy_static", 693 | "log", 694 | "mime", 695 | "native-tls", 696 | "percent-encoding", 697 | "pin-project-lite", 698 | "rustls", 699 | "rustls-pemfile", 700 | "serde", 701 | "serde_json", 702 | "serde_urlencoded", 703 | "tokio", 704 | "tokio-native-tls", 705 | "tokio-rustls", 706 | "url", 707 | "wasm-bindgen", 708 | "wasm-bindgen-futures", 709 | "web-sys", 710 | "webpki-roots", 711 | "winreg", 712 | ] 713 | 714 | [[package]] 715 | name = "ring" 716 | version = "0.16.20" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 719 | dependencies = [ 720 | "cc", 721 | "libc", 722 | "once_cell", 723 | "spin", 724 | "untrusted", 725 | "web-sys", 726 | "winapi", 727 | ] 728 | 729 | [[package]] 730 | name = "rustls" 731 | version = "0.20.6" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" 734 | dependencies = [ 735 | "log", 736 | "ring", 737 | "sct", 738 | "webpki", 739 | ] 740 | 741 | [[package]] 742 | name = "rustls-pemfile" 743 | version = "0.3.0" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" 746 | dependencies = [ 747 | "base64", 748 | ] 749 | 750 | [[package]] 751 | name = "ryu" 752 | version = "1.0.9" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 755 | 756 | [[package]] 757 | name = "schannel" 758 | version = "0.1.19" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 761 | dependencies = [ 762 | "lazy_static", 763 | "winapi", 764 | ] 765 | 766 | [[package]] 767 | name = "sct" 768 | version = "0.7.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 771 | dependencies = [ 772 | "ring", 773 | "untrusted", 774 | ] 775 | 776 | [[package]] 777 | name = "security-framework" 778 | version = "2.6.1" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 781 | dependencies = [ 782 | "bitflags", 783 | "core-foundation", 784 | "core-foundation-sys", 785 | "libc", 786 | "security-framework-sys", 787 | ] 788 | 789 | [[package]] 790 | name = "security-framework-sys" 791 | version = "2.6.1" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 794 | dependencies = [ 795 | "core-foundation-sys", 796 | "libc", 797 | ] 798 | 799 | [[package]] 800 | name = "serde" 801 | version = "1.0.136" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 804 | dependencies = [ 805 | "serde_derive", 806 | ] 807 | 808 | [[package]] 809 | name = "serde_derive" 810 | version = "1.0.136" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 813 | dependencies = [ 814 | "proc-macro2", 815 | "quote", 816 | "syn", 817 | ] 818 | 819 | [[package]] 820 | name = "serde_json" 821 | version = "1.0.79" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" 824 | dependencies = [ 825 | "itoa", 826 | "ryu", 827 | "serde", 828 | ] 829 | 830 | [[package]] 831 | name = "serde_urlencoded" 832 | version = "0.7.1" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 835 | dependencies = [ 836 | "form_urlencoded", 837 | "itoa", 838 | "ryu", 839 | "serde", 840 | ] 841 | 842 | [[package]] 843 | name = "sharded-slab" 844 | version = "0.1.4" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 847 | dependencies = [ 848 | "lazy_static", 849 | ] 850 | 851 | [[package]] 852 | name = "slab" 853 | version = "0.4.5" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 856 | 857 | [[package]] 858 | name = "smallvec" 859 | version = "1.8.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 862 | 863 | [[package]] 864 | name = "snap" 865 | version = "1.0.5" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" 868 | 869 | [[package]] 870 | name = "socket2" 871 | version = "0.4.4" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 874 | dependencies = [ 875 | "libc", 876 | "winapi", 877 | ] 878 | 879 | [[package]] 880 | name = "spin" 881 | version = "0.5.2" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 884 | 885 | [[package]] 886 | name = "syn" 887 | version = "1.0.89" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" 890 | dependencies = [ 891 | "proc-macro2", 892 | "quote", 893 | "unicode-xid", 894 | ] 895 | 896 | [[package]] 897 | name = "tempfile" 898 | version = "3.3.0" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 901 | dependencies = [ 902 | "cfg-if", 903 | "fastrand", 904 | "libc", 905 | "redox_syscall", 906 | "remove_dir_all", 907 | "winapi", 908 | ] 909 | 910 | [[package]] 911 | name = "thread_local" 912 | version = "1.1.4" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 915 | dependencies = [ 916 | "once_cell", 917 | ] 918 | 919 | [[package]] 920 | name = "tinyvec" 921 | version = "1.5.1" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 924 | dependencies = [ 925 | "tinyvec_macros", 926 | ] 927 | 928 | [[package]] 929 | name = "tinyvec_macros" 930 | version = "0.1.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 933 | 934 | [[package]] 935 | name = "tokio" 936 | version = "1.17.0" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" 939 | dependencies = [ 940 | "bytes", 941 | "libc", 942 | "memchr", 943 | "mio", 944 | "num_cpus", 945 | "pin-project-lite", 946 | "socket2", 947 | "tokio-macros", 948 | "winapi", 949 | ] 950 | 951 | [[package]] 952 | name = "tokio-macros" 953 | version = "1.7.0" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 956 | dependencies = [ 957 | "proc-macro2", 958 | "quote", 959 | "syn", 960 | ] 961 | 962 | [[package]] 963 | name = "tokio-native-tls" 964 | version = "0.3.0" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 967 | dependencies = [ 968 | "native-tls", 969 | "tokio", 970 | ] 971 | 972 | [[package]] 973 | name = "tokio-rustls" 974 | version = "0.23.4" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" 977 | dependencies = [ 978 | "rustls", 979 | "tokio", 980 | "webpki", 981 | ] 982 | 983 | [[package]] 984 | name = "tokio-stream" 985 | version = "0.1.8" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" 988 | dependencies = [ 989 | "futures-core", 990 | "pin-project-lite", 991 | "tokio", 992 | ] 993 | 994 | [[package]] 995 | name = "tokio-util" 996 | version = "0.6.9" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" 999 | dependencies = [ 1000 | "bytes", 1001 | "futures-core", 1002 | "futures-sink", 1003 | "log", 1004 | "pin-project-lite", 1005 | "tokio", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "tower-service" 1010 | version = "0.3.1" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1013 | 1014 | [[package]] 1015 | name = "tracing" 1016 | version = "0.1.32" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" 1019 | dependencies = [ 1020 | "cfg-if", 1021 | "pin-project-lite", 1022 | "tracing-attributes", 1023 | "tracing-core", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "tracing-attributes" 1028 | version = "0.1.20" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" 1031 | dependencies = [ 1032 | "proc-macro2", 1033 | "quote", 1034 | "syn", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "tracing-core" 1039 | version = "0.1.23" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" 1042 | dependencies = [ 1043 | "lazy_static", 1044 | "valuable", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "tracing-log" 1049 | version = "0.1.2" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" 1052 | dependencies = [ 1053 | "lazy_static", 1054 | "log", 1055 | "tracing-core", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "tracing-loki" 1060 | version = "0.2.6" 1061 | dependencies = [ 1062 | "loki-api", 1063 | "reqwest", 1064 | "serde", 1065 | "serde_json", 1066 | "snap", 1067 | "tokio", 1068 | "tokio-stream", 1069 | "tracing", 1070 | "tracing-core", 1071 | "tracing-log", 1072 | "tracing-serde", 1073 | "tracing-subscriber", 1074 | "url", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "tracing-serde" 1079 | version = "0.1.3" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" 1082 | dependencies = [ 1083 | "serde", 1084 | "tracing-core", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "tracing-subscriber" 1089 | version = "0.3.9" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" 1092 | dependencies = [ 1093 | "ansi_term", 1094 | "sharded-slab", 1095 | "smallvec", 1096 | "thread_local", 1097 | "tracing-core", 1098 | "tracing-log", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "try-lock" 1103 | version = "0.2.3" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1106 | 1107 | [[package]] 1108 | name = "unicode-bidi" 1109 | version = "0.3.7" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 1112 | 1113 | [[package]] 1114 | name = "unicode-normalization" 1115 | version = "0.1.19" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1118 | dependencies = [ 1119 | "tinyvec", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "unicode-segmentation" 1124 | version = "1.9.0" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 1127 | 1128 | [[package]] 1129 | name = "unicode-xid" 1130 | version = "0.2.2" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1133 | 1134 | [[package]] 1135 | name = "untrusted" 1136 | version = "0.7.1" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1139 | 1140 | [[package]] 1141 | name = "url" 1142 | version = "2.2.2" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1145 | dependencies = [ 1146 | "form_urlencoded", 1147 | "idna", 1148 | "matches", 1149 | "percent-encoding", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "valuable" 1154 | version = "0.1.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1157 | 1158 | [[package]] 1159 | name = "vcpkg" 1160 | version = "0.2.15" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1163 | 1164 | [[package]] 1165 | name = "want" 1166 | version = "0.3.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1169 | dependencies = [ 1170 | "log", 1171 | "try-lock", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "wasi" 1176 | version = "0.11.0+wasi-snapshot-preview1" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1179 | 1180 | [[package]] 1181 | name = "wasm-bindgen" 1182 | version = "0.2.79" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" 1185 | dependencies = [ 1186 | "cfg-if", 1187 | "wasm-bindgen-macro", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "wasm-bindgen-backend" 1192 | version = "0.2.79" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" 1195 | dependencies = [ 1196 | "bumpalo", 1197 | "lazy_static", 1198 | "log", 1199 | "proc-macro2", 1200 | "quote", 1201 | "syn", 1202 | "wasm-bindgen-shared", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "wasm-bindgen-futures" 1207 | version = "0.4.29" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" 1210 | dependencies = [ 1211 | "cfg-if", 1212 | "js-sys", 1213 | "wasm-bindgen", 1214 | "web-sys", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "wasm-bindgen-macro" 1219 | version = "0.2.79" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" 1222 | dependencies = [ 1223 | "quote", 1224 | "wasm-bindgen-macro-support", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "wasm-bindgen-macro-support" 1229 | version = "0.2.79" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" 1232 | dependencies = [ 1233 | "proc-macro2", 1234 | "quote", 1235 | "syn", 1236 | "wasm-bindgen-backend", 1237 | "wasm-bindgen-shared", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "wasm-bindgen-shared" 1242 | version = "0.2.79" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" 1245 | 1246 | [[package]] 1247 | name = "web-sys" 1248 | version = "0.3.56" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" 1251 | dependencies = [ 1252 | "js-sys", 1253 | "wasm-bindgen", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "webpki" 1258 | version = "0.22.0" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" 1261 | dependencies = [ 1262 | "ring", 1263 | "untrusted", 1264 | ] 1265 | 1266 | [[package]] 1267 | name = "webpki-roots" 1268 | version = "0.22.3" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" 1271 | dependencies = [ 1272 | "webpki", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "which" 1277 | version = "4.2.4" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" 1280 | dependencies = [ 1281 | "either", 1282 | "lazy_static", 1283 | "libc", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "winapi" 1288 | version = "0.3.9" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1291 | dependencies = [ 1292 | "winapi-i686-pc-windows-gnu", 1293 | "winapi-x86_64-pc-windows-gnu", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "winapi-i686-pc-windows-gnu" 1298 | version = "0.4.0" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1301 | 1302 | [[package]] 1303 | name = "winapi-x86_64-pc-windows-gnu" 1304 | version = "0.4.0" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1307 | 1308 | [[package]] 1309 | name = "winreg" 1310 | version = "0.10.1" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1313 | dependencies = [ 1314 | "winapi", 1315 | ] 1316 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "loki-api", 4 | "loki-api/generate", 5 | ] 6 | 7 | [package] 8 | name = "tracing-loki" 9 | description = "A tracing layer for shipping logs to Grafana Loki" 10 | authors = ["hrxi "] 11 | repository = "https://github.com/hrxi/tracing-loki" 12 | keywords = ["tracing", "loki"] 13 | version = "0.2.6" 14 | license = "MIT/Apache-2.0" 15 | edition = "2021" 16 | 17 | [dependencies] 18 | loki-api = { version = "0.1.0", path = "loki-api" } 19 | reqwest = { version = ">=0.11.10,<0.13.0", default-features = false } 20 | snap = "1.0.5" 21 | serde = { version = "1.0.136", features = ["derive"] } 22 | serde_json = "1.0.79" 23 | tokio = { version = "1.17.0", features = ["sync"] } 24 | tokio-stream = "0.1.8" 25 | tracing = "0.1.32" 26 | tracing-core = "0.1.23" 27 | tracing-log = ">=0.1.2,<0.3.0" 28 | tracing-serde = ">=0.1.3,<0.3.0" 29 | tracing-subscriber = "0.3.9" 30 | url = "2.2.2" 31 | 32 | [dev-dependencies] 33 | tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] } 34 | 35 | [features] 36 | default = ["compat-0-2-1", "native-tls"] 37 | compat-0-2-1 = [] 38 | 39 | native-tls = ["reqwest/native-tls"] 40 | rustls = ["reqwest/rustls-tls"] 41 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tracing-loki 2 | ============ 3 | 4 | A [tracing](https://github.com/tokio-rs/tracing) layer for [Grafana 5 | Loki](https://grafana.com/oss/loki/). 6 | 7 | [![Build status](https://github.com/hrxi/tracing-loki/actions/workflows/build.yaml/badge.svg)](https://github.com/hrxi/tracing-loki/actions/workflows/build.yaml) 8 | 9 | Documentation 10 | ------------- 11 | 12 | https://docs.rs/tracing-loki 13 | 14 | Usage 15 | ----- 16 | 17 | Add this to your `Cargo.toml`: 18 | ```toml 19 | [dependencies] 20 | tracing-loki = "0.2" 21 | ``` 22 | 23 | Example 24 | ------- 25 | 26 | ```rust 27 | use tracing_subscriber::layer::SubscriberExt; 28 | use tracing_subscriber::util::SubscriberInitExt; 29 | use std::process; 30 | use url::Url; 31 | 32 | #[tokio::main] 33 | async fn main() -> Result<(), tracing_loki::Error> { 34 | let (layer, task) = tracing_loki::builder() 35 | .label("host", "mine")? 36 | .extra_field("pid", format!("{}", process::id()))? 37 | .build_url(Url::parse("http://127.0.0.1:3100").unwrap())?; 38 | 39 | // We need to register our layer with `tracing`. 40 | tracing_subscriber::registry() 41 | .with(layer) 42 | // One could add more layers here, for example logging to stdout: 43 | // .with(tracing_subscriber::fmt::Layer::new()) 44 | .init(); 45 | 46 | // The background task needs to be spawned so the logs actually get 47 | // delivered. 48 | tokio::spawn(task); 49 | 50 | tracing::info!( 51 | task = "tracing_setup", 52 | result = "success", 53 | "tracing successfully set up", 54 | ); 55 | 56 | Ok(()) 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /examples/builder.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::time::Duration; 3 | use tracing::info; 4 | use tracing::info_span; 5 | use tracing_subscriber::filter::LevelFilter; 6 | use tracing_subscriber::fmt::Layer; 7 | use tracing_subscriber::layer::SubscriberExt; 8 | use tracing_subscriber::util::SubscriberInitExt; 9 | use url::Url; 10 | 11 | fn tracing_setup() -> Result<(), Box> { 12 | let (layer, task) = tracing_loki::builder() 13 | .label("host", "mine")? 14 | .build_url(Url::parse("http://127.0.0.1:3100").unwrap())?; 15 | 16 | tracing_subscriber::registry() 17 | .with(LevelFilter::INFO) 18 | .with(layer) 19 | .with(Layer::new()) 20 | .init(); 21 | tokio::spawn(task); 22 | Ok(()) 23 | } 24 | 25 | #[tokio::main(flavor = "current_thread")] 26 | async fn main() -> Result<(), Box> { 27 | tracing_setup()?; 28 | 29 | info_span!("report", output = "tracing").in_scope(|| { 30 | info!( 31 | task = "tracing_setup", 32 | result = "success", 33 | "tracing successfully set up" 34 | ); 35 | }); 36 | 37 | tokio::time::sleep(Duration::from_secs(1)).await; 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /examples/layer.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::time::Duration; 3 | use tracing::info; 4 | use tracing::info_span; 5 | use tracing_subscriber::filter::LevelFilter; 6 | use tracing_subscriber::fmt::Layer; 7 | use tracing_subscriber::layer::SubscriberExt; 8 | use tracing_subscriber::util::SubscriberInitExt; 9 | use url::Url; 10 | 11 | fn tracing_setup() -> Result<(), Box> { 12 | let (layer, task) = tracing_loki::layer( 13 | Url::parse("http://127.0.0.1:3100").unwrap(), 14 | vec![("host".into(), "mine".into())].into_iter().collect(), 15 | vec![].into_iter().collect(), 16 | )?; 17 | tracing_subscriber::registry() 18 | .with(LevelFilter::INFO) 19 | .with(layer) 20 | .with(Layer::new()) 21 | .init(); 22 | tokio::spawn(task); 23 | Ok(()) 24 | } 25 | 26 | #[tokio::main(flavor = "current_thread")] 27 | async fn main() -> Result<(), Box> { 28 | tracing_setup()?; 29 | 30 | info_span!("report", output = "tracing").in_scope(|| { 31 | info!( 32 | task = "tracing_setup", 33 | result = "success", 34 | "tracing successfully set up" 35 | ); 36 | }); 37 | 38 | tokio::time::sleep(Duration::from_secs(1)).await; 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/shutdown.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use tokio::task::JoinHandle; 3 | use tracing::info; 4 | use tracing::info_span; 5 | use tracing_subscriber::filter::LevelFilter; 6 | use tracing_subscriber::fmt::Layer; 7 | use tracing_subscriber::layer::SubscriberExt; 8 | use tracing_subscriber::util::SubscriberInitExt; 9 | use url::Url; 10 | 11 | fn tracing_setup( 12 | ) -> Result<(tracing_loki::BackgroundTaskController, JoinHandle<()>), Box> { 13 | let (layer, controller, task) = tracing_loki::builder() 14 | .label("host", "mine")? 15 | .build_controller_url(Url::parse("http://127.0.0.1:3100").unwrap())?; 16 | 17 | tracing_subscriber::registry() 18 | .with(LevelFilter::INFO) 19 | .with(layer) 20 | .with(Layer::new()) 21 | .init(); 22 | Ok((controller, tokio::spawn(task))) 23 | } 24 | 25 | #[tokio::main(flavor = "current_thread")] 26 | async fn main() -> Result<(), Box> { 27 | let (controller, task) = tracing_setup()?; 28 | 29 | info_span!("report", output = "tracing").in_scope(|| { 30 | info!( 31 | task = "tracing_setup", 32 | result = "success", 33 | "tracing successfully set up" 34 | ); 35 | }); 36 | 37 | controller.shutdown().await; 38 | task.await.unwrap(); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /loki-api/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Recent changes (loki-api) 2 | ========================= 3 | 4 | 0.1.1 (2023-03-08) 5 | ------------------ 6 | 7 | - Relax `prost` and `prost-types` version requirement to allow 0.10 and 0.11 in 8 | addition to 0.9. 9 | -------------------------------------------------------------------------------- /loki-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "loki-api" 3 | description = "Protobuf types used by the Grafana Loki HTTP API" 4 | authors = ["hrxi "] 5 | repository = "https://github.com/hrxi/tracing-loki" 6 | keywords = ["tracing", "loki"] 7 | version = "0.1.3" 8 | license = "MIT/Apache-2.0" 9 | edition = "2021" 10 | 11 | [dependencies] 12 | prost = ">=0.9.0,<0.14.0" 13 | prost-types = ">=0.9.0,<0.14.0" 14 | -------------------------------------------------------------------------------- /loki-api/generate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "loki-api-generate" 3 | description = "Code generation for loki-api" 4 | authors = ["hrxi "] 5 | repository = "https://github.com/hrxi/tracing-loki" 6 | keywords = ["tracing", "loki"] 7 | version = "0.0.1" 8 | license = "MIT/Apache-2.0" 9 | edition = "2021" 10 | 11 | [build-dependencies] 12 | prost-build = "0.9" 13 | -------------------------------------------------------------------------------- /loki-api/generate/build.rs: -------------------------------------------------------------------------------- 1 | use prost_build::compile_protos; 2 | use std::io; 3 | fn main() -> io::Result<()> { 4 | compile_protos(&["proto/logproto.proto"], &["proto/"])?; 5 | Ok(()) 6 | } 7 | -------------------------------------------------------------------------------- /loki-api/generate/proto/github.com/gogo/protobuf/gogoproto/gogo.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers for Go with Gadgets 2 | // 3 | // Copyright (c) 2013, The GoGo Authors. All rights reserved. 4 | // http://github.com/gogo/protobuf 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are 8 | // met: 9 | // 10 | // * Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // * Redistributions in binary form must reproduce the above 13 | // copyright notice, this list of conditions and the following disclaimer 14 | // in the documentation and/or other materials provided with the 15 | // distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | syntax = "proto2"; 30 | package gogoproto; 31 | 32 | import "google/protobuf/descriptor.proto"; 33 | 34 | option java_package = "com.google.protobuf"; 35 | option java_outer_classname = "GoGoProtos"; 36 | option go_package = "github.com/gogo/protobuf/gogoproto"; 37 | 38 | extend google.protobuf.EnumOptions { 39 | optional bool goproto_enum_prefix = 62001; 40 | optional bool goproto_enum_stringer = 62021; 41 | optional bool enum_stringer = 62022; 42 | optional string enum_customname = 62023; 43 | optional bool enumdecl = 62024; 44 | } 45 | 46 | extend google.protobuf.EnumValueOptions { 47 | optional string enumvalue_customname = 66001; 48 | } 49 | 50 | extend google.protobuf.FileOptions { 51 | optional bool goproto_getters_all = 63001; 52 | optional bool goproto_enum_prefix_all = 63002; 53 | optional bool goproto_stringer_all = 63003; 54 | optional bool verbose_equal_all = 63004; 55 | optional bool face_all = 63005; 56 | optional bool gostring_all = 63006; 57 | optional bool populate_all = 63007; 58 | optional bool stringer_all = 63008; 59 | optional bool onlyone_all = 63009; 60 | 61 | optional bool equal_all = 63013; 62 | optional bool description_all = 63014; 63 | optional bool testgen_all = 63015; 64 | optional bool benchgen_all = 63016; 65 | optional bool marshaler_all = 63017; 66 | optional bool unmarshaler_all = 63018; 67 | optional bool stable_marshaler_all = 63019; 68 | 69 | optional bool sizer_all = 63020; 70 | 71 | optional bool goproto_enum_stringer_all = 63021; 72 | optional bool enum_stringer_all = 63022; 73 | 74 | optional bool unsafe_marshaler_all = 63023; 75 | optional bool unsafe_unmarshaler_all = 63024; 76 | 77 | optional bool goproto_extensions_map_all = 63025; 78 | optional bool goproto_unrecognized_all = 63026; 79 | optional bool gogoproto_import = 63027; 80 | optional bool protosizer_all = 63028; 81 | optional bool compare_all = 63029; 82 | optional bool typedecl_all = 63030; 83 | optional bool enumdecl_all = 63031; 84 | 85 | optional bool goproto_registration = 63032; 86 | optional bool messagename_all = 63033; 87 | 88 | optional bool goproto_sizecache_all = 63034; 89 | optional bool goproto_unkeyed_all = 63035; 90 | } 91 | 92 | extend google.protobuf.MessageOptions { 93 | optional bool goproto_getters = 64001; 94 | optional bool goproto_stringer = 64003; 95 | optional bool verbose_equal = 64004; 96 | optional bool face = 64005; 97 | optional bool gostring = 64006; 98 | optional bool populate = 64007; 99 | optional bool stringer = 67008; 100 | optional bool onlyone = 64009; 101 | 102 | optional bool equal = 64013; 103 | optional bool description = 64014; 104 | optional bool testgen = 64015; 105 | optional bool benchgen = 64016; 106 | optional bool marshaler = 64017; 107 | optional bool unmarshaler = 64018; 108 | optional bool stable_marshaler = 64019; 109 | 110 | optional bool sizer = 64020; 111 | 112 | optional bool unsafe_marshaler = 64023; 113 | optional bool unsafe_unmarshaler = 64024; 114 | 115 | optional bool goproto_extensions_map = 64025; 116 | optional bool goproto_unrecognized = 64026; 117 | 118 | optional bool protosizer = 64028; 119 | optional bool compare = 64029; 120 | 121 | optional bool typedecl = 64030; 122 | 123 | optional bool messagename = 64033; 124 | 125 | optional bool goproto_sizecache = 64034; 126 | optional bool goproto_unkeyed = 64035; 127 | } 128 | 129 | extend google.protobuf.FieldOptions { 130 | optional bool nullable = 65001; 131 | optional bool embed = 65002; 132 | optional string customtype = 65003; 133 | optional string customname = 65004; 134 | optional string jsontag = 65005; 135 | optional string moretags = 65006; 136 | optional string casttype = 65007; 137 | optional string castkey = 65008; 138 | optional string castvalue = 65009; 139 | 140 | optional bool stdtime = 65010; 141 | optional bool stdduration = 65011; 142 | optional bool wktpointer = 65012; 143 | 144 | } 145 | -------------------------------------------------------------------------------- /loki-api/generate/proto/logproto.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package logproto; 4 | 5 | option go_package = "github.com/grafana/loki/pkg/logproto"; 6 | 7 | import "google/protobuf/timestamp.proto"; 8 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 9 | import "pkg/logqlmodel/stats/stats.proto"; 10 | 11 | service Pusher { 12 | rpc Push(PushRequest) returns (PushResponse) {}; 13 | } 14 | 15 | service Querier { 16 | rpc Query(QueryRequest) returns (stream QueryResponse) {}; 17 | rpc QuerySample(SampleQueryRequest) returns (stream SampleQueryResponse) {}; 18 | rpc Label(LabelRequest) returns (LabelResponse) {}; 19 | rpc Tail(TailRequest) returns (stream TailResponse) {}; 20 | rpc Series(SeriesRequest) returns (SeriesResponse) {}; 21 | rpc TailersCount(TailersCountRequest) returns (TailersCountResponse) {}; 22 | rpc GetChunkIDs(GetChunkIDsRequest) returns (GetChunkIDsResponse) {}; // GetChunkIDs returns ChunkIDs from the index store holding logs for given selectors and time-range. 23 | } 24 | 25 | service Ingester { 26 | rpc TransferChunks(stream TimeSeriesChunk) returns (TransferChunksResponse) {}; 27 | } 28 | 29 | message PushRequest { 30 | repeated StreamAdapter streams = 1 [(gogoproto.jsontag) = "streams", (gogoproto.customtype) = "Stream"]; 31 | } 32 | 33 | message PushResponse { 34 | } 35 | 36 | message QueryRequest { 37 | string selector = 1; 38 | uint32 limit = 2; 39 | google.protobuf.Timestamp start = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 40 | google.protobuf.Timestamp end = 4 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 41 | Direction direction = 5; 42 | reserved 6; 43 | repeated string shards = 7 [(gogoproto.jsontag) = "shards,omitempty"]; 44 | repeated Delete deletes = 8; 45 | } 46 | 47 | message SampleQueryRequest { 48 | string selector = 1; 49 | google.protobuf.Timestamp start = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 50 | google.protobuf.Timestamp end = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 51 | repeated string shards = 4 [(gogoproto.jsontag) = "shards,omitempty"]; 52 | repeated Delete deletes = 5; 53 | } 54 | 55 | message Delete { 56 | string selector = 1; 57 | int64 start = 2; 58 | int64 end = 3; 59 | } 60 | 61 | message QueryResponse { 62 | repeated StreamAdapter streams = 1 [(gogoproto.customtype) = "Stream", (gogoproto.nullable) = true]; 63 | stats.Ingester stats = 2 [(gogoproto.nullable) = false]; 64 | } 65 | 66 | message SampleQueryResponse { 67 | repeated Series series = 1 [(gogoproto.customtype) = "Series", (gogoproto.nullable) = true]; 68 | stats.Ingester stats = 2 [(gogoproto.nullable) = false]; 69 | } 70 | 71 | 72 | enum Direction { 73 | FORWARD = 0; 74 | BACKWARD = 1; 75 | } 76 | 77 | 78 | 79 | message LabelRequest { 80 | string name = 1; 81 | bool values = 2; // True to fetch label values, false for fetch labels names. 82 | google.protobuf.Timestamp start = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true]; 83 | google.protobuf.Timestamp end = 4 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true]; 84 | } 85 | 86 | message LabelResponse { 87 | repeated string values = 1; 88 | } 89 | 90 | message StreamAdapter { 91 | string labels = 1 [(gogoproto.jsontag) = "labels"]; 92 | repeated EntryAdapter entries = 2 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "entries"]; 93 | // hash contains the original hash of the stream. 94 | uint64 hash = 3 [(gogoproto.jsontag) = "-"]; 95 | } 96 | 97 | message EntryAdapter { 98 | google.protobuf.Timestamp timestamp = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.jsontag) = "ts"]; 99 | string line = 2 [(gogoproto.jsontag) = "line"]; 100 | } 101 | 102 | message Sample { 103 | int64 timestamp = 1 [(gogoproto.jsontag) = "ts"]; 104 | double value = 2 [(gogoproto.jsontag) = "value"]; 105 | uint64 hash = 3 [(gogoproto.jsontag) = "hash"]; 106 | } 107 | 108 | // LegacySample exists for backwards compatibility reasons and is deprecated. Do not use. 109 | message LegacySample { 110 | double value = 1; 111 | int64 timestamp_ms = 2; 112 | } 113 | 114 | message Series { 115 | string labels = 1 [(gogoproto.jsontag) = "labels"]; 116 | repeated Sample samples = 2 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "samples"]; 117 | uint64 streamHash = 3 [(gogoproto.jsontag) = "streamHash"]; 118 | } 119 | 120 | message TailRequest { 121 | string query = 1; 122 | reserved 2; 123 | uint32 delayFor = 3; 124 | uint32 limit = 4; 125 | google.protobuf.Timestamp start = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 126 | } 127 | 128 | message TailResponse { 129 | StreamAdapter stream = 1 [(gogoproto.customtype) = "Stream"]; 130 | repeated DroppedStream droppedStreams = 2; 131 | } 132 | 133 | message SeriesRequest { 134 | google.protobuf.Timestamp start = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 135 | google.protobuf.Timestamp end = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 136 | repeated string groups = 3; 137 | repeated string shards = 4 [(gogoproto.jsontag) = "shards,omitempty"]; 138 | } 139 | 140 | message SeriesResponse { 141 | repeated SeriesIdentifier series = 1 [(gogoproto.nullable) = false]; 142 | } 143 | 144 | message SeriesIdentifier { 145 | map labels = 1; 146 | } 147 | 148 | message DroppedStream { 149 | google.protobuf.Timestamp from = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 150 | google.protobuf.Timestamp to = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 151 | string labels = 3; 152 | } 153 | 154 | message TimeSeriesChunk { 155 | string from_ingester_id = 1; 156 | string user_id = 2; 157 | repeated LabelPair labels = 3; 158 | repeated Chunk chunks = 4; 159 | } 160 | 161 | message LabelPair { 162 | string name = 1; 163 | string value = 2; 164 | } 165 | 166 | // LegacyLabelPair exists for backwards compatibility reasons and is deprecated. Do not use. 167 | message LegacyLabelPair { 168 | bytes name = 1; 169 | bytes value = 2; 170 | } 171 | 172 | message Chunk { 173 | bytes data = 1; 174 | } 175 | 176 | message TransferChunksResponse { 177 | 178 | } 179 | 180 | message TailersCountRequest { 181 | 182 | } 183 | 184 | message TailersCountResponse { 185 | uint32 count = 1; 186 | } 187 | 188 | message GetChunkIDsRequest { 189 | string matchers = 1; 190 | google.protobuf.Timestamp start = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 191 | google.protobuf.Timestamp end = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 192 | } 193 | 194 | message GetChunkIDsResponse { 195 | repeated string chunkIDs = 1; 196 | } 197 | -------------------------------------------------------------------------------- /loki-api/generate/proto/pkg/logqlmodel/stats/stats.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package stats; 4 | 5 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 6 | 7 | option (gogoproto.marshaler_all) = true; 8 | option (gogoproto.unmarshaler_all) = true; 9 | option go_package = "github.com/grafana/loki/pkg/logqlmodel/stats"; 10 | 11 | 12 | // Result contains LogQL query statistics. 13 | message Result { 14 | Summary summary = 1 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "summary"]; 15 | Querier querier = 2 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "querier"]; 16 | Ingester ingester = 3 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "ingester"]; 17 | } 18 | 19 | // Summary is the summary of a query statistics. 20 | message Summary { 21 | // Total bytes processed per second. 22 | int64 bytesProcessedPerSecond = 1 [(gogoproto.jsontag) = "bytesProcessedPerSecond"]; 23 | // Total lines processed per second. 24 | int64 linesProcessedPerSecond = 2 [(gogoproto.jsontag) = "linesProcessedPerSecond"]; 25 | // Total bytes processed. 26 | int64 totalBytesProcessed = 3 [(gogoproto.jsontag) = "totalBytesProcessed"]; 27 | // Total lines processed. 28 | int64 totalLinesProcessed = 4 [(gogoproto.jsontag) = "totalLinesProcessed"]; 29 | // Execution time in seconds. 30 | // In addition to internal calculations this is also returned by the HTTP API. 31 | // Grafana expects time values to be returned in seconds as float. 32 | double execTime = 5 [(gogoproto.jsontag) = "execTime"]; 33 | // Queue time in seconds. 34 | // In addition to internal calculations this is also returned by the HTTP API. 35 | // Grafana expects time values to be returned in seconds as float. 36 | double queueTime = 6 [(gogoproto.jsontag) = "queueTime"]; 37 | // Total of subqueries created to fulfill this query. 38 | int64 subqueries = 7 [(gogoproto.jsontag) = "subqueries"]; 39 | } 40 | 41 | message Querier { 42 | Store store = 1 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "store"]; 43 | } 44 | 45 | message Ingester { 46 | // Total ingester reached for this query. 47 | int32 totalReached = 1 [(gogoproto.jsontag) = "totalReached"]; 48 | // Total of chunks matched by the query from ingesters 49 | int64 totalChunksMatched = 2 [(gogoproto.jsontag) = "totalChunksMatched"]; 50 | // Total of batches sent from ingesters. 51 | int64 totalBatches = 3 [(gogoproto.jsontag) = "totalBatches"]; 52 | // Total lines sent by ingesters. 53 | int64 totalLinesSent = 4 [(gogoproto.jsontag) = "totalLinesSent"]; 54 | 55 | Store store = 5 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "store"]; 56 | 57 | } 58 | 59 | message Store { 60 | // The total of chunk reference fetched from index. 61 | int64 totalChunksRef = 1 [(gogoproto.jsontag) = "totalChunksRef"]; 62 | // Total number of chunks fetched. 63 | int64 totalChunksDownloaded = 2 [(gogoproto.jsontag) = "totalChunksDownloaded"]; 64 | // Time spent fetching chunks in nanoseconds. 65 | int64 chunksDownloadTime = 3 [(gogoproto.jsontag) = "chunksDownloadTime"]; 66 | 67 | Chunk chunk = 4 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "chunk"]; 68 | } 69 | 70 | message Chunk { 71 | // Total bytes processed but was already in memory. (found in the headchunk) 72 | int64 headChunkBytes = 4 [(gogoproto.jsontag) = "headChunkBytes"]; 73 | // Total lines processed but was already in memory. (found in the headchunk) 74 | int64 headChunkLines = 5 [(gogoproto.jsontag) = "headChunkLines"]; 75 | // Total bytes decompressed and processed from chunks. 76 | int64 decompressedBytes = 6 [(gogoproto.jsontag) = "decompressedBytes"]; 77 | // Total lines decompressed and processed from chunks. 78 | int64 decompressedLines = 7 [(gogoproto.jsontag) = "decompressedLines"]; 79 | // Total bytes of compressed chunks (blocks) processed. 80 | int64 compressedBytes = 8 [(gogoproto.jsontag) = "compressedBytes"]; 81 | // Total duplicates found while processing. 82 | int64 totalDuplicates = 9 [(gogoproto.jsontag) = "totalDuplicates"]; 83 | } 84 | -------------------------------------------------------------------------------- /loki-api/generate/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io; 3 | use std::path::PathBuf; 4 | fn main() -> io::Result<()> { 5 | let out_dir = PathBuf::from(env!("OUT_DIR")); 6 | fs::copy(out_dir.join("logproto.rs"), "../src/logproto.rs")?; 7 | fs::copy(out_dir.join("stats.rs"), "../src/stats.rs")?; 8 | Ok(()) 9 | } 10 | -------------------------------------------------------------------------------- /loki-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub extern crate prost; 2 | pub extern crate prost_types; 3 | 4 | #[rustfmt::skip] 5 | pub mod logproto; 6 | #[rustfmt::skip] 7 | pub mod stats; 8 | -------------------------------------------------------------------------------- /loki-api/src/logproto.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, PartialEq, ::prost::Message)] 2 | pub struct PushRequest { 3 | #[prost(message, repeated, tag="1")] 4 | pub streams: ::prost::alloc::vec::Vec, 5 | } 6 | #[derive(Clone, PartialEq, ::prost::Message)] 7 | pub struct PushResponse { 8 | } 9 | #[derive(Clone, PartialEq, ::prost::Message)] 10 | pub struct QueryRequest { 11 | #[prost(string, tag="1")] 12 | pub selector: ::prost::alloc::string::String, 13 | #[prost(uint32, tag="2")] 14 | pub limit: u32, 15 | #[prost(message, optional, tag="3")] 16 | pub start: ::core::option::Option<::prost_types::Timestamp>, 17 | #[prost(message, optional, tag="4")] 18 | pub end: ::core::option::Option<::prost_types::Timestamp>, 19 | #[prost(enumeration="Direction", tag="5")] 20 | pub direction: i32, 21 | #[prost(string, repeated, tag="7")] 22 | pub shards: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 23 | #[prost(message, repeated, tag="8")] 24 | pub deletes: ::prost::alloc::vec::Vec, 25 | } 26 | #[derive(Clone, PartialEq, ::prost::Message)] 27 | pub struct SampleQueryRequest { 28 | #[prost(string, tag="1")] 29 | pub selector: ::prost::alloc::string::String, 30 | #[prost(message, optional, tag="2")] 31 | pub start: ::core::option::Option<::prost_types::Timestamp>, 32 | #[prost(message, optional, tag="3")] 33 | pub end: ::core::option::Option<::prost_types::Timestamp>, 34 | #[prost(string, repeated, tag="4")] 35 | pub shards: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 36 | #[prost(message, repeated, tag="5")] 37 | pub deletes: ::prost::alloc::vec::Vec, 38 | } 39 | #[derive(Clone, PartialEq, ::prost::Message)] 40 | pub struct Delete { 41 | #[prost(string, tag="1")] 42 | pub selector: ::prost::alloc::string::String, 43 | #[prost(int64, tag="2")] 44 | pub start: i64, 45 | #[prost(int64, tag="3")] 46 | pub end: i64, 47 | } 48 | #[derive(Clone, PartialEq, ::prost::Message)] 49 | pub struct QueryResponse { 50 | #[prost(message, repeated, tag="1")] 51 | pub streams: ::prost::alloc::vec::Vec, 52 | #[prost(message, optional, tag="2")] 53 | pub stats: ::core::option::Option, 54 | } 55 | #[derive(Clone, PartialEq, ::prost::Message)] 56 | pub struct SampleQueryResponse { 57 | #[prost(message, repeated, tag="1")] 58 | pub series: ::prost::alloc::vec::Vec, 59 | #[prost(message, optional, tag="2")] 60 | pub stats: ::core::option::Option, 61 | } 62 | #[derive(Clone, PartialEq, ::prost::Message)] 63 | pub struct LabelRequest { 64 | #[prost(string, tag="1")] 65 | pub name: ::prost::alloc::string::String, 66 | /// True to fetch label values, false for fetch labels names. 67 | #[prost(bool, tag="2")] 68 | pub values: bool, 69 | #[prost(message, optional, tag="3")] 70 | pub start: ::core::option::Option<::prost_types::Timestamp>, 71 | #[prost(message, optional, tag="4")] 72 | pub end: ::core::option::Option<::prost_types::Timestamp>, 73 | } 74 | #[derive(Clone, PartialEq, ::prost::Message)] 75 | pub struct LabelResponse { 76 | #[prost(string, repeated, tag="1")] 77 | pub values: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 78 | } 79 | #[derive(Clone, PartialEq, ::prost::Message)] 80 | pub struct StreamAdapter { 81 | #[prost(string, tag="1")] 82 | pub labels: ::prost::alloc::string::String, 83 | #[prost(message, repeated, tag="2")] 84 | pub entries: ::prost::alloc::vec::Vec, 85 | /// hash contains the original hash of the stream. 86 | #[prost(uint64, tag="3")] 87 | pub hash: u64, 88 | } 89 | #[derive(Clone, PartialEq, ::prost::Message)] 90 | pub struct EntryAdapter { 91 | #[prost(message, optional, tag="1")] 92 | pub timestamp: ::core::option::Option<::prost_types::Timestamp>, 93 | #[prost(string, tag="2")] 94 | pub line: ::prost::alloc::string::String, 95 | } 96 | #[derive(Clone, PartialEq, ::prost::Message)] 97 | pub struct Sample { 98 | #[prost(int64, tag="1")] 99 | pub timestamp: i64, 100 | #[prost(double, tag="2")] 101 | pub value: f64, 102 | #[prost(uint64, tag="3")] 103 | pub hash: u64, 104 | } 105 | /// LegacySample exists for backwards compatibility reasons and is deprecated. Do not use. 106 | #[derive(Clone, PartialEq, ::prost::Message)] 107 | pub struct LegacySample { 108 | #[prost(double, tag="1")] 109 | pub value: f64, 110 | #[prost(int64, tag="2")] 111 | pub timestamp_ms: i64, 112 | } 113 | #[derive(Clone, PartialEq, ::prost::Message)] 114 | pub struct Series { 115 | #[prost(string, tag="1")] 116 | pub labels: ::prost::alloc::string::String, 117 | #[prost(message, repeated, tag="2")] 118 | pub samples: ::prost::alloc::vec::Vec, 119 | #[prost(uint64, tag="3")] 120 | pub stream_hash: u64, 121 | } 122 | #[derive(Clone, PartialEq, ::prost::Message)] 123 | pub struct TailRequest { 124 | #[prost(string, tag="1")] 125 | pub query: ::prost::alloc::string::String, 126 | #[prost(uint32, tag="3")] 127 | pub delay_for: u32, 128 | #[prost(uint32, tag="4")] 129 | pub limit: u32, 130 | #[prost(message, optional, tag="5")] 131 | pub start: ::core::option::Option<::prost_types::Timestamp>, 132 | } 133 | #[derive(Clone, PartialEq, ::prost::Message)] 134 | pub struct TailResponse { 135 | #[prost(message, optional, tag="1")] 136 | pub stream: ::core::option::Option, 137 | #[prost(message, repeated, tag="2")] 138 | pub dropped_streams: ::prost::alloc::vec::Vec, 139 | } 140 | #[derive(Clone, PartialEq, ::prost::Message)] 141 | pub struct SeriesRequest { 142 | #[prost(message, optional, tag="1")] 143 | pub start: ::core::option::Option<::prost_types::Timestamp>, 144 | #[prost(message, optional, tag="2")] 145 | pub end: ::core::option::Option<::prost_types::Timestamp>, 146 | #[prost(string, repeated, tag="3")] 147 | pub groups: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 148 | #[prost(string, repeated, tag="4")] 149 | pub shards: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 150 | } 151 | #[derive(Clone, PartialEq, ::prost::Message)] 152 | pub struct SeriesResponse { 153 | #[prost(message, repeated, tag="1")] 154 | pub series: ::prost::alloc::vec::Vec, 155 | } 156 | #[derive(Clone, PartialEq, ::prost::Message)] 157 | pub struct SeriesIdentifier { 158 | #[prost(map="string, string", tag="1")] 159 | pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, 160 | } 161 | #[derive(Clone, PartialEq, ::prost::Message)] 162 | pub struct DroppedStream { 163 | #[prost(message, optional, tag="1")] 164 | pub from: ::core::option::Option<::prost_types::Timestamp>, 165 | #[prost(message, optional, tag="2")] 166 | pub to: ::core::option::Option<::prost_types::Timestamp>, 167 | #[prost(string, tag="3")] 168 | pub labels: ::prost::alloc::string::String, 169 | } 170 | #[derive(Clone, PartialEq, ::prost::Message)] 171 | pub struct TimeSeriesChunk { 172 | #[prost(string, tag="1")] 173 | pub from_ingester_id: ::prost::alloc::string::String, 174 | #[prost(string, tag="2")] 175 | pub user_id: ::prost::alloc::string::String, 176 | #[prost(message, repeated, tag="3")] 177 | pub labels: ::prost::alloc::vec::Vec, 178 | #[prost(message, repeated, tag="4")] 179 | pub chunks: ::prost::alloc::vec::Vec, 180 | } 181 | #[derive(Clone, PartialEq, ::prost::Message)] 182 | pub struct LabelPair { 183 | #[prost(string, tag="1")] 184 | pub name: ::prost::alloc::string::String, 185 | #[prost(string, tag="2")] 186 | pub value: ::prost::alloc::string::String, 187 | } 188 | /// LegacyLabelPair exists for backwards compatibility reasons and is deprecated. Do not use. 189 | #[derive(Clone, PartialEq, ::prost::Message)] 190 | pub struct LegacyLabelPair { 191 | #[prost(bytes="vec", tag="1")] 192 | pub name: ::prost::alloc::vec::Vec, 193 | #[prost(bytes="vec", tag="2")] 194 | pub value: ::prost::alloc::vec::Vec, 195 | } 196 | #[derive(Clone, PartialEq, ::prost::Message)] 197 | pub struct Chunk { 198 | #[prost(bytes="vec", tag="1")] 199 | pub data: ::prost::alloc::vec::Vec, 200 | } 201 | #[derive(Clone, PartialEq, ::prost::Message)] 202 | pub struct TransferChunksResponse { 203 | } 204 | #[derive(Clone, PartialEq, ::prost::Message)] 205 | pub struct TailersCountRequest { 206 | } 207 | #[derive(Clone, PartialEq, ::prost::Message)] 208 | pub struct TailersCountResponse { 209 | #[prost(uint32, tag="1")] 210 | pub count: u32, 211 | } 212 | #[derive(Clone, PartialEq, ::prost::Message)] 213 | pub struct GetChunkIDsRequest { 214 | #[prost(string, tag="1")] 215 | pub matchers: ::prost::alloc::string::String, 216 | #[prost(message, optional, tag="2")] 217 | pub start: ::core::option::Option<::prost_types::Timestamp>, 218 | #[prost(message, optional, tag="3")] 219 | pub end: ::core::option::Option<::prost_types::Timestamp>, 220 | } 221 | #[derive(Clone, PartialEq, ::prost::Message)] 222 | pub struct GetChunkIDsResponse { 223 | #[prost(string, repeated, tag="1")] 224 | pub chunk_i_ds: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 225 | } 226 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] 227 | #[repr(i32)] 228 | pub enum Direction { 229 | Forward = 0, 230 | Backward = 1, 231 | } 232 | -------------------------------------------------------------------------------- /loki-api/src/stats.rs: -------------------------------------------------------------------------------- 1 | /// Result contains LogQL query statistics. 2 | #[derive(Clone, PartialEq, ::prost::Message)] 3 | pub struct Result { 4 | #[prost(message, optional, tag="1")] 5 | pub summary: ::core::option::Option, 6 | #[prost(message, optional, tag="2")] 7 | pub querier: ::core::option::Option, 8 | #[prost(message, optional, tag="3")] 9 | pub ingester: ::core::option::Option, 10 | } 11 | /// Summary is the summary of a query statistics. 12 | #[derive(Clone, PartialEq, ::prost::Message)] 13 | pub struct Summary { 14 | /// Total bytes processed per second. 15 | #[prost(int64, tag="1")] 16 | pub bytes_processed_per_second: i64, 17 | /// Total lines processed per second. 18 | #[prost(int64, tag="2")] 19 | pub lines_processed_per_second: i64, 20 | /// Total bytes processed. 21 | #[prost(int64, tag="3")] 22 | pub total_bytes_processed: i64, 23 | /// Total lines processed. 24 | #[prost(int64, tag="4")] 25 | pub total_lines_processed: i64, 26 | /// Execution time in seconds. 27 | /// In addition to internal calculations this is also returned by the HTTP API. 28 | /// Grafana expects time values to be returned in seconds as float. 29 | #[prost(double, tag="5")] 30 | pub exec_time: f64, 31 | /// Queue time in seconds. 32 | /// In addition to internal calculations this is also returned by the HTTP API. 33 | /// Grafana expects time values to be returned in seconds as float. 34 | #[prost(double, tag="6")] 35 | pub queue_time: f64, 36 | /// Total of subqueries created to fulfill this query. 37 | #[prost(int64, tag="7")] 38 | pub subqueries: i64, 39 | } 40 | #[derive(Clone, PartialEq, ::prost::Message)] 41 | pub struct Querier { 42 | #[prost(message, optional, tag="1")] 43 | pub store: ::core::option::Option, 44 | } 45 | #[derive(Clone, PartialEq, ::prost::Message)] 46 | pub struct Ingester { 47 | /// Total ingester reached for this query. 48 | #[prost(int32, tag="1")] 49 | pub total_reached: i32, 50 | /// Total of chunks matched by the query from ingesters 51 | #[prost(int64, tag="2")] 52 | pub total_chunks_matched: i64, 53 | /// Total of batches sent from ingesters. 54 | #[prost(int64, tag="3")] 55 | pub total_batches: i64, 56 | /// Total lines sent by ingesters. 57 | #[prost(int64, tag="4")] 58 | pub total_lines_sent: i64, 59 | #[prost(message, optional, tag="5")] 60 | pub store: ::core::option::Option, 61 | } 62 | #[derive(Clone, PartialEq, ::prost::Message)] 63 | pub struct Store { 64 | /// The total of chunk reference fetched from index. 65 | #[prost(int64, tag="1")] 66 | pub total_chunks_ref: i64, 67 | /// Total number of chunks fetched. 68 | #[prost(int64, tag="2")] 69 | pub total_chunks_downloaded: i64, 70 | /// Time spent fetching chunks in nanoseconds. 71 | #[prost(int64, tag="3")] 72 | pub chunks_download_time: i64, 73 | #[prost(message, optional, tag="4")] 74 | pub chunk: ::core::option::Option, 75 | } 76 | #[derive(Clone, PartialEq, ::prost::Message)] 77 | pub struct Chunk { 78 | /// Total bytes processed but was already in memory. (found in the headchunk) 79 | #[prost(int64, tag="4")] 80 | pub head_chunk_bytes: i64, 81 | /// Total lines processed but was already in memory. (found in the headchunk) 82 | #[prost(int64, tag="5")] 83 | pub head_chunk_lines: i64, 84 | /// Total bytes decompressed and processed from chunks. 85 | #[prost(int64, tag="6")] 86 | pub decompressed_bytes: i64, 87 | /// Total lines decompressed and processed from chunks. 88 | #[prost(int64, tag="7")] 89 | pub decompressed_lines: i64, 90 | /// Total bytes of compressed chunks (blocks) processed. 91 | #[prost(int64, tag="8")] 92 | pub compressed_bytes: i64, 93 | /// Total duplicates found while processing. 94 | #[prost(int64, tag="9")] 95 | pub total_duplicates: i64, 96 | } 97 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use super::event_channel; 2 | use super::BackgroundTask; 3 | use super::BackgroundTaskController; 4 | use super::Error; 5 | use super::ErrorI; 6 | use super::FormattedLabels; 7 | use super::Layer; 8 | use std::collections::hash_map; 9 | use std::collections::HashMap; 10 | use url::Url; 11 | 12 | /// Create a [`Builder`] for constructing a [`Layer`] and its corresponding 13 | /// [`BackgroundTask`]. 14 | /// 15 | /// See the crate's root documentation for an example. 16 | pub fn builder() -> Builder { 17 | let mut http_headers = reqwest::header::HeaderMap::new(); 18 | http_headers.insert( 19 | reqwest::header::CONTENT_TYPE, 20 | reqwest::header::HeaderValue::from_static("application/x-snappy"), 21 | ); 22 | Builder { 23 | labels: FormattedLabels::new(), 24 | extra_fields: HashMap::new(), 25 | http_headers, 26 | } 27 | } 28 | 29 | /// Builder for constructing a [`Layer`] and its corresponding 30 | /// [`BackgroundTask`]. 31 | /// 32 | /// See the crate's root documentation for an example. 33 | #[derive(Clone)] 34 | pub struct Builder { 35 | labels: FormattedLabels, 36 | extra_fields: HashMap, 37 | http_headers: reqwest::header::HeaderMap, 38 | } 39 | 40 | impl Builder { 41 | /// Add a label to the logs sent to Loki through the built `Layer`. 42 | /// 43 | /// Labels are supposed to be closed categories with few possible values. 44 | /// For example, `"environment"` with values `"ci"`, `"development"`, 45 | /// `"staging"` or `"production"` would work well. 46 | /// 47 | /// For open categories, extra fields are a better fit. See 48 | /// [`Builder::extra_field`]. 49 | /// 50 | /// No two labels can share the same name, and the key `"level"` is 51 | /// reserved for the log level. 52 | /// 53 | /// # Errors 54 | /// 55 | /// This function will return an error if a key is a duplicate or when the 56 | /// key is `"level"`. 57 | /// 58 | /// # Example 59 | /// 60 | /// ``` 61 | /// # use tracing_loki::Error; 62 | /// # fn main() -> Result<(), Error> { 63 | /// let builder = tracing_loki::builder() 64 | /// .label("environment", "production")?; 65 | /// # Ok(()) 66 | /// # } 67 | /// ``` 68 | pub fn label, T: AsRef>( 69 | mut self, 70 | key: S, 71 | value: T, 72 | ) -> Result { 73 | self.labels.add(key.into(), value.as_ref())?; 74 | Ok(self) 75 | } 76 | /// Set an extra field that is sent with all log records sent to Loki 77 | /// through the built layer. 78 | /// 79 | /// Fields are meant to be used for open categories or closed categories 80 | /// with many options. For example, `"run_id"` with randomly generated 81 | /// [UUIDv4](https://en.wikipedia.org/w/index.php?title=Universally_unique_identifier&oldid=1105876960#Version_4_(random))s 82 | /// would be a good fit for these extra fields. 83 | /// 84 | /// # Example 85 | /// 86 | /// ``` 87 | /// # use tracing_loki::Error; 88 | /// # fn main() -> Result<(), Error> { 89 | /// let builder = tracing_loki::builder() 90 | /// .extra_field("run_id", "5b6aedb4-e2c1-4ad9-b8a7-3ef92b5c8120")?; 91 | /// # Ok(()) 92 | /// # } 93 | /// ``` 94 | pub fn extra_field, T: Into>( 95 | mut self, 96 | key: S, 97 | value: T, 98 | ) -> Result { 99 | match self.extra_fields.entry(key.into()) { 100 | hash_map::Entry::Occupied(o) => { 101 | return Err(Error(ErrorI::DuplicateExtraField(o.key().clone()))); 102 | } 103 | hash_map::Entry::Vacant(v) => { 104 | v.insert(value.into()); 105 | } 106 | } 107 | Ok(self) 108 | } 109 | /// Set an extra HTTP header to be sent with all requests sent to Loki. 110 | /// 111 | /// This can be useful to set the `X-Scope-OrgID` header which Loki 112 | /// processes as the tenant ID in a multi-tenant setup. 113 | /// 114 | /// # Example 115 | /// 116 | /// ``` 117 | /// # use tracing_loki::Error; 118 | /// # fn main() -> Result<(), Error> { 119 | /// let builder = tracing_loki::builder() 120 | /// // Set the tenant ID for Loki. 121 | /// .http_header("X-Scope-OrgID", "7662a206-fa0f-407f-abe9-261d652c750b")?; 122 | /// # Ok(()) 123 | /// # } 124 | /// ``` 125 | pub fn http_header, T: AsRef>( 126 | mut self, 127 | key: S, 128 | value: T, 129 | ) -> Result { 130 | let key = key.as_ref(); 131 | let value = value.as_ref(); 132 | if self 133 | .http_headers 134 | .insert( 135 | reqwest::header::HeaderName::from_bytes(key.as_bytes()) 136 | .map_err(|_| Error(ErrorI::InvalidHttpHeaderName(key.into())))?, 137 | reqwest::header::HeaderValue::from_str(value) 138 | .map_err(|_| Error(ErrorI::InvalidHttpHeaderValue(key.into())))?, 139 | ) 140 | .is_some() 141 | { 142 | return Err(Error(ErrorI::DuplicateHttpHeader(key.into()))); 143 | } 144 | Ok(self) 145 | } 146 | /// Build the tracing [`Layer`] and its corresponding [`BackgroundTask`]. 147 | /// 148 | /// The `loki_url` is the URL of the Loki server, like 149 | /// `https://127.0.0.1:3100`. 150 | /// 151 | /// The [`Layer`] needs to be registered with a 152 | /// [`tracing_subscriber::Registry`], and the [`BackgroundTask`] needs to 153 | /// be [`tokio::spawn`]ed. 154 | /// 155 | /// **Note** that unlike the [`layer`](`crate::layer`) function, this 156 | /// function **does not strip off** the path component of `loki_url` before 157 | /// appending `/loki/api/v1/push`. 158 | /// 159 | /// See the crate's root documentation for an example. 160 | pub fn build_url(self, loki_url: Url) -> Result<(Layer, BackgroundTask), Error> { 161 | let (sender, receiver) = event_channel(); 162 | Ok(( 163 | Layer { 164 | sender, 165 | extra_fields: self.extra_fields, 166 | }, 167 | BackgroundTask::new(loki_url, self.http_headers, receiver, &self.labels)?, 168 | )) 169 | } 170 | /// Build the tracing [`Layer`], [`BackgroundTask`] and its 171 | /// [`BackgroundTaskController`]. 172 | /// 173 | /// The [`BackgroundTaskController`] can be used to signal the background 174 | /// task to shut down. 175 | /// 176 | /// The `loki_url` is the URL of the Loki server, like 177 | /// `https://127.0.0.1:3100`. 178 | /// 179 | /// The [`Layer`] needs to be registered with a 180 | /// [`tracing_subscriber::Registry`], and the [`BackgroundTask`] needs to 181 | /// be [`tokio::spawn`]ed. 182 | /// 183 | /// **Note** that unlike the [`layer`](`crate::layer`) function, this 184 | /// function **does not strip off** the path component of `loki_url` before 185 | /// appending `/loki/api/v1/push`. 186 | /// 187 | /// See the crate's root documentation for an example. 188 | pub fn build_controller_url( 189 | self, 190 | loki_url: Url, 191 | ) -> Result<(Layer, BackgroundTaskController, BackgroundTask), Error> { 192 | let (sender, receiver) = event_channel(); 193 | Ok(( 194 | Layer { 195 | sender: sender.clone(), 196 | extra_fields: self.extra_fields, 197 | }, 198 | BackgroundTaskController { sender }, 199 | BackgroundTask::new(loki_url, self.http_headers, receiver, &self.labels)?, 200 | )) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/labels.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fmt::Write as _; 3 | use tracing_core::Level; 4 | 5 | use super::Error; 6 | use super::ErrorI; 7 | 8 | #[derive(Clone)] 9 | pub struct FormattedLabels { 10 | seen_keys: HashSet, 11 | formatted: String, 12 | } 13 | 14 | impl FormattedLabels { 15 | pub fn new() -> FormattedLabels { 16 | FormattedLabels { 17 | seen_keys: HashSet::new(), 18 | formatted: String::from("{"), 19 | } 20 | } 21 | pub fn add(&mut self, key: String, value: &str) -> Result<(), Error> { 22 | // Couldn't find documentation except for the promtail source code: 23 | // https://github.com/grafana/loki/blob/8c06c546ab15a568f255461f10318dae37e022d3/vendor/github.com/prometheus/prometheus/promql/parser/generated_parser.y#L597-L598 24 | // 25 | // Apparently labels that confirm to yacc's "IDENTIFIER" are okay. I 26 | // couldn't find which those are. Let's be conservative and allow 27 | // `[A-Za-z_]*`. 28 | for (i, b) in key.bytes().enumerate() { 29 | match b { 30 | b'A'..=b'Z' | b'a'..=b'z' | b'_' => {} 31 | // The first byte outside of the above range must start a UTF-8 32 | // character. 33 | _ => { 34 | let c = key[i..].chars().next().unwrap(); 35 | return Err(Error(ErrorI::InvalidLabelCharacter(key, c))); 36 | } 37 | } 38 | } 39 | if key == "level" { 40 | return Err(Error(ErrorI::ReservedLabelLevel)); 41 | } 42 | 43 | // Couldn't find documentation except for the promtail source code: 44 | // https://github.com/grafana/loki/blob/8c06c546ab15a568f255461f10318dae37e022d3/clients/pkg/promtail/client/batch.go#L61-L75 45 | // 46 | // Go's %q displays the string in double quotes, escaping a few 47 | // characters, like Rust's {:?}. 48 | let old_len = self.formatted.len(); 49 | let sep = if self.formatted.len() <= 1 { "" } else { "," }; 50 | write!(&mut self.formatted, "{}{}={:?}", sep, key, value).unwrap(); 51 | 52 | if let Some(duplicate_key) = self.seen_keys.replace(key) { 53 | self.formatted.truncate(old_len); 54 | return Err(Error(ErrorI::DuplicateLabel(duplicate_key))); 55 | } 56 | Ok(()) 57 | } 58 | pub fn finish(&self, level: Level) -> String { 59 | let mut result = self.formatted.clone(); 60 | if result.len() > 1 { 61 | result.push(','); 62 | } 63 | result.push_str(match level { 64 | Level::TRACE => "level=\"trace\"}", 65 | Level::DEBUG => "level=\"debug\"}", 66 | Level::INFO => "level=\"info\"}", 67 | Level::WARN => "level=\"warn\"}", 68 | Level::ERROR => "level=\"error\"}", 69 | }); 70 | result 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod test { 76 | use super::FormattedLabels; 77 | use tracing_core::Level; 78 | 79 | #[test] 80 | fn simple() { 81 | assert_eq!( 82 | FormattedLabels::new().finish(Level::TRACE), 83 | r#"{level="trace"}"#, 84 | ); 85 | assert_eq!( 86 | FormattedLabels::new().finish(Level::DEBUG), 87 | r#"{level="debug"}"#, 88 | ); 89 | assert_eq!( 90 | FormattedLabels::new().finish(Level::INFO), 91 | r#"{level="info"}"#, 92 | ); 93 | assert_eq!( 94 | FormattedLabels::new().finish(Level::WARN), 95 | r#"{level="warn"}"#, 96 | ); 97 | assert_eq!( 98 | FormattedLabels::new().finish(Level::ERROR), 99 | r#"{level="error"}"#, 100 | ); 101 | } 102 | 103 | #[test] 104 | fn level() { 105 | assert!(FormattedLabels::new().add("level".into(), "").is_err()); 106 | assert!(FormattedLabels::new().add("level".into(), "blurb").is_err()); 107 | } 108 | 109 | #[test] 110 | fn duplicate() { 111 | let mut labels = FormattedLabels::new(); 112 | labels.add("label".into(), "abc").unwrap(); 113 | assert!(labels.clone().add("label".into(), "def").is_err()); 114 | assert!(labels.clone().add("label".into(), "abc").is_err()); 115 | assert!(labels.clone().add("label".into(), "").is_err()); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/level_map.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops; 3 | use std::slice; 4 | use tracing_core::Level; 5 | 6 | #[derive(Default)] 7 | pub struct LevelMap { 8 | map: [T; 5], 9 | } 10 | 11 | fn level_index(level: Level) -> usize { 12 | match level { 13 | Level::TRACE => 0, 14 | Level::DEBUG => 1, 15 | Level::INFO => 2, 16 | Level::WARN => 3, 17 | Level::ERROR => 4, 18 | } 19 | } 20 | 21 | impl LevelMap { 22 | pub fn from_fn T>(mut f: F) -> LevelMap { 23 | LevelMap { 24 | map: [ 25 | f(Level::TRACE), 26 | f(Level::DEBUG), 27 | f(Level::INFO), 28 | f(Level::WARN), 29 | f(Level::ERROR), 30 | ], 31 | } 32 | } 33 | pub fn values(&self) -> slice::Iter<'_, T> { 34 | self.map.iter() 35 | } 36 | pub fn values_mut(&mut self) -> slice::IterMut<'_, T> { 37 | self.map.iter_mut() 38 | } 39 | } 40 | 41 | impl fmt::Debug for LevelMap { 42 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 | f.debug_map() 44 | .entry(&Level::TRACE, &self[Level::TRACE]) 45 | .entry(&Level::DEBUG, &self[Level::DEBUG]) 46 | .entry(&Level::INFO, &self[Level::INFO]) 47 | .entry(&Level::WARN, &self[Level::WARN]) 48 | .entry(&Level::ERROR, &self[Level::ERROR]) 49 | .finish() 50 | } 51 | } 52 | 53 | impl ops::Index for LevelMap { 54 | type Output = T; 55 | fn index(&self, index: Level) -> &T { 56 | &self.map[level_index(index)] 57 | } 58 | } 59 | 60 | impl ops::IndexMut for LevelMap { 61 | fn index_mut(&mut self, index: Level) -> &mut T { 62 | &mut self.map[level_index(index)] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A [`tracing`] layer for shipping logs to [Grafana 2 | //! Loki](https://grafana.com/oss/loki/). 3 | //! 4 | //! Usage 5 | //! ===== 6 | //! 7 | //! ```rust 8 | //! use tracing_subscriber::layer::SubscriberExt; 9 | //! use tracing_subscriber::util::SubscriberInitExt; 10 | //! use std::process; 11 | //! use url::Url; 12 | //! 13 | //! #[tokio::main] 14 | //! async fn main() -> Result<(), tracing_loki::Error> { 15 | //! let (layer, task) = tracing_loki::builder() 16 | //! .label("host", "mine")? 17 | //! .extra_field("pid", format!("{}", process::id()))? 18 | //! .build_url(Url::parse("http://127.0.0.1:3100").unwrap())?; 19 | //! 20 | //! // We need to register our layer with `tracing`. 21 | //! tracing_subscriber::registry() 22 | //! .with(layer) 23 | //! // One could add more layers here, for example logging to stdout: 24 | //! // .with(tracing_subscriber::fmt::Layer::new()) 25 | //! .init(); 26 | //! 27 | //! // The background task needs to be spawned so the logs actually get 28 | //! // delivered. 29 | //! tokio::spawn(task); 30 | //! 31 | //! tracing::info!( 32 | //! task = "tracing_setup", 33 | //! result = "success", 34 | //! "tracing successfully set up", 35 | //! ); 36 | //! 37 | //! Ok(()) 38 | //! } 39 | //! ``` 40 | 41 | #![allow(clippy::or_fun_call)] 42 | #![allow(clippy::type_complexity)] 43 | #![deny(missing_docs)] 44 | 45 | #[cfg(not(feature = "compat-0-2-1"))] 46 | compile_error!( 47 | "The feature `compat-0-2-1` must be enabled to ensure \ 48 | forward compatibility with future versions of this crate" 49 | ); 50 | 51 | /// The re-exported `url` dependency of this crate. 52 | /// 53 | /// Use this to avoid depending on a potentially-incompatible `url` version yourself. 54 | pub extern crate url; 55 | 56 | use loki_api::logproto as loki; 57 | use loki_api::prost; 58 | use serde::Serialize; 59 | use std::cmp; 60 | use std::collections::HashMap; 61 | use std::error; 62 | use std::fmt; 63 | use std::future::Future; 64 | use std::mem; 65 | use std::pin::Pin; 66 | use std::task::Context; 67 | use std::task::Poll; 68 | use std::time::Duration; 69 | use std::time::SystemTime; 70 | use tokio::sync::mpsc; 71 | use tokio_stream::wrappers::ReceiverStream; 72 | use tokio_stream::Stream; 73 | use tracing::instrument::WithSubscriber; 74 | use tracing_core::field::Field; 75 | use tracing_core::field::Visit; 76 | use tracing_core::span::Attributes; 77 | use tracing_core::span::Id; 78 | use tracing_core::span::Record; 79 | use tracing_core::Event; 80 | use tracing_core::Level; 81 | use tracing_core::Subscriber; 82 | use tracing_log::NormalizeEvent; 83 | use tracing_subscriber::layer::Context as TracingContext; 84 | use tracing_subscriber::registry::LookupSpan; 85 | use url::Url; 86 | 87 | use labels::FormattedLabels; 88 | use level_map::LevelMap; 89 | use log_support::SerializeEventFieldMapStrippingLog; 90 | use no_subscriber::NoSubscriber; 91 | use ErrorInner as ErrorI; 92 | 93 | pub use builder::builder; 94 | pub use builder::Builder; 95 | 96 | mod builder; 97 | mod labels; 98 | mod level_map; 99 | mod log_support; 100 | mod no_subscriber; 101 | 102 | #[cfg(doctest)] 103 | #[doc = include_str!("../README.md")] 104 | struct ReadmeDoctests; 105 | 106 | fn event_channel() -> ( 107 | mpsc::Sender>, 108 | mpsc::Receiver>, 109 | ) { 110 | mpsc::channel(512) 111 | } 112 | 113 | /// The error type for constructing a [`Layer`]. 114 | /// 115 | /// Nothing except for the [`std::error::Error`] (and [`std::fmt::Debug`] and 116 | /// [`std::fmt::Display`]) implementation of this type is exposed. 117 | pub struct Error(ErrorInner); 118 | 119 | impl fmt::Debug for Error { 120 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 121 | self.0.fmt(f) 122 | } 123 | } 124 | impl fmt::Display for Error { 125 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 126 | self.0.fmt(f) 127 | } 128 | } 129 | impl error::Error for Error {} 130 | 131 | #[derive(Debug)] 132 | enum ErrorInner { 133 | DuplicateExtraField(String), 134 | DuplicateHttpHeader(String), 135 | DuplicateLabel(String), 136 | InvalidHttpHeaderName(String), 137 | InvalidHttpHeaderValue(String), 138 | InvalidLabelCharacter(String, char), 139 | InvalidLokiUrl, 140 | ReservedLabelLevel, 141 | } 142 | 143 | impl fmt::Display for ErrorInner { 144 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 145 | use self::ErrorInner::*; 146 | match self { 147 | DuplicateExtraField(key) => write!(f, "duplicate extra field key {:?}", key), 148 | DuplicateHttpHeader(name) => write!(f, "duplicate HTTP header {:?}", name), 149 | DuplicateLabel(key) => write!(f, "duplicate label key {:?}", key), 150 | InvalidHttpHeaderName(name) => write!(f, "invalid HTTP header name {:?}", name), 151 | InvalidHttpHeaderValue(name) => write!(f, "invalid HTTP header value for {:?}", name), 152 | InvalidLabelCharacter(key, c) => { 153 | write!(f, "invalid label character {:?} in key {:?}", c, key) 154 | } 155 | InvalidLokiUrl => write!(f, "invalid Loki URL"), 156 | ReservedLabelLevel => write!(f, "cannot add custom label for \"level\""), 157 | } 158 | } 159 | } 160 | 161 | /// Construct a [`Layer`] and its corresponding [`BackgroundTask`]. 162 | /// 163 | /// The [`Layer`] needs to be registered with a 164 | /// [`tracing_subscriber::Registry`], and the [`BackgroundTask`] needs to be 165 | /// [`tokio::spawn`]ed. 166 | /// 167 | /// **Note** that unlike the [`Builder::build_url`] function, this function 168 | /// **strips off** the path component of `loki_url` before appending 169 | /// `/loki/api/v1/push`. 170 | /// 171 | /// See [`builder()`] and this crate's root documentation for a more flexible 172 | /// method. 173 | /// 174 | /// # Example 175 | /// 176 | /// ```rust 177 | /// use tracing_subscriber::layer::SubscriberExt; 178 | /// use tracing_subscriber::util::SubscriberInitExt; 179 | /// use url::Url; 180 | /// 181 | /// #[tokio::main] 182 | /// async fn main() -> Result<(), tracing_loki::Error> { 183 | /// let (layer, task) = tracing_loki::layer( 184 | /// Url::parse("http://127.0.0.1:3100").unwrap(), 185 | /// vec![("host".into(), "mine".into())].into_iter().collect(), 186 | /// vec![].into_iter().collect(), 187 | /// )?; 188 | /// 189 | /// // We need to register our layer with `tracing`. 190 | /// tracing_subscriber::registry() 191 | /// .with(layer) 192 | /// // One could add more layers here, for example logging to stdout: 193 | /// // .with(tracing_subscriber::fmt::Layer::new()) 194 | /// .init(); 195 | /// 196 | /// // The background task needs to be spawned so the logs actually get 197 | /// // delivered. 198 | /// tokio::spawn(task); 199 | /// 200 | /// tracing::info!( 201 | /// task = "tracing_setup", 202 | /// result = "success", 203 | /// "tracing successfully set up", 204 | /// ); 205 | /// 206 | /// Ok(()) 207 | /// } 208 | /// ``` 209 | pub fn layer( 210 | loki_url: Url, 211 | labels: HashMap, 212 | extra_fields: HashMap, 213 | ) -> Result<(Layer, BackgroundTask), Error> { 214 | let mut builder = builder(); 215 | for (key, value) in labels { 216 | builder = builder.label(key, value)?; 217 | } 218 | for (key, value) in extra_fields { 219 | builder = builder.extra_field(key, value)?; 220 | } 221 | builder.build_url( 222 | loki_url 223 | .join("/") 224 | .map_err(|_| Error(ErrorI::InvalidLokiUrl))?, 225 | ) 226 | } 227 | 228 | /// The [`tracing_subscriber::Layer`] implementation for the Loki backend. 229 | /// 230 | /// See the crate's root documentation for an example. 231 | pub struct Layer { 232 | extra_fields: HashMap, 233 | sender: mpsc::Sender>, 234 | } 235 | 236 | struct LokiEvent { 237 | trigger_send: bool, 238 | timestamp: SystemTime, 239 | level: Level, 240 | message: String, 241 | } 242 | 243 | #[derive(Serialize)] 244 | struct SerializedEvent<'a> { 245 | #[serde(flatten)] 246 | event: SerializeEventFieldMapStrippingLog<'a>, 247 | #[serde(flatten)] 248 | extra_fields: &'a HashMap, 249 | #[serde(flatten)] 250 | span_fields: serde_json::Map, 251 | _spans: &'a [&'a str], 252 | _target: &'a str, 253 | _module_path: Option<&'a str>, 254 | _file: Option<&'a str>, 255 | _line: Option, 256 | } 257 | 258 | #[derive(Default)] 259 | struct Fields { 260 | fields: serde_json::Map, 261 | } 262 | 263 | impl Fields { 264 | fn record_impl(&mut self, field: &Field, value: serde_json::Value) { 265 | self.fields.insert(field.name().into(), value); 266 | } 267 | fn record>(&mut self, field: &Field, value: T) { 268 | self.record_impl(field, value.into()); 269 | } 270 | } 271 | 272 | impl Visit for Fields { 273 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { 274 | self.record(field, format!("{:?}", value)); 275 | } 276 | fn record_f64(&mut self, field: &Field, value: f64) { 277 | self.record(field, value); 278 | } 279 | fn record_i64(&mut self, field: &Field, value: i64) { 280 | self.record(field, value); 281 | } 282 | fn record_u64(&mut self, field: &Field, value: u64) { 283 | self.record(field, value); 284 | } 285 | fn record_bool(&mut self, field: &Field, value: bool) { 286 | self.record(field, value); 287 | } 288 | fn record_str(&mut self, field: &Field, value: &str) { 289 | self.record(field, value); 290 | } 291 | fn record_error(&mut self, field: &Field, value: &(dyn error::Error + 'static)) { 292 | self.record(field, format!("{}", value)); 293 | } 294 | } 295 | 296 | impl LookupSpan<'a>> tracing_subscriber::Layer for Layer { 297 | fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: TracingContext<'_, S>) { 298 | let span = ctx.span(id).expect("Span not found, this is a bug"); 299 | let mut extensions = span.extensions_mut(); 300 | if extensions.get_mut::().is_none() { 301 | let mut fields = Fields::default(); 302 | attrs.record(&mut fields); 303 | extensions.insert(fields); 304 | } 305 | } 306 | fn on_record(&self, id: &Id, values: &Record<'_>, ctx: TracingContext<'_, S>) { 307 | let span = ctx.span(id).expect("Span not found, this is a bug"); 308 | let mut extensions = span.extensions_mut(); 309 | let fields = extensions.get_mut::().expect("unregistered span"); 310 | values.record(fields); 311 | } 312 | fn on_event(&self, event: &Event<'_>, ctx: TracingContext<'_, S>) { 313 | let timestamp = SystemTime::now(); 314 | let normalized_meta = event.normalized_metadata(); 315 | let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); 316 | let mut span_fields: serde_json::Map = Default::default(); 317 | let spans = event 318 | .parent() 319 | .cloned() 320 | .or_else(|| ctx.current_span().id().cloned()) 321 | .and_then(|id| { 322 | ctx.span_scope(&id).map(|scope| { 323 | scope.from_root().fold(Vec::new(), |mut spans, span| { 324 | span_fields.extend( 325 | span.extensions() 326 | .get::() 327 | .expect("unregistered span") 328 | .fields 329 | .iter() 330 | .map(|(f, v)| (f.clone(), v.clone())), 331 | ); 332 | spans.push(span.name()); 333 | spans 334 | }) 335 | }) 336 | }) 337 | .unwrap_or(Vec::new()); 338 | // TODO: Anything useful to do when the capacity has been reached? 339 | let _ = self.sender.try_send(Some(LokiEvent { 340 | trigger_send: !meta.target().starts_with("tracing_loki"), 341 | timestamp, 342 | level: *meta.level(), 343 | message: serde_json::to_string(&SerializedEvent { 344 | event: SerializeEventFieldMapStrippingLog(event), 345 | extra_fields: &self.extra_fields, 346 | span_fields, 347 | _spans: &spans, 348 | _target: meta.target(), 349 | _module_path: meta.module_path(), 350 | _file: meta.file(), 351 | _line: meta.line(), 352 | }) 353 | .expect("json serialization shouldn't fail"), 354 | })); 355 | } 356 | } 357 | 358 | struct SendQueue { 359 | encoded_labels: String, 360 | sending: Vec, 361 | to_send: Vec, 362 | } 363 | 364 | impl SendQueue { 365 | fn new(encoded_labels: String) -> SendQueue { 366 | SendQueue { 367 | encoded_labels, 368 | sending: Vec::new(), 369 | to_send: Vec::new(), 370 | } 371 | } 372 | fn push(&mut self, event: LokiEvent) { 373 | // TODO: Add limit. 374 | self.to_send.push(event); 375 | } 376 | fn drop_outstanding(&mut self) -> usize { 377 | let len = self.sending.len(); 378 | self.sending.clear(); 379 | len 380 | } 381 | fn on_send_result(&mut self, result: Result<(), ()>) { 382 | match result { 383 | Ok(()) => self.sending.clear(), 384 | Err(()) => { 385 | self.sending.append(&mut self.to_send); 386 | mem::swap(&mut self.sending, &mut self.to_send); 387 | } 388 | } 389 | } 390 | fn should_send(&self) -> bool { 391 | self.to_send.iter().any(|e| e.trigger_send) 392 | } 393 | fn prepare_sending(&mut self) -> loki::StreamAdapter { 394 | if !self.sending.is_empty() { 395 | panic!("can only prepare sending while no request is in flight"); 396 | } 397 | mem::swap(&mut self.sending, &mut self.to_send); 398 | loki::StreamAdapter { 399 | labels: self.encoded_labels.clone(), 400 | entries: self 401 | .sending 402 | .iter() 403 | .map(|e| loki::EntryAdapter { 404 | timestamp: Some(e.timestamp.into()), 405 | line: e.message.clone(), 406 | }) 407 | .collect(), 408 | // Couldn't find documentation except for the promtail source code: 409 | // https://github.com/grafana/loki/blob/8c06c546ab15a568f255461f10318dae37e022d3/clients/pkg/promtail/client/batch.go#L55-L58 410 | // 411 | // In the Go code, the hash value isn't initialized explicitly, 412 | // hence it is set to 0. 413 | hash: 0, 414 | } 415 | } 416 | } 417 | 418 | #[derive(Debug)] 419 | struct BadRedirect { 420 | status: u16, 421 | to: Url, 422 | } 423 | 424 | impl fmt::Display for BadRedirect { 425 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 426 | // Following such a redirect drops the request body, and will likely 427 | // give an HTTP 200 response even though nobody ever looked at the POST 428 | // body. 429 | // 430 | // This can e.g. happen for login redirects when you post to a 431 | // login-protected URL. 432 | write!(f, "invalid HTTP {} redirect to {}", self.status, self.to) 433 | } 434 | } 435 | 436 | impl error::Error for BadRedirect {} 437 | 438 | /// The background task that ships logs to Loki. It must be [`tokio::spawn`]ed 439 | /// by the calling application. 440 | /// 441 | /// See the crate's root documentation for an example. 442 | pub struct BackgroundTask { 443 | loki_url: Url, 444 | receiver: ReceiverStream>, 445 | queues: LevelMap, 446 | buffer: Buffer, 447 | http_client: reqwest::Client, 448 | backoff_count: u32, 449 | backoff: Option>>, 450 | quitting: bool, 451 | send_task: 452 | Option>> + Send + 'static>>>, 453 | } 454 | 455 | impl BackgroundTask { 456 | fn new( 457 | loki_url: Url, 458 | http_headers: reqwest::header::HeaderMap, 459 | receiver: mpsc::Receiver>, 460 | labels: &FormattedLabels, 461 | ) -> Result { 462 | Ok(BackgroundTask { 463 | receiver: ReceiverStream::new(receiver), 464 | loki_url: loki_url 465 | .join("loki/api/v1/push") 466 | .map_err(|_| Error(ErrorI::InvalidLokiUrl))?, 467 | queues: LevelMap::from_fn(|level| SendQueue::new(labels.finish(level))), 468 | buffer: Buffer::new(), 469 | http_client: reqwest::Client::builder() 470 | .user_agent(concat!( 471 | env!("CARGO_PKG_NAME"), 472 | "/", 473 | env!("CARGO_PKG_VERSION") 474 | )) 475 | .default_headers(http_headers) 476 | .redirect(reqwest::redirect::Policy::custom(|a| { 477 | let status = a.status().as_u16(); 478 | if status == 302 || status == 303 { 479 | let to = a.url().clone(); 480 | return a.error(BadRedirect { status, to }); 481 | } 482 | reqwest::redirect::Policy::default().redirect(a) 483 | })) 484 | .build() 485 | .expect("reqwest client builder"), 486 | backoff_count: 0, 487 | backoff: None, 488 | quitting: false, 489 | send_task: None, 490 | }) 491 | } 492 | fn backoff_time(&self) -> (bool, Duration) { 493 | let backoff_time = if self.backoff_count >= 1 { 494 | Duration::from_millis( 495 | 500u64 496 | .checked_shl(self.backoff_count - 1) 497 | .unwrap_or(u64::MAX), 498 | ) 499 | } else { 500 | Duration::from_millis(0) 501 | }; 502 | ( 503 | backoff_time >= Duration::from_secs(30), 504 | cmp::min(backoff_time, Duration::from_secs(600)), 505 | ) 506 | } 507 | } 508 | 509 | impl Future for BackgroundTask { 510 | type Output = (); 511 | fn poll(mut self: Pin<&mut BackgroundTask>, cx: &mut Context<'_>) -> Poll<()> { 512 | let mut default_guard = tracing::subscriber::set_default(NoSubscriber::default()); 513 | 514 | while let Poll::Ready(maybe_maybe_item) = Pin::new(&mut self.receiver).poll_next(cx) { 515 | match maybe_maybe_item { 516 | Some(Some(item)) => self.queues[item.level].push(item), 517 | Some(None) => self.quitting = true, // Explicit close. 518 | None => self.quitting = true, // The sender was dropped. 519 | } 520 | } 521 | 522 | let mut backing_off = if let Some(backoff) = &mut self.backoff { 523 | matches!(Pin::new(backoff).poll(cx), Poll::Pending) 524 | } else { 525 | false 526 | }; 527 | if !backing_off { 528 | self.backoff = None; 529 | } 530 | loop { 531 | if let Some(send_task) = &mut self.send_task { 532 | match Pin::new(send_task).poll(cx) { 533 | Poll::Ready(res) => { 534 | if let Err(e) = &res { 535 | let (drop_outstanding, backoff_time) = self.backoff_time(); 536 | drop(default_guard); 537 | tracing::error!( 538 | error_count = self.backoff_count + 1, 539 | ?backoff_time, 540 | error = %e, 541 | "couldn't send logs to loki", 542 | ); 543 | default_guard = 544 | tracing::subscriber::set_default(NoSubscriber::default()); 545 | if drop_outstanding { 546 | let num_dropped: usize = 547 | self.queues.values_mut().map(|q| q.drop_outstanding()).sum(); 548 | drop(default_guard); 549 | tracing::error!( 550 | num_dropped, 551 | "dropped outstanding messages due to sending errors", 552 | ); 553 | default_guard = 554 | tracing::subscriber::set_default(NoSubscriber::default()); 555 | } 556 | self.backoff = Some(Box::pin(tokio::time::sleep(backoff_time))); 557 | self.backoff_count += 1; 558 | backing_off = true; 559 | } else { 560 | self.backoff_count = 0; 561 | } 562 | let res = res.map_err(|_| ()); 563 | for q in self.queues.values_mut() { 564 | q.on_send_result(res); 565 | } 566 | self.send_task = None; 567 | } 568 | Poll::Pending => {} 569 | } 570 | } 571 | if self.send_task.is_none() 572 | && !backing_off 573 | && self.queues.values().any(|q| q.should_send()) 574 | { 575 | let streams = self 576 | .queues 577 | .values_mut() 578 | .map(|q| q.prepare_sending()) 579 | .filter(|s| !s.entries.is_empty()) 580 | .collect(); 581 | let body = self 582 | .buffer 583 | .encode(&loki::PushRequest { streams }) 584 | .to_owned(); 585 | let request_builder = self.http_client.post(self.loki_url.clone()); 586 | self.send_task = Some(Box::pin( 587 | async move { 588 | request_builder 589 | .header(reqwest::header::CONTENT_TYPE, "application/x-snappy") 590 | .body(body) 591 | .send() 592 | .await? 593 | .error_for_status()?; 594 | Ok(()) 595 | } 596 | .with_subscriber(NoSubscriber::default()), 597 | )); 598 | } else { 599 | break; 600 | } 601 | } 602 | if self.quitting && self.send_task.is_none() { 603 | Poll::Ready(()) 604 | } else { 605 | Poll::Pending 606 | } 607 | } 608 | } 609 | 610 | struct Buffer { 611 | encoded: Vec, 612 | snappy: Vec, 613 | } 614 | 615 | impl Buffer { 616 | pub fn new() -> Buffer { 617 | Buffer { 618 | encoded: Vec::new(), 619 | snappy: Vec::new(), 620 | } 621 | } 622 | pub fn encode<'a, T: prost::Message>(&'a mut self, message: &T) -> &'a [u8] { 623 | self.encoded.clear(); 624 | message 625 | .encode(&mut self.encoded) 626 | .expect("protobuf encoding is infallible"); 627 | self.compress_encoded() 628 | } 629 | fn compress_encoded(&mut self) -> &[u8] { 630 | self.snappy 631 | .resize(snap::raw::max_compress_len(self.encoded.len()), 0); 632 | // Couldn't find documentation except for the promtail source code: 633 | // https://github.com/grafana/loki/blob/8c06c546ab15a568f255461f10318dae37e022d3/clients/pkg/promtail/client/batch.go#L101 634 | // 635 | // In the Go code, `snappy.Encode` is used, which corresponds to the 636 | // snappy block format, and not the snappy stream format. hence 637 | // `snap::raw` instead of `snap::write` is needed. 638 | let snappy_len = snap::raw::Encoder::new() 639 | .compress(&self.encoded, &mut self.snappy) 640 | .expect("snappy encoding is infallible"); 641 | &self.snappy[..snappy_len] 642 | } 643 | } 644 | 645 | /// Handle to cleanly shut down the `BackgroundTask`. 646 | /// 647 | /// It'll still try to send all available data and then quit. 648 | pub struct BackgroundTaskController { 649 | sender: mpsc::Sender>, 650 | } 651 | 652 | impl BackgroundTaskController { 653 | /// Shut down the associated `BackgroundTask`. 654 | pub async fn shutdown(&self) { 655 | // Ignore the error. If no one is listening, it already shut down. 656 | let _ = self.sender.send(None).await; 657 | } 658 | } 659 | -------------------------------------------------------------------------------- /src/log_support.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::SerializeMap; 2 | use serde::Serialize; 3 | use serde::Serializer; 4 | use std::error; 5 | use std::fmt; 6 | use tracing_core::field::Visit; 7 | use tracing_core::Event; 8 | use tracing_core::Field; 9 | use tracing_serde::SerdeMapVisitor; 10 | 11 | pub struct SerializeEventFieldMapStrippingLog<'a>(pub &'a Event<'a>); 12 | 13 | impl<'a> Serialize for SerializeEventFieldMapStrippingLog<'a> { 14 | fn serialize(&self, serializer: S) -> Result { 15 | let len = self.0.fields().count(); 16 | let serializer = serializer.serialize_map(Some(len))?; 17 | let mut visitor = SerdeMapVisitorStrippingLog::new(serializer); 18 | self.0.record(&mut visitor); 19 | visitor.finish() 20 | } 21 | } 22 | 23 | struct SerdeMapVisitorStrippingLog(SerdeMapVisitor); 24 | 25 | impl SerdeMapVisitorStrippingLog { 26 | fn new(serializer: S) -> SerdeMapVisitorStrippingLog { 27 | SerdeMapVisitorStrippingLog(SerdeMapVisitor::new(serializer)) 28 | } 29 | fn ignore(field: &Field) -> bool { 30 | field.name().starts_with("log.") 31 | } 32 | fn finish(self) -> Result { 33 | self.0.finish() 34 | } 35 | } 36 | 37 | impl Visit for SerdeMapVisitorStrippingLog { 38 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { 39 | if !Self::ignore(field) { 40 | self.0.record_debug(field, value); 41 | } 42 | } 43 | fn record_f64(&mut self, field: &Field, value: f64) { 44 | if !Self::ignore(field) { 45 | self.0.record_f64(field, value); 46 | } 47 | } 48 | fn record_i64(&mut self, field: &Field, value: i64) { 49 | if !Self::ignore(field) { 50 | self.0.record_i64(field, value); 51 | } 52 | } 53 | fn record_u64(&mut self, field: &Field, value: u64) { 54 | if !Self::ignore(field) { 55 | self.0.record_u64(field, value); 56 | } 57 | } 58 | fn record_bool(&mut self, field: &Field, value: bool) { 59 | if !Self::ignore(field) { 60 | self.0.record_bool(field, value); 61 | } 62 | } 63 | fn record_str(&mut self, field: &Field, value: &str) { 64 | if !Self::ignore(field) { 65 | self.0.record_str(field, value); 66 | } 67 | } 68 | fn record_error(&mut self, field: &Field, value: &(dyn error::Error + 'static)) { 69 | if !Self::ignore(field) { 70 | self.0.record_error(field, value); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/no_subscriber.rs: -------------------------------------------------------------------------------- 1 | //! Copy-pasted from 2 | //! . 3 | //! 4 | //! This is done to get a different type ID to work around the issue 5 | //! . 6 | 7 | use tracing_core::span; 8 | use tracing_core::Event; 9 | use tracing_core::Interest; 10 | use tracing_core::Metadata; 11 | use tracing_core::Subscriber; 12 | 13 | /// A no-op [`Subscriber`]. 14 | /// 15 | /// [`NoSubscriber`] implements the [`Subscriber`] trait by never being enabled, 16 | /// never being interested in any callsite, and dropping all spans and events. 17 | #[derive(Copy, Clone, Debug, Default)] 18 | pub struct NoSubscriber(()); 19 | 20 | impl Subscriber for NoSubscriber { 21 | #[inline] 22 | fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest { 23 | Interest::never() 24 | } 25 | 26 | fn new_span(&self, _: &span::Attributes<'_>) -> span::Id { 27 | span::Id::from_u64(0xDEAD) 28 | } 29 | 30 | fn event(&self, _event: &Event<'_>) {} 31 | 32 | fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {} 33 | 34 | fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {} 35 | 36 | #[inline] 37 | fn enabled(&self, _metadata: &Metadata<'_>) -> bool { 38 | false 39 | } 40 | 41 | fn enter(&self, _span: &span::Id) {} 42 | fn exit(&self, _span: &span::Id) {} 43 | } 44 | --------------------------------------------------------------------------------