From 6bd56aad75aa8e96e85a9e6d36b28010265cb2be Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 4 Aug 2020 22:26:30 -0400 Subject: [PATCH] add newrelic logging capability --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 1 + src/main.rs | 34 ++++++++++++++++++++++++++++------ src/newrelic_logger.rs | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 src/newrelic_logger.rs diff --git a/Cargo.lock b/Cargo.lock index 5e1f55a..aeefe27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -420,6 +420,22 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "newrelic" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ec1eb312ccd33c2d88ff317d59164465a41014ad465ba8a67e2c062343e3a8" +dependencies = [ + "log", + "newrelic-sys", +] + +[[package]] +name = "newrelic-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9b9030b5f433ac2afb956eeaf89020e07d3cb9b0561cd07f1737c97a4af93a" + [[package]] name = "num_cpus" version = "1.13.0" @@ -835,6 +851,7 @@ dependencies = [ "hyper", "hyper-reverse-proxy", "log", + "newrelic", "rand", "serde", "serde_derive", diff --git a/Cargo.toml b/Cargo.toml index 07267c4..977a05f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ futures = "0.1" hyper = "0.12" hyper-reverse-proxy = "0.4" log = "0.4.8" +newrelic = "0.2.2" rand = "0.7.3" serde = "1.0.104" serde_derive = "1.0.104" diff --git a/src/main.rs b/src/main.rs index b731b52..de6faf2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ #[macro_use] extern crate log; #[macro_use] extern crate serde_derive; +pub mod newrelic_logger; + use anyhow::Result as AnyhowResult; use futures::future::{self, Future}; use hyper::Method; @@ -19,18 +21,22 @@ use std::sync::Arc; use rand::seq::IteratorRandom; use hyper::http::header::HeaderName; use hyper::header::HeaderValue; +use newrelic::App; +use crate::newrelic_logger::NewRelicLogger; type BoxFut = Box, Error=hyper::Error> + Send>; const ENV_DEFAULT_ROUTE : &'static str = "DEFAULT_ROUTE"; +const ENV_NEWRELIC_API_KEY : &'static str = "NEWRELIC_API_KEY"; const ENV_PROXY_CONFIG_FILE : &'static str = "PROXY_CONFIG_FILE"; const ENV_RUST_LOG : &'static str = "RUST_LOG"; const ENV_SERVICE_PORT : &'static str = "SERVICE_PORT"; const DEFAULT_DEFAULT_ROUTE : &'static str = "http://127.0.0.1:12345"; +const DEFAULT_NEWRELIC_API_KEY : &'static str = ""; const DEFAULT_PROXY_CONFIG_FILE : &'static str = "proxy_configs.toml"; -const DEFAULT_RUST_LOG: &'static str = "tokio_reactor=warn,hyper=info,debug"; -const DEFAULT_SERVICE_PORT: u16 = 5555; +const DEFAULT_RUST_LOG : &'static str = "tokio_reactor=warn,hyper=info,debug"; +const DEFAULT_SERVICE_PORT : u16 = 5555; #[derive(Deserialize, Debug, Clone)] struct ProxyConfigs { @@ -198,6 +204,7 @@ fn main() -> AnyhowResult<()> { let default_route = get_env_string(ENV_DEFAULT_ROUTE, DEFAULT_DEFAULT_ROUTE); let service_port = get_env_num::(ENV_SERVICE_PORT, DEFAULT_SERVICE_PORT)?; + let newrelic_api_key = get_env_string(ENV_NEWRELIC_API_KEY, DEFAULT_NEWRELIC_API_KEY); info!("Default route: {}", default_route); @@ -219,23 +226,38 @@ fn main() -> AnyhowResult<()> { default_route: default_route, }); + let newrelic_logger = if newrelic_api_key.is_empty() { + NewRelicLogger::null_instance() + } else { + NewRelicLogger::try_new_or_null("voder-proxy", &newrelic_api_key) + }; + + let newrelic_logger = Arc::new(newrelic_logger); + let make_service = make_service_fn(move |socket: &AddrStream| { let remote_addr = socket.remote_addr(); info!("Got a request from: {:?}", remote_addr); let router2 = router.clone(); + let newrelic_logger2 = newrelic_logger.clone(); service_fn(move |req: Request| { let router3 = router2.clone(); + let newrelic_logger3 = newrelic_logger2.clone(); match (req.method(), req.uri().path()) { - (&Method::POST, "/speak") => - speak_proxy(req, remote_addr.clone(), router3, "/speak"), - (&Method::POST, "/speak_spectrogram") => - speak_proxy(req, remote_addr.clone(), router3, "/speak_spectrogram"), + (&Method::POST, "/speak") => { + let _droppable_transaction = newrelic_logger3.web_transaction("proxy /speak"); + speak_proxy(req, remote_addr.clone(), router3, "/speak") + }, + (&Method::POST, "/speak_spectrogram") => { + let _droppable_transaction = newrelic_logger3.web_transaction("proxy /speak_spectrogram"); + speak_proxy(req, remote_addr.clone(), router3, "/speak_spectrogram") + }, (&Method::GET, "/proxy_health") => health_check_response(req), _ => { + let _droppable_transaction = newrelic_logger3.web_transaction("unmatched endpoint"); let forward = router3.get_random_host(); info!("Forwarding to `{}` random host: {}", &forward, req.uri()); hyper_reverse_proxy::call(remote_addr.ip(), &forward, req) diff --git a/src/newrelic_logger.rs b/src/newrelic_logger.rs new file mode 100644 index 0000000..ea6b74b --- /dev/null +++ b/src/newrelic_logger.rs @@ -0,0 +1,36 @@ +use anyhow::Result as AnyhowResult; +use newrelic::{App, Transaction}; + +pub struct NewRelicLogger { + newrelic_api: Option, +} + +impl NewRelicLogger { + pub fn new(app_name: &str, api_key: &str) -> AnyhowResult { + let newrelic_api = App::new(app_name, api_key)?; + Ok(Self { + newrelic_api: Some(newrelic_api), + }) + } + + pub fn null_instance() -> Self { + Self { + newrelic_api: None, + } + } + + pub fn try_new_or_null(app_name: &str, api_key: &str) -> Self { + match Self::new(app_name, api_key) { + Ok(instance) => instance, + Err(_) => Self::null_instance(), + } + } + + pub fn web_transaction(&self, transaction_name: &str) -> Option { + if let Some(ref newrelic_api) = self.newrelic_api { + info!("Logging request to NewRelic"); + return newrelic_api.web_transaction(transaction_name).ok(); + } + None + } +}