diff --git a/.gitignore b/.gitignore index 6985cf1..e43b0f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb +.DS_Store diff --git a/grpc_example/.gitignore b/grpc_example/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/grpc_example/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/grpc_example/Cargo.toml b/grpc_example/Cargo.toml new file mode 100644 index 0000000..8e09194 --- /dev/null +++ b/grpc_example/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "grpc_test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tonic = "0.11.0" +tonic-health = "0.11.0" +prost = "0.12.4" +opentelemetry = { version = "0.22", features = ["metrics"] } +opentelemetry-semantic-conventions = "0.14.0" +opentelemetry-otlp = { version = "0.15.0", features = [ + "tonic", + "metrics", + "logs", +] } +opentelemetry-appender-log = { version = "0.3.0", default-features = false } +tracing = { version = "0.1.40", features = [ + "max_level_debug", + "release_max_level_info", +] } +tracing-opentelemetry = "0.23.0" +tracing-subscriber = "0.3.18" +serde_json = "1.0.116" +config = "0.14.0" +env_logger = "0.11.3" + +opentelemetry_sdk = { version = "0.22", features = [ + "rt-tokio", + "logs", + "logs_level_enabled", +] } +tokio = { version = "1.37.0", features = ["macros"] } +#opentelemetry-stdout = { version = "0.3.0", features = ["logs"] } +log = "0.4.21" +function_name = "0.3.0" + +[build-dependencies] +tonic-build = "0.11.0" + +[[bin]] # Bin to run the Tenant gRPC server +name = "main_grpc_server" +path = "src/main_grpc_server.rs" diff --git a/grpc_example/README.md b/grpc_example/README.md new file mode 100644 index 0000000..a24405e --- /dev/null +++ b/grpc_example/README.md @@ -0,0 +1,55 @@ +# Rust Grpc Example + +This project is an example of using gRPC in Rust. +It demonstrates how to implement a gRPC server and client in Rust, and provides a simple example of a service and its corresponding client. + + +## Table of Contents + +- [Technical stack](#technical-stack) +- [Installation](#installation) +- [Usage](#usage) +- [License](#license) + +## Technical stack + +- [Rust](https://www.rust-lang.org/fr) +- [GRPC](https://grpc.io/) +- [Opentelemetry](https://opentelemetry.io/) +- [Opentelemetry Rust Github](https://github.com/open-telemetry/opentelemetry-rust) + +## Installation + +You need to have docker installed on your host machine + +## Usage + +Run telemetry stack with this docker command: + +`docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm` + +Run application with log level info: + +`OTEL_SERVICE_NAME=rust_tenant_service cargo run` + +After application started, you can call the application GRPC service named `TenantGrpc` with postman with url: [localhost:50501](localhost:50501) +You need to specify [this proto file](proto/tenant.proto) + +![Call GRpc](docs/images/postman_call_grpc.png) + +You can visualize telemetry with prometheuse stack at [localhost:3000](localhost:3000) + +Grafana span: + +![Grafana span list example](docs/images/grafana_span_list.png) + +![Grafana span trace example](docs/images/grafana_span_trace.png) + +Grafana logs: + +![Grafana logs example](docs/images/grafana_logs.png) + + +## License + +MIT License. diff --git a/grpc_example/application.yml b/grpc_example/application.yml new file mode 100644 index 0000000..9a9569f --- /dev/null +++ b/grpc_example/application.yml @@ -0,0 +1,2 @@ +otel: + endpoint: "http://localhost:4317" diff --git a/grpc_example/build.rs b/grpc_example/build.rs new file mode 100644 index 0000000..b9473e3 --- /dev/null +++ b/grpc_example/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/tenant.proto")?; + Ok(()) +} diff --git a/grpc_example/docker-compose.yaml b/grpc_example/docker-compose.yaml new file mode 100644 index 0000000..2550637 --- /dev/null +++ b/grpc_example/docker-compose.yaml @@ -0,0 +1,57 @@ +version: '3.8' + +services: + + prometheus: + image: prom/prometheus + container_name: prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + ports: + - 9090:9090 + restart: unless-stopped + volumes: + - ./config/prometheus:/etc/prometheus + + grafana: + image: grafana/grafana + container_name: grafana + ports: + - 3000:3000 + restart: unless-stopped + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=grafana + volumes: + - ./config/grafana:/etc/grafana/provisioning/datasources + + # Jaeger + jaeger-all-in-one: + image: jaegertracing/all-in-one:latest + restart: always + ports: + - "16686:16686" + + # Zipkin + zipkin: + image: openzipkin/zipkin:latest + environment: + - JAVA_OPTS=-Xms1024m -Xmx1024m -XX:+ExitOnOutOfMemoryError + restart: always + ports: + - "9411:9411" + + otel-collector: + image: otel/opentelemetry-collector-contrib + restart: always + command: [ "--config=/etc/otel-collector-config.yaml" ] + volumes: + - ./config/otel-collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml + ports: + - "1888:1888" # pprof extension + - "13133:13133" # health_check extension + - "4317:4317" # OTLP gRPC receiver + - "55679:55679" # zpages extension + depends_on: + - jaeger-all-in-one + - zipkin diff --git a/grpc_example/docs/images/grafana_logs.png b/grpc_example/docs/images/grafana_logs.png new file mode 100644 index 0000000..1d590ca Binary files /dev/null and b/grpc_example/docs/images/grafana_logs.png differ diff --git a/grpc_example/docs/images/grafana_span_list.png b/grpc_example/docs/images/grafana_span_list.png new file mode 100644 index 0000000..64a6869 Binary files /dev/null and b/grpc_example/docs/images/grafana_span_list.png differ diff --git a/grpc_example/docs/images/grafana_span_trace.png b/grpc_example/docs/images/grafana_span_trace.png new file mode 100644 index 0000000..d318bec Binary files /dev/null and b/grpc_example/docs/images/grafana_span_trace.png differ diff --git a/grpc_example/docs/images/postman_call_grpc.png b/grpc_example/docs/images/postman_call_grpc.png new file mode 100644 index 0000000..c02f9b0 Binary files /dev/null and b/grpc_example/docs/images/postman_call_grpc.png differ diff --git a/grpc_example/proto/tenant.proto b/grpc_example/proto/tenant.proto new file mode 100644 index 0000000..be2ab8d --- /dev/null +++ b/grpc_example/proto/tenant.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; +package tenant_grpc; + +// Request +message GetTenantRequest { + string id = 1; +} + +message GetTenantListRequest { +} + +message CreateTenantRequest { + string name = 1; +} + +message UpdateTenantRequest { + string id = 1; + string name = 2; +} + +message DeleteTenantRequest { + string id = 1; +} + +// Response +message TenantResponse { + string id = 1; + string name = 2; +} + +message TenantListResponse { + repeated TenantResponse tenants = 1; +} + +message DeleteTenantResponse { +} + +service TenantGrpc { + rpc GetTenant (GetTenantRequest) returns (TenantResponse); + rpc GetTenantList (GetTenantListRequest) returns (TenantListResponse); + rpc CreateTenant (CreateTenantRequest) returns (TenantResponse); + rpc UpdateTenant (UpdateTenantRequest) returns (TenantResponse); + rpc DeleteTenant (DeleteTenantRequest) returns (DeleteTenantResponse); +} \ No newline at end of file diff --git a/grpc_example/src/domain/mod.rs b/grpc_example/src/domain/mod.rs new file mode 100644 index 0000000..7a5e413 --- /dev/null +++ b/grpc_example/src/domain/mod.rs @@ -0,0 +1,14 @@ +pub mod tenant_service; + +pub struct TenantBO { + pub id: String, + pub name: String, +} + +pub trait ITenantService: Send + Sync + 'static { + fn get_tenant(&self) -> TenantBO; + fn get_tenant_list(&self) -> Vec; + fn create_tenant(&self) -> TenantBO; + fn update_tenant(&self) -> TenantBO; + fn delete_tenant(&self); +} diff --git a/grpc_example/src/domain/tenant_service.rs b/grpc_example/src/domain/tenant_service.rs new file mode 100644 index 0000000..ae3be3b --- /dev/null +++ b/grpc_example/src/domain/tenant_service.rs @@ -0,0 +1,43 @@ +use crate::domain::{ITenantService, TenantBO}; + +pub struct TenantService {} + +impl ITenantService for TenantService { + fn get_tenant(&self) -> TenantBO { + let tenant = TenantBO { + id: String::from("1"), + name: String::from("name 1"), + }; + tenant + } + + fn get_tenant_list(&self) -> Vec { + let mut tenant_list = Vec::new(); + for i in 0..=10 { + let tenant = TenantBO { + id: i.to_string(), + name: format!("name {}", i), + }; + tenant_list.push(tenant); + } + tenant_list + } + + fn create_tenant(&self) -> TenantBO { + let tenant = TenantBO { + id: String::from("1"), + name: String::from("name 1"), + }; + tenant + } + + fn update_tenant(&self) -> TenantBO { + let tenant = TenantBO { + id: String::from("1"), + name: String::from("name 1"), + }; + tenant + } + + fn delete_tenant(&self) {} +} diff --git a/grpc_example/src/grpc/mod.rs b/grpc_example/src/grpc/mod.rs new file mode 100644 index 0000000..d2550ed --- /dev/null +++ b/grpc_example/src/grpc/mod.rs @@ -0,0 +1,30 @@ +use std::net::SocketAddr; + +use self::{ + models::tenant_grpc::tenant_grpc_server::TenantGrpcServer, + tenant_grpc_service::TenantGrpcService, +}; + +mod models; +pub mod tenant_grpc_service; +pub mod tenant_mapper; + +#[derive(Debug)] +pub struct OtlpConfig { + pub endpoint: String, +} + +#[allow(dead_code)] +pub async fn start_grpc_server(addr: SocketAddr) -> Result<(), Box> { + let (mut health_reporter, health_service) = tonic_health::server::health_reporter(); + health_reporter + .set_serving::>() + .await; + + tonic::transport::Server::builder() + .add_service(tenant_grpc_service::create_grpc_service()) + .add_service(health_service) + .serve(addr) + .await?; + Ok(()) +} diff --git a/grpc_example/src/grpc/models.rs b/grpc_example/src/grpc/models.rs new file mode 100644 index 0000000..da42a1a --- /dev/null +++ b/grpc_example/src/grpc/models.rs @@ -0,0 +1,4 @@ +// Import the generated proto-rust file into a module +pub(crate) mod tenant_grpc { + tonic::include_proto!("tenant_grpc"); +} diff --git a/grpc_example/src/grpc/tenant_grpc_service.rs b/grpc_example/src/grpc/tenant_grpc_service.rs new file mode 100644 index 0000000..99cd2e6 --- /dev/null +++ b/grpc_example/src/grpc/tenant_grpc_service.rs @@ -0,0 +1,155 @@ +use crate::domain::{self}; +use crate::grpc::tenant_mapper; +use function_name::named; +use log::debug; +use opentelemetry::{ + global, + propagation::Extractor, + trace::{Span, SpanKind, Tracer}, +}; +use std::sync::Arc; + +use super::models::tenant_grpc::tenant_grpc_server::TenantGrpc; +use super::models::tenant_grpc::tenant_grpc_server::TenantGrpcServer; +use super::models::tenant_grpc::{ + CreateTenantRequest, DeleteTenantRequest, DeleteTenantResponse, GetTenantListRequest, + GetTenantRequest, TenantListResponse, TenantResponse, UpdateTenantRequest, +}; +use tonic::{Request, Response, Status}; + +pub fn create_grpc_service() -> TenantGrpcServer { + let domain_tenant_service = domain::tenant_service::TenantService {}; + let my_tenant_service = TenantGrpcService { + tenant_service: Arc::new(domain_tenant_service), + }; + return TenantGrpcServer::new(my_tenant_service); +} +// Implement the service skeleton for the "Tenant" service +// defined in the proto +pub struct TenantGrpcService { + tenant_service: Arc, +} + +fn span_creator(name: String, req: &Request) -> global::BoxedSpan +where + T: std::fmt::Debug, +{ + let parent_cx = + global::get_text_map_propagator(|prop| prop.extract(&MetadataMap(req.metadata()))); + let tracer = global::tracer("rust/example"); + let mut span = tracer + .span_builder(name) + .with_kind(SpanKind::Server) + .start_with_context(&tracer, &parent_cx); + + span.add_event(format!("Received Request: {:?}", req.get_ref()), vec![]); + + span +} + +// Implement the service function(s) defined in the proto +// for the Tenant service +#[tonic::async_trait] +impl TenantGrpc for TenantGrpcService { + #[named] + async fn get_tenant( + &self, + request: Request, + ) -> Result, Status> { + debug!("call getTenant ..."); + + // Create a span for the request (operntelemetry span) + let mut span = span_creator(function_name!().to_string(), &request); + span.add_event("test event", vec![]); + + let tenant = self.tenant_service.get_tenant(); + let response = tenant_mapper::tenant_bo_to_tenant_response(&tenant); + + Ok(Response::new(response)) + } + + #[named] + async fn get_tenant_list( + &self, + request: Request, + ) -> Result, Status> { + debug!("call getTenantList ..."); + + // Create a span for the request (operntelemetry span) + let _ = span_creator(function_name!().to_string(), &request); + + let tenant_list = self.tenant_service.get_tenant_list(); + let response = TenantListResponse { + tenants: tenant_list + .iter() + .map(|tenant| tenant_mapper::tenant_bo_to_tenant_response(tenant)) + .collect(), + }; + + Ok(Response::new(response)) + } + + #[named] + async fn create_tenant( + &self, + request: Request, + ) -> Result, Status> { + debug!("call createTenant ..."); + + // Create a span for the request (operntelemetry span) + let _ = span_creator(function_name!().to_string(), &request); + + let tenant = self.tenant_service.create_tenant(); + let response = tenant_mapper::tenant_bo_to_tenant_response(&tenant); + Ok(Response::new(response)) + } + + #[named] + async fn update_tenant( + &self, + request: Request, + ) -> Result, Status> { + debug!("call updateTenant ..."); + + // Create a span for the request (operntelemetry span) + let _ = span_creator(function_name!().to_string(), &request); + + let tenant = self.tenant_service.update_tenant(); + let response = tenant_mapper::tenant_bo_to_tenant_response(&tenant); + Ok(Response::new(response)) + } + + #[named] + async fn delete_tenant( + &self, + request: Request, + ) -> Result, Status> { + debug!("call deleteTenant ..."); + + // Create a span for the request (operntelemetry span) + let _ = span_creator(function_name!().to_string(), &request); + + self.tenant_service.delete_tenant(); + let response = DeleteTenantResponse {}; + Ok(Response::new(response)) + } +} +struct MetadataMap<'a>(&'a tonic::metadata::MetadataMap); + +impl<'a> Extractor for MetadataMap<'a> { + /// Get a value for a key from the MetadataMap. If the value can't be converted to &str, returns None + fn get(&self, key: &str) -> Option<&str> { + self.0.get(key).and_then(|metadata| metadata.to_str().ok()) + } + + /// Collect all the keys from the MetadataMap. + fn keys(&self) -> Vec<&str> { + self.0 + .keys() + .map(|key| match key { + tonic::metadata::KeyRef::Ascii(v) => v.as_str(), + tonic::metadata::KeyRef::Binary(v) => v.as_str(), + }) + .collect::>() + } +} diff --git a/grpc_example/src/grpc/tenant_mapper.rs b/grpc_example/src/grpc/tenant_mapper.rs new file mode 100644 index 0000000..aff772f --- /dev/null +++ b/grpc_example/src/grpc/tenant_mapper.rs @@ -0,0 +1,10 @@ +use super::models::tenant_grpc::TenantResponse; +use crate::domain::TenantBO; + +pub fn tenant_bo_to_tenant_response(tenant: &TenantBO) -> TenantResponse { + let response = TenantResponse { + id: tenant.id.clone(), + name: tenant.name.clone(), + }; + response +} diff --git a/grpc_example/src/main_grpc_server.rs b/grpc_example/src/main_grpc_server.rs new file mode 100644 index 0000000..40e897f --- /dev/null +++ b/grpc_example/src/main_grpc_server.rs @@ -0,0 +1,69 @@ +use std::path::Path; + +use config::{Config, File}; +use log::*; +use opentelemetry::global::logger_provider; +use opentelemetry_appender_log::OpenTelemetryLogBridge; + +use crate::telemetry::{ + logs_conf::init_logs, + metrics_conf::init_metrics, + traces_conf::{/*init_reqwest_tracing, */ init_reqwest_tracing, init_tracer}, +}; + +mod domain; +mod grpc; +mod telemetry; + +// Runtime to run our server +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box> { + //env_logger::init(); + + info!("Read configuration ..."); + + let settings: Config = Config::builder() + .add_source(File::from(Path::new("./application.yml"))) + .build() + .unwrap(); + + let otlp_config = grpc::OtlpConfig { + endpoint: settings + .get_string("otel.endpoint") + .unwrap_or("http://localhost:4317".to_string()), + }; + + info!( + "OTel pipeline creating [Endpoint:{}]...", + otlp_config.endpoint + ); + + // By binding the result to an unused variable, the lifetime of the variable + // matches the containing block, reporting traces and metrics during the whole + // execution. + let _ = init_reqwest_tracing(init_tracer(&otlp_config)?)?; + let meter_provider = init_metrics(&otlp_config)?; + // Initialize logs, which sets the global loggerprovider. + let _ = init_logs(&otlp_config); + + // Retrieve the global LoggerProvider. + let logger_provider = logger_provider(); + + // Create a new OpenTelemetryLogBridge using the above LoggerProvider. + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(Level::Info.to_level_filter()); + + info!("OTel pipeline created"); + + let addr = "[::1]:50051".parse()?; + + info!("listening on {}", addr); + grpc::start_grpc_server(addr).await?; + + opentelemetry::global::shutdown_tracer_provider(); + opentelemetry::global::shutdown_logger_provider(); + meter_provider.shutdown()?; + + Ok(()) +} diff --git a/grpc_example/src/telemetry/logs_conf.rs b/grpc_example/src/telemetry/logs_conf.rs new file mode 100644 index 0000000..e4bb602 --- /dev/null +++ b/grpc_example/src/telemetry/logs_conf.rs @@ -0,0 +1,20 @@ +use crate::grpc::OtlpConfig; +use opentelemetry::logs::LogError; +use opentelemetry_otlp::{Protocol, WithExportConfig}; +use opentelemetry_sdk::logs::Config; +use opentelemetry_sdk::runtime; + +use super::traces_conf::get_resource_attr; + +pub fn init_logs(otlp_config: &OtlpConfig) -> Result { + opentelemetry_otlp::new_pipeline() + .logging() + .with_log_config(Config::default().with_resource(get_resource_attr())) + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint(otlp_config.endpoint.clone()) + .with_protocol(Protocol::Grpc), + ) + .install_batch(runtime::Tokio) +} diff --git a/grpc_example/src/telemetry/metrics_conf.rs b/grpc_example/src/telemetry/metrics_conf.rs new file mode 100644 index 0000000..5dbce6e --- /dev/null +++ b/grpc_example/src/telemetry/metrics_conf.rs @@ -0,0 +1,25 @@ +use opentelemetry::metrics::{self}; +use opentelemetry_otlp::{ExportConfig, WithExportConfig}; +use opentelemetry_sdk::{metrics::SdkMeterProvider, runtime}; + +use crate::grpc::OtlpConfig; + +use super::traces_conf::get_resource_attr; + +pub fn init_metrics(otlp_config: &OtlpConfig) -> metrics::Result { + let export_config = ExportConfig { + endpoint: otlp_config.endpoint.clone(), + ..ExportConfig::default() + }; + let provider = opentelemetry_otlp::new_pipeline() + .metrics(runtime::Tokio) + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_export_config(export_config), + ) + .with_resource(get_resource_attr()) + .build(); + + provider +} diff --git a/grpc_example/src/telemetry/mod.rs b/grpc_example/src/telemetry/mod.rs new file mode 100644 index 0000000..5c937dc --- /dev/null +++ b/grpc_example/src/telemetry/mod.rs @@ -0,0 +1,4 @@ +pub mod logs_conf; +pub mod metrics_conf; +pub mod resources_conf; +pub mod traces_conf; diff --git a/grpc_example/src/telemetry/resources_conf.rs b/grpc_example/src/telemetry/resources_conf.rs new file mode 100644 index 0000000..16036c6 --- /dev/null +++ b/grpc_example/src/telemetry/resources_conf.rs @@ -0,0 +1,22 @@ +use opentelemetry_sdk::{ + resource::{ + EnvResourceDetector, OsResourceDetector, ProcessResourceDetector, ResourceDetector, + SdkProvidedResourceDetector, TelemetryResourceDetector, + }, + Resource, +}; +use std::time::Duration; + +pub fn get_resource_attr() -> Resource { + let os_resource = OsResourceDetector.detect(Duration::from_secs(0)); + let process_resource = ProcessResourceDetector.detect(Duration::from_secs(0)); + let sdk_resource = SdkProvidedResourceDetector.detect(Duration::from_secs(0)); + let env_resource = EnvResourceDetector::new().detect(Duration::from_secs(0)); + let telemetry_resource = TelemetryResourceDetector.detect(Duration::from_secs(0)); + + os_resource + .merge(&process_resource) + .merge(&sdk_resource) + .merge(&env_resource) + .merge(&telemetry_resource) +} diff --git a/grpc_example/src/telemetry/traces_conf.rs b/grpc_example/src/telemetry/traces_conf.rs new file mode 100644 index 0000000..26604d6 --- /dev/null +++ b/grpc_example/src/telemetry/traces_conf.rs @@ -0,0 +1,34 @@ +use opentelemetry::{global, trace::TraceError}; +use opentelemetry_otlp::{self, WithExportConfig}; +use opentelemetry_sdk::{ + propagation::TraceContextPropagator, + runtime::{self}, + trace::{self as sdktrace}, +}; +use tracing_subscriber::{layer::SubscriberExt, Registry}; + +use crate::grpc::OtlpConfig; + +pub use super::resources_conf::get_resource_attr; + +pub fn init_tracer(otlp_config: &OtlpConfig) -> Result { + global::set_text_map_propagator(TraceContextPropagator::new()); + + opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint(&otlp_config.endpoint), + ) + .with_trace_config(sdktrace::config().with_resource(get_resource_attr())) + .install_batch(runtime::Tokio) +} + +pub fn init_reqwest_tracing( + tracer: sdktrace::Tracer, +) -> Result<(), tracing::subscriber::SetGlobalDefaultError> { + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + let subscriber = Registry::default().with(telemetry); + tracing::subscriber::set_global_default(subscriber) +} diff --git a/rust_example.code-workspace b/rust_example.code-workspace new file mode 100644 index 0000000..2cf1cd4 --- /dev/null +++ b/rust_example.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "grpc_example" + } + ] +} \ No newline at end of file