From 0aca9d13da0f13b92b4444ed2f2088f22bf8488f Mon Sep 17 00:00:00 2001 From: Hiroto Funakoshi Date: Thu, 8 Aug 2024 16:50:39 +0900 Subject: [PATCH] Introduce an observability crate using opentelemetry-rust (#2535) * feat: add observability crate Signed-off-by: hlts2 * fix: fix crate name Signed-off-by: hlts2 * feat: add module files Signed-off-by: hlts2 * feat: create config Signed-off-by: hlts2 * fix: implement from trait Signed-off-by: hlts2 * feat: add tracer and meter macro Signed-off-by: hlts2 * feat: add license Signed-off-by: hlts2 * fix: use full module path Signed-off-by: hlts2 * fix: add shutdown method Signed-off-by: hlts2 * feat: add build method to crate tracer provider and meter provider Signed-off-by: hlts2 * fix: module path Signed-off-by: hlts2 * fix: add export timeout duration and comment Signed-off-by: hlts2 * feat: update interface Signed-off-by: hlts2 * feat: add endpint configuration to root configuration Signed-off-by: hlts2 * fix: update deps Signed-off-by: hlts2 * fix: small refactor Signed-off-by: hlts2 * fix: deleted build method to create trace and metrics provider Signed-off-by: hlts2 --------- Signed-off-by: hlts2 --- rust/Cargo.lock | 458 +++++++++++++++++++ rust/Cargo.toml | 8 +- rust/libs/observability/Cargo.toml | 33 ++ rust/libs/observability/src/config.rs | 142 ++++++ rust/libs/observability/src/lib.rs | 19 + rust/libs/observability/src/macros.rs | 150 ++++++ rust/libs/observability/src/observability.rs | 124 +++++ 7 files changed, 933 insertions(+), 1 deletion(-) create mode 100644 rust/libs/observability/Cargo.toml create mode 100644 rust/libs/observability/src/config.rs create mode 100644 rust/libs/observability/src/lib.rs create mode 100644 rust/libs/observability/src/macros.rs create mode 100644 rust/libs/observability/src/observability.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index c9e69d71a6..c6b8a0217d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -161,6 +161,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -195,6 +201,22 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cxx" version = "1.0.124" @@ -245,6 +267,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -267,6 +298,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -282,6 +322,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -301,9 +369,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -323,6 +396,12 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "h2" version = "0.3.26" @@ -430,6 +509,16 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -450,6 +539,12 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "is_ci" version = "1.2.0" @@ -471,6 +566,21 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.155" @@ -502,6 +612,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "matchit" version = "0.7.3" @@ -590,6 +706,15 @@ dependencies = [ "rand", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.3" @@ -599,12 +724,125 @@ dependencies = [ "memchr", ] +[[package]] +name = "observability" +version = "0.1.0" +dependencies = [ + "anyhow", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", + "paste", + "scopeguard", + "serde_json", + "tokio", + "url", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opentelemetry" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry-http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0ba633e55c5ea6f431875ba55e71664f2fa5d3a90bd34ec9302eecc41c865dd" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cefe0543875379e47eb5f1e68ff83f45cc41366a92dfd0d073d513bf68e9a05" + +[[package]] +name = "opentelemetry_sdk" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "lazy_static", + "once_cell", + "opentelemetry", + "ordered-float", + "percent-encoding", + "rand", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6" +dependencies = [ + "num-traits", +] + [[package]] name = "owo-colors" version = "4.0.0" @@ -634,6 +872,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -780,6 +1024,42 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -805,6 +1085,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -837,6 +1123,30 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -915,6 +1225,27 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -965,6 +1296,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.39.2" @@ -1136,6 +1482,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -1148,12 +1500,32 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "want" version = "0.3.1" @@ -1169,6 +1541,82 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi-util" version = "0.1.9" @@ -1326,6 +1774,16 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 666259b2b2..1c1c581bb1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -14,4 +14,10 @@ # limitations under the License. # [workspace] -members = ["libs/proto", "libs/ngt", "libs/ngt-rs", "bin/agent"] +members = [ + "libs/ngt", + "libs/ngt-rs", + "libs/observability", + "libs/proto", + "bin/agent", +] diff --git a/rust/libs/observability/Cargo.toml b/rust/libs/observability/Cargo.toml new file mode 100644 index 0000000000..4ef91d715e --- /dev/null +++ b/rust/libs/observability/Cargo.toml @@ -0,0 +1,33 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +[package] +name = "observability" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +opentelemetry = { version = "0.23" } +opentelemetry_sdk = { version = "0.23", features = ["rt-tokio"] } +opentelemetry-otlp = { version = "0.16.0", features = ["http-proto", "reqwest-client", "logs"] } +tokio = { version = "1.38.0", features = ["full"] } +serde_json = { version="1.0.120" } +opentelemetry-semantic-conventions = { version = "0.16.0"} +scopeguard = { version = "1.2.0"} +paste = {version = "1.0.15"} +anyhow = { version = "1.0.86"} +url = { version = "2.5.2"} diff --git a/rust/libs/observability/src/config.rs b/rust/libs/observability/src/config.rs new file mode 100644 index 0000000000..1faa8fd90e --- /dev/null +++ b/rust/libs/observability/src/config.rs @@ -0,0 +1,142 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +use std::collections::HashMap; +use std::time::Duration; + +use opentelemetry::KeyValue; +use opentelemetry_sdk::{self, Resource}; + +#[derive(Clone, Debug)] +pub struct Config { + pub enabled: bool, + pub endpoint: String, + pub attributes: HashMap, + pub tracer: Tracer, + pub meter: Meter, +} + +#[derive(Clone, Debug, Default)] +pub struct Tracer { + pub enabled: bool, +} + +#[derive(Clone, Debug)] +pub struct Meter { + pub enabled: bool, + pub export_duration: Duration, + pub export_timeout_duration: Duration, +} + +impl Config { + pub fn new() -> Self { + Self::default() + } + + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + pub fn endpoint(mut self, endpoint: &str) -> Self { + self.endpoint = endpoint.to_string(); + self + } + + pub fn attributes(mut self, attrs: HashMap) -> Self { + self.attributes = attrs; + self + } + + pub fn attribute(mut self, key: &str, value: &str) -> Self { + self.attributes.insert(key.to_string(), value.to_string()); + self + } + + pub fn tracer(mut self, cfg: Tracer) -> Self { + self.tracer = cfg; + self + } + + pub fn meter(mut self, cfg: Meter) -> Self { + self.meter = cfg; + self + } +} + +impl Default for Config { + fn default() -> Self { + Self { + enabled: false, + endpoint: "".to_string(), + attributes: HashMap::new(), + tracer: Tracer::default(), + meter: Meter::default(), + } + } +} + +impl From<&Config> for Resource { + fn from(value: &Config) -> Self { + let key_values: Vec = value + .attributes + .iter() + .map(|(key, val)| KeyValue::new(key.clone(), val.clone())) + .collect(); + Resource::new(key_values) + } +} + +impl Tracer { + pub fn new() -> Self { + Tracer::default() + } + + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } +} + +impl Meter { + pub fn new() -> Self { + Meter::default() + } + + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + pub fn export_duration(mut self, dur: Duration) -> Self { + self.export_duration = dur; + self + } + + pub fn export_timeout_duration(mut self, dur: Duration) -> Self { + self.export_timeout_duration = dur; + self + } +} + +impl Default for Meter { + fn default() -> Self { + Self { + enabled: false, + export_duration: Duration::from_secs(1), + export_timeout_duration: Duration::from_secs(5), + } + } +} diff --git a/rust/libs/observability/src/lib.rs b/rust/libs/observability/src/lib.rs new file mode 100644 index 0000000000..bf0aef235c --- /dev/null +++ b/rust/libs/observability/src/lib.rs @@ -0,0 +1,19 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +pub mod config; +pub mod macros; +pub mod observability; diff --git a/rust/libs/observability/src/macros.rs b/rust/libs/observability/src/macros.rs new file mode 100644 index 0000000000..21b57f5331 --- /dev/null +++ b/rust/libs/observability/src/macros.rs @@ -0,0 +1,150 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#[macro_export] +macro_rules! tracer { + () => {{ + tracer!("vald") + }}; + + ($name:expr) => {{ + opentelemetry::global::tracer($name) + }}; +} + +#[macro_export] +macro_rules! ctx_span { + ($ctx:expr, $name:expr) => {{ + ctx_span!($ctx, $name, opentelemetry::trace::SpanKind::Internal) + }}; + + ($ctx:expr, $name:expr, $kind:expr) => {{ + let tracer = tracer!(); + let parent_ctx: &opentelemetry::Context = $ctx; + let span = tracer + .span_builder($name) + .with_kind($kind) + .start_with_context(&tracer, parent_ctx); + opentelemetry::Context::current_with_span(span) + }}; +} + +#[macro_export] +macro_rules! meter { + () => {{ + meter!("vald") + }}; + + ($name:expr) => {{ + opentelemetry::global::meter($name) + }}; +} + +#[derive(Debug, PartialEq)] +pub enum InstrumentKind { + UpdownCounter, + Counter, + Histogram, + Gauge, +} + +#[macro_export] +macro_rules! instrument { + (InstrumentKind::Counter, $typ:ty, $name:expr, $disc:expr, $unit:expr) => {{ + let meter = meter!(); + paste::paste! { + meter + .[<$typ _counter>]($name) // typ = f64 or u64 + .with_description($disc) + .with_unit($unit) + .init() + } + }}; + + (InstrumentKind::Counter, $typ:ty, $name:expr, $disc:expr, $unit:expr, $measurement:expr, $key_value:expr) => {{ + let meter = meter!(); + paste::paste! { + meter + .[<$typ _observable_counter>]($name) // typ = f64 or u64 + .with_description($disc) + .with_unit($unit) + .with_callback(|observe| { + observe.observe($measurement, $key_value); + }) + .init() + } + }}; + + (InstrumentKind::UpdownCounter, $typ:ty, $name:expr, $disc:expr, $unit:expr) => {{ + let meter = meter!(); + paste::paste! { + meter + .[<$typ _up_down_counter>]($name) // typ = f64 or i64 + .with_description($disc) + .with_unit($unit) + .init() + } + }}; + + (InstrumentKind::UpdownCounter, $typ:ty, $name:expr, $disc:expr, $unit:expr, $measurement:expr, $key_value:expr) => {{ + let meter = meter!(); + paste::paste! { + meter + .[<$typ _observable_up_down_counter>]($name) // typ = f64 or i64 + .with_description($disc) + .with_unit($unit) + .with_callback(|observe| { + observe.observe($measurement, $key_value); + }) + .init() + } + }}; + + (InstrumentKind::Histogram, $typ:ty, $name:expr, $disc:expr, $unit:expr) => {{ + let meter = meter!(); + paste::paste! { + meter + .[<$typ _histogram>]($name) // typ = f64 or i64 + .with_description($disc) + .with_unit($unit) + .init() + } + }}; + + (InstrumentKind::Gauge, $typ:ty, $name:expr, $disc:expr, $unit:expr) => {{ + let meter = meter!(); + paste::paste! { + meter + .[<$typ _gauge>]($name) // typ = f64 or i64 or u64 + .with_description($disc) + .with_unit($unit) + .init() + } + }}; + + (InstrumentKind::Gauge, $typ:ty, $name:expr, $disc:expr, $unit:expr, $measurement:expr, $key_value:expr) => {{ + let meter = meter!(); + paste::paste! { + meter + .[<$typ _observable_gauge>]($name) // typ = f64 or u64 or u64 + .with_description($disc) + .with_unit($unit) + .with_callback(|observe| { + observe.observe($measurement, $key_value); + }) + .init() + } + }}; +} diff --git a/rust/libs/observability/src/observability.rs b/rust/libs/observability/src/observability.rs new file mode 100644 index 0000000000..a80ab6d92a --- /dev/null +++ b/rust/libs/observability/src/observability.rs @@ -0,0 +1,124 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +use anyhow::{Context, Ok, Result}; +use opentelemetry::global::{self, shutdown_tracer_provider}; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::metrics::SdkMeterProvider; +use opentelemetry_sdk::propagation::TraceContextPropagator; +use opentelemetry_sdk::trace::{self, TracerProvider}; +use opentelemetry_sdk::{runtime, Resource}; +use url::Url; + +use crate::config::Config; + +pub const SERVICE_NAME: &str = opentelemetry_semantic_conventions::resource::SERVICE_NAME; + +pub trait Observability { + fn shutdown(&mut self) -> Result<()>; +} + +pub struct ObservabilityImpl { + config: Config, + meter_provider: Option, + tracer_provider: Option, +} + +impl ObservabilityImpl { + pub fn new(cfg: Config) -> Result { + let mut obj = ObservabilityImpl { + config: cfg, + meter_provider: None, + tracer_provider: None, + }; + + if !obj.config.enabled { + return Ok(obj); + } + + if obj.config.meter.enabled { + // NOTE: Since the agent implementation does not use views, we will use the simplest implementation for the current phase. + // If we want flexibility and customization, use SdkMeterProvider::builder. + let provider = opentelemetry_otlp::new_pipeline() + .metrics(runtime::Tokio) + .with_period(obj.config.meter.export_duration) + .with_resource(Resource::from(obj.config())) + .with_timeout(obj.config.meter.export_timeout_duration) + .with_exporter( + opentelemetry_otlp::new_exporter().tonic().with_endpoint( + Url::parse(obj.config.endpoint.as_str())? + .join("/v1/metrics")? + .as_str(), + ), + ) + .build()?; + obj.meter_provider = Some(provider.clone()); + global::set_meter_provider(provider.clone()); + } + + if obj.config.tracer.enabled { + let tracer = opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter( + opentelemetry_otlp::new_exporter().tonic().with_endpoint( + Url::parse(obj.config.endpoint.as_str())? + .join("/v1/traces")? + .as_str(), + ), + ) + .with_trace_config( + trace::config() + .with_sampler(trace::Sampler::AlwaysOn) + .with_resource(Resource::from(obj.config())) + .with_id_generator(trace::RandomIdGenerator::default()), + ) + .install_batch(runtime::Tokio)?; + let provider = tracer.provider().context("failed to get provider")?; + obj.tracer_provider = Some(provider.clone()); + global::set_text_map_propagator(TraceContextPropagator::new()); + global::set_tracer_provider(provider.clone()); + } + Ok(obj) + } + + fn config(&self) -> &Config { + &self.config + } +} + +impl Observability for ObservabilityImpl { + fn shutdown(&mut self) -> Result<()> { + if !self.config.enabled { + return Ok(()); + } + + if self.config.meter.enabled { + if let Some(ref provider) = self.meter_provider { + provider.force_flush()?; + provider.shutdown()?; + } + } + + if self.config.meter.enabled { + if let Some(ref provider) = self.tracer_provider { + for result in provider.force_flush() { + result?; + } + shutdown_tracer_provider(); + } + } + Ok(()) + } +}