Rust OpenTelemetry
В этом пошаговом руководстве показано, как добавить возможность наблюдения в ваше приложение Rust с помощью библиотек и инструментов OpenTelemetry Rust.
| Особенность | Поддержка |
|---|---|
| Автоматические инструменты | Нет |
| Трассировки | Да |
| Метрики | Да |
| Логи | Да |
Предустановка
- Ключ-АСТРОМ версии 1.222+
- Для трассировки включен контекст трассировки W3C.
- Перейдите в Настройки > Предпочтения > Функции ЕдиногоАгента.
- Включите опцию Отправлять HTTP-заголовки контекста трассировки W3C.
Получение данных для доступа к Ключ-АСТРОМ
Определение базового URL API
Подробную информацию о сборке базового URL-адреса конечной точки OTLP см. в разделе Экспорт с помощью OTLP. URL-адрес должен заканчиваться на /api/v2/otlp.
Получение токена доступа API
Токен доступа для сбора трассировок, логов и метрик можно создать в разделе Токены доступа.
Экспорт с помощью OTLP содержит более подробную информацию о формате и необходимых областях доступа.
Настройка OpenTelemetry
1. Добавьте следующее в ваш файл Cargo.toml.
| opentelemetry = { version = "~0", features = ["trace", "metrics"] }
opentelemetry_sdk = { version = "~0", features = ["rt-tokio", "metrics", "logs", "spec_unstable_metrics_views"] } opentelemetry-otlp = { version = "~0", features = ["http-proto", "http-json", "logs", "reqwest-client", "reqwest-rustls"] } opentelemetry-http = { version = "~0" } opentelemetry-appender-log = { version = "~0" } opentelemetry-semantic-conventions = { version = "~0" } |
2. Добавьте в свой код следующие объявления use.
| use std::{env, convert::Infallible, net::SocketAddr, collections::HashMap, io::{BufRead, BufReader, Read}};
use opentelemetry_sdk::trace::SdkTracerProvider; use opentelemetry_sdk::{logs::SdkLoggerProvider, metrics::{PeriodicReader, SdkMeterProvider}, propagation::TraceContextPropagator, Resource}; use opentelemetry_otlp::{LogExporter, MetricExporter, Protocol, SpanExporter, WithExportConfig, WithHttpConfig}; use opentelemetry_semantic_conventions::trace; use opentelemetry_http::{Bytes, HeaderExtractor, HeaderInjector}; use opentelemetry_appender_log::OpenTelemetryLogBridge; use opentelemetry::{global, trace::{FutureExt, Span, SpanKind, TraceContextExt, Tracer}, Context, KeyValue}; |
3. Добавьте следующую функцию в ваш стартовый файл.
| fn init_opentelemetry() {
// Helper function to read potentially available OneAgent data fn read_dt_metadata() -> Vec<KeyValue> { fn read_single(path: &str, metadata: &mut Vec<KeyValue>) -> std::io::Result<()> { let mut file = std::fs::File::open(path)?; if path.starts_with("dt_metadata") { let mut name = String::new(); file.read_to_string(&mut name)?; file = std::fs::File::open(name)?; } for line in BufReader::new(file).lines() { if let Some((k, v)) = line?.split_once('=') { metadata.push(KeyValue::new(k.to_string(), v.to_string())) } } Ok(()) } let mut metadata = Vec::new(); for name in [ "dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", "/var/lib/astromkey/enrichment/dt_metadata.properties", "/var/lib/astromkey/enrichment/dt_host_metadata.properties" ] { let _ = read_single(name, &mut metadata); } return metadata; } // ===== GENERAL SETUP ===== let dt_api_token = env::var("DT_API_TOKEN").unwrap(); // TODO: change let dt_api_url = env::var("DT_API_URL").unwrap();
let mut map = HashMap::new(); map.insert("Authorization".to_string(), format!("Api-Token {}", dt_api_token)); let resource = Resource::builder() .with_service_name("rust-manual-quickstart") .with_attributes(read_dt_metadata()) .build(); use reqwest::blocking::Client; // Workaround for currently not supported reqwest library let client = Client::new(); // ===== TRACING SETUP ===== global::set_text_map_propagator(TraceContextPropagator::new()); let tracer_exporter = SpanExporter::builder() .with_http() .with_http_client(client.clone()) .with_headers(map.clone()) .with_protocol(Protocol::HttpBinary) .with_endpoint(dt_api_url.clone() + "/v1/traces") .build() .unwrap(); let tracer_provider = SdkTracerProvider::builder() .with_resource(resource.clone()) .with_batch_exporter(tracer_exporter) .build(); global::set_tracer_provider(tracer_provider.clone()); // ===== METRICS SETUP ====== let metrics_exporter = MetricExporter::builder() .with_http() .with_http_client(client.clone()) .with_headers(map.clone()) .with_endpoint(dt_api_url.clone() + "/v1/metrics") .with_protocol(opentelemetry_otlp::Protocol::HttpBinary) .build() .unwrap(); let meter_provider = SdkMeterProvider::builder() .with_reader(PeriodicReader::builder(metrics_exporter).build()) .with_resource(resource.clone()) .build(); global::set_meter_provider(meter_provider); // ===== LOGS SETUP ====== let logger_exporter = LogExporter::builder() .with_http() .with_http_client(client.clone()) .with_headers(map.clone()) .with_endpoint(dt_api_url.clone() + "/v1/logs") .with_protocol(opentelemetry_otlp::Protocol::HttpBinary) .build() .unwrap(); let logger_provider = SdkLoggerProvider::builder() .with_batch_exporter(logger_exporter) .with_resource(resource.clone()) .build(); let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); log::set_max_level(Level::Debug.to_level_filter()); } |
Расширение данных Ключ-АСТРОМ
Операции чтения файлов, анализирующие файлы dt_metadata в примере кода, пытаются прочитать файлы данных ЕдиногоАгента, чтобы расширить запрос OTLP и гарантировать, что вся соответствующая информация о топологии доступна в Ключ-АСТРОМ.
4. Убедитесь, что переменные среды DT_API_URL и DT_API_TOKEN правильно настроены для URL-адреса Ключ-АСТРОМ и токена доступа.
5. Вызовите функцию init_opentelemetry() как можно раньше в стартовом коде.
Инструментирование своего приложения
Добавление трассировки
1. Для начала нам нужно получить объект трассировки.
| let tracer = global::tracer("my-tracer"); |
2. С помощью tracer теперь мы можем начинать новые интервалы.
| let mut _span = tracer
.span_builder("Call to /myendpoint") .with_kind(SpanKind::Internal) .start(&tracer); _span.set_attribute(KeyValue::new("http.method", "GET")); _span.set_attribute(KeyValue::new("net.protocol.version", "1.1")); // TODO: Your code goes here _span.end(); |
В приведенном выше коде мы:
- Создали новый диапазон и назвали его «Call to /myendpoint».
- Добавили два атрибута, следуя семантическому соглашению об именовании, специфичные для действия этого диапазона: информацию о методе HTTP и версии.
- Добавили
TODOвместо конечной бизнес-логики - Вызовали метод span
end()для завершения span.
Сбор метрик
1. Для начала нам нужно получить объект метрики.
| let meter = global::meter("request_counter"); |
2. С помощью meter мы теперь можем создавать отдельные инструменты, например, метрику.
| let updown_counter = meter.i64_up_down_counter("request_counter").build(); |
3. Теперь мы можем вызвать метод add() для записи новых значений updown_counter с помощью счетчика.
| updown_counter.add(1,&[],); |
Подключение логов
В init_opentelemetry(), мы ранее инициализировали контейнер логов с его мостом логов OpenTelemetry и теперь можем вызывать любой из его макросов логов для ведения логов непосредственно в Ключ-АСТРОМ.
| error!("logging an error");
debug!("logging a debug message"); |
Обеспечение распространения контекста (необязательно)
Распространение контекста особенно важно, когда задействованы сетевые вызовы (например, REST).
Извлечение контекста при получении запроса
Чтобы продолжить существующую трассировку HTTP-запроса, необходимо сначала извлечь контекст. Для этого мы объявляем функцию extract_context_from_request(), которая принимает объект входящего запроса, извлекает переданный контекст с помощью метода пропагатора extract() и возвращает соответствующий объект контекста.
| // Utility function to extract the context from the incoming request headers
fn extract_context_from_request(req: &Request<Incoming>) -> Context { global::get_text_map_propagator(|propagator| { propagator.extract(&HeaderExtractor(req.headers())) }) } |
Затем мы можем использовать extract_context_from_request() в нашем обработчике запросов для получения этого контекста и передать его как родительский в наш собственный, новый серверный диапазон с помощью start_with_context().
| async fn router(req: Request<Incoming>) -> Result<Response<BoxBody<Bytes, hyper::Error>>, Infallible> {
// Extract the context from the incoming request headers let parent_cx = extract_context_from_request(&req); let response = { // Create a span parenting the remote client span. let tracer = global::tracer("example/server"); let mut span = tracer .span_builder("router") .with_kind(SpanKind::Server) .start_with_context(&tracer, &parent_cx); // Adding custom attributes span.set_attribute(KeyValue::new("my-server-key-1", "my-server-value-1")); }; // TODO Handle the HTTP request } |
Внедрение контекста при отправке запросов
Чтобы передать текущий контекст другому HTTP-сервису, мы добавляем информацию о контексте в заголовки HTTP-запроса. В следующем примере объявляется функция send_request(), которая принимает URL-адрес запроса, его содержимое и отправляет запрос с помощью hyper.
После инициализации объекта гиперзапроса мы вызываем метод get_text_map_propagator() для получения глобального объекта propagator, а затем используем функцию inject_context() для добавления текущей контекстной информации к запросу.
| async fn send_request(url: &str, body_content: &str, span_name: &str) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let client = Client::builder(TokioExecutor::new()).build_http(); let tracer = global::tracer("example/client"); let span = tracer .span_builder(String::from(span_name)) .with_kind(SpanKind::Client) .start(&tracer); let cx = Context::current_with_span(span); let mut req = hyper::Request::builder().uri(url); global::get_text_map_propagator(|propagator| { propagator.inject_context(&cx, &mut HeaderInjector(req.headers_mut().unwrap())) }); let res = client .request(req.body(Full::new(Bytes::from(body_content.to_string())))?) .await?; cx.span().add_event( "Got response!", vec![KeyValue::new("status", res.status().to_string())], ); Ok(()) } |
Настройте сбор данных в соответствии с требованиями конфиденциальности (необязательно)
Хотя Ключ-АСТРОМ автоматически собирает все атрибуты OpenTelemetry, в веб-интерфейсе Ключ-АСТРОМ сохраняются и отображаются только значения атрибутов, указанные в списке разрешенных. Это предотвращает случайное сохранение персональных данных, позволяя вам соблюдать требования к конфиденциальности и контролировать объем хранимых данных мониторинга.
Чтобы просматривать пользовательские атрибуты, необходимо сначала разрешить их использование в веб-интерфейсе Ключ-АСТРОМ.
Проверка загрузки данных в Ключ-АСТРОМ
После завершения инструментирования вашего приложения выполните несколько тестовых действий для создания и отправки демонстрационных трассировок, метрик и логов, а также проверьте, что они были правильно загружены в Ключ-АСТРОМ.
Чтобы сделать это для трассировок, перейдите в раздел Трассировки и выберите вкладку Распределенные трассировки. Если вы используете ЕдиныйАгент, выберите PurePaths .
Для просмотра метрик и логов перейдите в раздел Метрики или Логов или Логи и события.