A Rocket fairing instrumenting requests using New Relic.
Attach the fairing to your Rocket
app, and any requests that include
a [Transaction
] in their request guard will be instrumented using
the handler base path and name as the transaction name.
Important - this fairing still requires the New Relic daemon to be run alongside your app in some way, and the underlying newrelic and newrelic-sys crates have some additional build requirements. Make sure these are met when trying to use this crate.
Crucially the libnewrelic
C SDK requires a few functions not provided by musl
(at least qsort_r
and backtrace
), so this won't (currently) build against
musl.
Add the crate to your Cargo.toml:
[dependencies]
rocket_newrelic = { git = "https://github.com/sd2k/rocket_newrelic" }
Then add a &Transaction
request guard to any handlers you
wish to instrument:
use rocket_newrelic::Transaction;
#[get("/user/me")]
pub fn get_me(_transaction: &Transaction) -> &'static str {
"It's me!"
}
Finally, attach the fairing to your Rocket
app:
use rocket_newrelic::NewRelic;
fn main() -> {
let newrelic = NewRelic::new("MY_APP_NAME", "MY_LICENSE_KEY")
.expect("Could not register with New Relic");
rocket::ignite()
.manage(newrelic)
.mount("/root", routes![get_me])
.launch();
}
In the above example we'd then be able to see these transactions under
/root/get_me
.
The [Transaction
] object used in the request guard provides a few methods
to allow further instrumentation, such as custom attributes and transaction
segments. The below example demonstrates some of this functionality; see the
methods of Transaction
for more details.
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
use newrelic::{Datastore, ExternalParamsBuilder};
use rocket_contrib::json::Json;
use rocket_newrelic::{NewRelic, Transaction};
use serde_json::Value;
struct User;
// This would normally connect to a database and perhaps return some data.
fn insert_into_db(_: &Json<Value>) -> Result<User, ()> {
Ok(User {})
}
#[post("/users", data = "<user>")]
fn create_user(transaction: &Transaction, user: Json<Value>) {
// Add attributes to a transaction
if let Some(Value::String(name)) = user.get("name") {
transaction.add_attribute("user name", name);
}
if let Some(age) = user.get("age").and_then(|a| a.as_i64()) {
transaction.add_attribute("user age", age);
}
// Executing a query in a datastore segment
let query = "INSERT INTO users VALUES (%s, %s);";
match transaction.datastore_segment(Datastore::Postgres, "users", "insert", query, |_| {
insert_into_db(&user)
}) {
Ok(_) => println!("Created user"),
Err(_) => println!("Could not create user"),
}
// Doing expensive operations in a custom segment
let _expensive_value: Result<reqwest::blocking::Response, reqwest::Error> =
transaction.custom_segment("process user", "process", |s| {
// Nesting an external segment within the custom segment
let url = "https://logging-thing";
let external_params = ExternalParamsBuilder::new(url)
.procedure("set")
.library("reqwest")
.build()
.unwrap();
s.external_nested(&external_params, |_| {
reqwest::blocking::Client::new().post(url).send()
})
});
}
fn main() {
let newrelic = NewRelic::from_env();
rocket::ignite()
.manage(newrelic)
.mount("/", routes![create_user])
.launch();
}
With the diesel
feature enabled it's possible to pass a Diesel query,
along with a &Connection
, into the diesel_segment_load
and
diesel_segment_first
methods of a [Transaction
]. This will log the SQL query
and return either all results, or the first result, respectively.