Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tracing support for GraphQL objects, interfaces and subscriptions #972

Draft
wants to merge 91 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
e0f7eb5
Add tracing support
mihai-dinculescu Jul 25, 2020
419520d
Merge branch 'master' into add_tracing_support
LegNeato Jul 28, 2020
c1f9e5f
Merge branch 'master' into add_tracing_support
LegNeato Jul 29, 2020
753c80f
Rocket can now compile on stable
LegNeato Jul 28, 2020
c599bae
Remove `boxed` in favor of `pin`.
LegNeato Jul 29, 2020
b556934
Add tracing support example
LegNeato Jul 29, 2020
0134da3
Add some coarse execution tracing
LegNeato Jul 29, 2020
5c888a8
Remove old comment / docs
LegNeato Jul 29, 2020
ef2da9b
Merge branch 'master' into add_tracing_support
LegNeato Jul 29, 2020
01b3453
Fix book tests
LegNeato Jul 29, 2020
7ac7304
fix up some imports
LegNeato Jul 29, 2020
06687dd
Add back subscriber Cargo.toml instructions
LegNeato Jul 29, 2020
697a743
Change trace errors to trace
mihai-dinculescu Jul 29, 2020
f2977d5
Tracing unit tests
mihai-dinculescu Jul 29, 2020
6c3654c
Tracing unit tests names
mihai-dinculescu Jul 29, 2020
7f746de
Revert "Rocket can now compile on stable"
LegNeato Jul 29, 2020
64cad08
Merge branch 'master' into add_tracing_support
LegNeato Jul 30, 2020
d7acba9
Merge branch 'master' into add_tracing_support
LegNeato Aug 10, 2020
88dc9c4
Fix tracing spans
LegNeato Aug 14, 2020
8a2f6ec
Do not include trailing newline on the last error
LegNeato Aug 14, 2020
83fd44d
Standard tracing labels on snake_case
LegNeato Aug 14, 2020
c92dd87
Add a validation error case to the tracing example
LegNeato Aug 14, 2020
64d8e11
Use $crate everywhere
LegNeato Aug 15, 2020
cdbc0ce
Add other levels to span macros
LegNeato Aug 15, 2020
65ab688
Merge branch 'master' into add_tracing_support
LegNeato Aug 19, 2020
6cf48d9
Merge branch 'master' into add_tracing_support
LegNeato Aug 21, 2020
5f8a4ca
Merge branch 'master' into add_tracing_support
LegNeato Sep 3, 2020
ae1054e
Merge branch 'master' into add_tracing_support
LegNeato Oct 3, 2020
5938345
Use the released tracing crate
mihai-dinculescu Oct 3, 2020
8bc07f6
Fix email address
LegNeato Oct 29, 2020
82497d3
Standardize on snake case for tracing
LegNeato Oct 29, 2020
79102a3
Add tracing support
mihai-dinculescu Jul 25, 2020
6a16cf8
Rocket can now compile on stable
LegNeato Jul 28, 2020
11bb5e2
Add tracing support example
LegNeato Jul 29, 2020
57ecb56
Add some coarse execution tracing
LegNeato Jul 29, 2020
cff6678
Remove old comment / docs
LegNeato Jul 29, 2020
cb3ee27
Fix book tests
LegNeato Jul 29, 2020
0d52c50
fix up some imports
LegNeato Jul 29, 2020
7f24509
Add back subscriber Cargo.toml instructions
LegNeato Jul 29, 2020
cb7ab0c
Change trace errors to trace
mihai-dinculescu Jul 29, 2020
7b02ccc
Tracing unit tests
mihai-dinculescu Jul 29, 2020
4000f1e
Tracing unit tests names
mihai-dinculescu Jul 29, 2020
e968c4b
Revert "Rocket can now compile on stable"
LegNeato Jul 29, 2020
58fed76
Fix tracing spans
LegNeato Aug 14, 2020
7b5ad7e
Do not include trailing newline on the last error
LegNeato Aug 14, 2020
c34a1c2
Standard tracing labels on snake_case
LegNeato Aug 14, 2020
26407e2
Add a validation error case to the tracing example
LegNeato Aug 14, 2020
94279d1
Use $crate everywhere
LegNeato Aug 15, 2020
bf35077
Add other levels to span macros
LegNeato Aug 15, 2020
2365016
Use the released tracing crate
mihai-dinculescu Oct 3, 2020
ca744b5
Fix email address
LegNeato Oct 29, 2020
62acd3f
Standardize on snake case for tracing
LegNeato Oct 29, 2020
4d7797a
Merge master
LegNeato Oct 29, 2020
72a28d4
Remove rustfmt option causing breakage
LegNeato Oct 29, 2020
2977b1f
Remove subscription helper mod
LegNeato Oct 29, 2020
264030f
Merge branch 'master' into add_tracing_support
LegNeato Nov 13, 2020
7a1dfdc
Fix type checking on fragments and implement e2e tests to cover this …
ArsileLuci Apr 29, 2021
d427f80
Add CHANGELOG note
ArsileLuci Apr 29, 2021
a34294c
Merge branch 'master' into ArsileLuci-master
tyranron Apr 29, 2021
64cb389
Some corrections [skip ci]
tyranron Apr 29, 2021
a340673
Fix interface fields not being resolved for sync context and rework a…
ArsileLuci Apr 29, 2021
28ebd31
Minor corrections
ArsileLuci Apr 29, 2021
9e46dce
Small style corrections
tyranron Apr 30, 2021
7c6fe5c
Merge master
ArsileLuci Jul 12, 2021
72d8cfa
Merge master
ArsileLuci Jul 12, 2021
d57126a
Merge master
ArsileLuci Jul 12, 2021
d0558a4
Impl async resolve tracing
ArsileLuci Jul 12, 2021
3c555dd
Impl codegen, add initial docs and tests
ArsileLuci Jul 21, 2021
5b1fed8
Fully hide tracing behind feature gate, add docs
ArsileLuci Jul 22, 2021
05a1432
Add test for subscriptions
ArsileLuci Jul 23, 2021
174593e
Extend example, book and small test corrections [run ci]
ArsileLuci Jul 23, 2021
469a46e
Merge branch 'master' into tracing-support
ArsileLuci Jul 23, 2021
2fe4106
Fix book imports [run ci]
ArsileLuci Jul 23, 2021
bb0e87d
Merge branch 'tracing-support' of https://github.com/ArsileLuci/junip…
ArsileLuci Jul 23, 2021
7b9ac11
Fix tokio dependency in book [run ci]
ArsileLuci Jul 23, 2021
2772b32
Fix book build [run ci]
ArsileLuci Jul 23, 2021
b2a285a
Minor corrections [run ci]
ArsileLuci Jul 23, 2021
33d6952
Some corrections
tyranron Jul 23, 2021
1af4115
WIP
ArsileLuci Jul 26, 2021
46c76bc
Refactor
ArsileLuci Jul 27, 2021
5dbdc94
Book corrections
ArsileLuci Jul 27, 2021
07bae4b
Fetch upstream
ArsileLuci Aug 12, 2021
c256a2e
Restore tracing codegen
ArsileLuci Aug 12, 2021
123a87b
Book and docs correction
ArsileLuci Aug 13, 2021
3420e8f
fmt fix
ArsileLuci Aug 13, 2021
7e31803
Impl sigils
ArsileLuci Aug 15, 2021
ae02db7
Impl field::Empty
ArsileLuci Aug 16, 2021
e69f261
Improve subscription tracing [run ci]
ArsileLuci Aug 16, 2021
c22ef43
Remove redundant code
ArsileLuci Aug 17, 2021
a2e43fc
Merge branch 'graphql-rust:master' into tracing-support
ArsileLuci Aug 31, 2021
4861c24
Small corrections [run-ci]
ArsileLuci Aug 31, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"juniper_codegen",
"juniper",
"examples/basic_subscriptions",
"examples/tracing_support",
"examples/warp_async",
"examples/warp_subscriptions",
"examples/actix_subscriptions",
Expand Down
2 changes: 2 additions & 0 deletions docs/book/content/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
- [Hyper](servers/hyper.md)
- [Third Party Integrations](servers/third-party.md)

- [Tracing](tracing/index.md)

- [Advanced Topics](advanced/index.md)

- [Introspection](advanced/introspection.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/book/content/advanced/implicit_and_explicit_null.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ In Juniper, this can be done using the `Nullable` type:
# extern crate juniper;
use juniper::{FieldResult, Nullable};

#[derive(juniper::GraphQLInputObject)]
#[derive(Debug, juniper::GraphQLInputObject)]
struct UserPatchInput {
pub favorite_number: Nullable<i32>,
pub least_favorite_number: Nullable<i32>,
Expand Down
4 changes: 2 additions & 2 deletions docs/book/content/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use juniper::{
# fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
# }

#[derive(GraphQLEnum)]
#[derive(Debug, GraphQLEnum)]
enum Episode {
NewHope,
Empire,
Expand All @@ -57,7 +57,7 @@ struct Human {

// There is also a custom derive for mapping GraphQL input objects.

#[derive(GraphQLInputObject)]
#[derive(Debug, GraphQLInputObject)]
#[graphql(description = "A humanoid creature in the Star Wars universe")]
struct NewHuman {
name: String,
Expand Down
296 changes: 296 additions & 0 deletions docs/book/content/tracing/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
# Tracing

Juniper has optional support for the [tracing] crate for instrumentation.

This feature is off by default and can be enabled via the `tracing` feature.

!FILENAME Cargo.toml

```toml
[dependencies]
juniper = { version = "0.15.7", features = ["default", "tracing"]}
tracing = "0.1.26"
tracing-subscriber = "0.2.15"
```

## Usage

```rust
# extern crate juniper;
extern crate tokio;
extern crate tracing;
extern crate tracing_subscriber;
use juniper::{EmptyMutation, EmptySubscription, RootNode, graphql_object, Variables};

#[derive(Clone, Copy, Debug)]
struct Query;

struct Foo {
value: i32,
}

#[graphql_object]
impl Foo {
// Value resolving is pretty straight-forward so we can skip tracing.
#[graphql(tracing(ignore))]
fn value(&self) -> i32 {
self.value
}

// Here we'll record span and it will have field with name "another" and value we passed.
fn multiply_values(&self, another: i32) -> i32 {
self.value * another
}

// Here we'll record span and it will have field with name "self.value"
#[instrument(fields(self.value = self.value))]
fn square_value(&self) -> i32 {
self.value * self.value
}
}

#[graphql_object]
impl Query {
async fn foo() -> Foo {
Foo { value: 42 }
}
}

type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;


#[tokio::main]
async fn main() {
// Set up the tracing subscriber.
let subscriber = tracing_subscriber::fmt()
// This enables standard env variables such as `RUST_LOG=trace`.
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
// This makes it so we can see all span events.
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL)
.finish();

tracing::subscriber::set_global_default(subscriber)
.expect("Setting default tracing subscriber failed");

// Set up GraphQL information.
let vars = Variables::new();
let root = Schema::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);

// When run with `RUST_LOG=trace cargo run`, this should output traces /
// span events to `stdout`.
let query = r#"
{
foo {
value
multiplyValues(another: 23)
squareValue
}
}"#;
let (_, _errors) = juniper::execute(query, None, &root, &vars, &())
.await
.unwrap();
}
```

## Skipping field resolvers

In certain scenarios you may want to skip tracing of some fields because it too
simple and straight-forward, that tracing preparations of this resolver would actually
take more time then execution. In this cases you can use `tracing(ignore)` argument of
`#[graphql]` attribute to completely disable tracing of this field resolver.

### Example

```rust
# extern crate juniper;
# use juniper::graphql_object;
# fn main() {}
#
# struct Context;
# impl juniper::Context for Context {}

struct User {
id: i32,
}

#[graphql_object(context = Context)]
impl User {
#[graphql(tracing(ignore))]
fn id(&self) -> i32 {
self.id
}

async fn friends(&self, context: &Context) -> Vec<User> {
// Some async query in which you're actually interested.
# unimplemented!()
}
}
```

Manually setting `#[graphql(tracing(ignore))]` to avoid tracing of all, let's say for
example synchronous field resolvers is rather inefficient when you have GraphQL
object with too much fields. In this case you can use `tracing` attribute on
top-level to skip tracing of specific field group or not to trace at all.
`tracing` attribute can be used with one of the following arguments:
`sync`, `async`, `only` or `skip_all`.
- Use `sync` to trace only synchronous resolvers (struct fields and `fn`s).
- Use `async` to trace only asynchronous resolvers (`async fn`s) and
subscriptions.
- Use `only` to trace only fields marked with `#[graphql(tracing(only))]`
- Use `skip_all` to skip tracing of all fields.

### Example

```rust
# extern crate juniper;
# use juniper::graphql_object;
# fn main() {}

struct MagicOfTracing;

#[graphql_object]
#[tracing(async)]
impl MagicOfTracing {
// Won't produce span because it's sync resolver
fn my_sync_fn(&self) -> String {
"Woah sync resolver!!".to_owned()
}

// Will produce span `MagicOfTracing.myAsyncFn`.
async fn my_async_fn(&self) -> String {
"Woah async resolver with traces!!".to_owned()
}

// Won't produce span because even though this is an async resolver
// it's also marked with `#[graphql(tracing(ignore))]`.
#[graphql(tracing(ignore))]
async fn non_traced_async_fn(&self) -> String {
"Leave no traces".to_owned()
}
}
```

**Note:** using of `tracing(sync)` with derived struct is no-op because all
resolvers within derived GraphQL object is considered to be synchronous, also
because of this `tracing(async)` will result in no traces.

In addition you can use `#[graphql(tracing(ignore))]` with `sync` and `async`
variants to exclude field from tracing even if it belongs to traced group.

**Be careful when skipping trace as it can lead to bad structured span trees,
disabling of tracing on one level won't disable tracing in it's child methods.
As a rule of thumb you should trace all field resolvers which may produce child
spans.**

If resolving of certain field requires additional arguments (when used `fn`s or
`async fn`s) they also will be included in resulted trace (except `self` and
`Context`).

```rust
# extern crate juniper;
# use juniper::{graphql_object, GraphQLObject};
#
# fn main() {}
#
# type Filter = i32;
#
# #[derive(GraphQLObject)]
# struct Product {
# id: i32
# }
#
# struct Catalog;
#[graphql_object]
impl Catalog {
async fn products(filter: Filter, count: i32) -> Vec<Product> {
// Some query
# unimplemented!()
}
}
```

In example above both `filter` and `count` of `products` field will be recorded
in produced [`Span`]. All fields will be recorded using their `fmt::Debug`
implementation, if your field doesn't implement `fmt::Debug` you'll get compile
time error. In this case ypu should either implement `fmt::Debug` or skip it
ArsileLuci marked this conversation as resolved.
Show resolved Hide resolved
using `#[instrument(skip(<fields to skip>))]` if you still want to record it but
for some reason you don't want to implement `fmt::Debug` trait, consider reintroducing
this field with `fields(field_name = some_value)` like shown bellow.


### Example
```rust
# extern crate juniper;
# use juniper::graphql_object;
#
# fn main() {}
#
# struct Query;

#[derive(Clone, juniper::GraphQLInputObject)]
struct NonDebug {
important_field: String,
}

# #[graphql_object]
# impl Query {
// Note that you can use name of the skipped field as alias.
#[instrument(skip(non_debug), fields(non_debug = non_debug.important_field.clone()))]
fn my_query(&self, non_debug: NonDebug) -> i32 {
// Some query
# unimplemented!()
}
# }
```

Custom fields generated this way are aware of `self` and can use `self` even if it not implicitly passed
to resolver. In case when resolver is `fn` with not only `self` arguments they're also available
to interact with as shown above. You can also access `executor` and `Context` as a result.

### Example
```rust
# extern crate juniper;
# use juniper::graphql_object;
#
# fn main() {}
#
struct Context {
data: i32,
}

impl juniper::Context for Context {}

struct Query {
data: i32,
}

#[graphql_object(context = Context)]
impl Query {
#[instrument(fields(ctx.data = executor.context().data))]
fn my_query(&self) -> i32 {
// Some query
# unimplemented!()
}

#[instrument(fields(data = self.data))]
fn self_aware() -> i32 {
// Some query
# unimplemented!()
}
# }
```

## `#[instrument]` attribute

In most aspects it mimics behavior of the original `#[instrument]` attribute
from [tracing] crate and you could use it as a reference. With the only key
deference you should understand, it applied implicitly to all resolvers if the
`tracing` feature is enabled.

[tracing]: https://crates.io/crates/tracing
[`skip`]: https://docs.rs/tracing/0.1.26/tracing/attr.instrument.html#skipping-fields
[`Span`]: https://docs.rs/tracing/0.1.26/tracing/struct.Span.html
4 changes: 2 additions & 2 deletions docs/book/content/types/input_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ attribute, similar to simple objects and enums:
```rust
# #![allow(unused_variables)]
# extern crate juniper;
#[derive(juniper::GraphQLInputObject)]
#[derive(Debug, juniper::GraphQLInputObject)]
struct Coordinate {
latitude: f64,
longitude: f64
Expand Down Expand Up @@ -36,7 +36,7 @@ and add documentation to both the type and the fields:
```rust
# #![allow(unused_variables)]
# extern crate juniper;
#[derive(juniper::GraphQLInputObject)]
#[derive(Debug, juniper::GraphQLInputObject)]
#[graphql(name="Coordinate", description="A position on the globe")]
struct WorldCoordinate {
#[graphql(name="lat", description="The latitude")]
Expand Down
7 changes: 5 additions & 2 deletions docs/book/tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Magnus Hallin <[email protected]>"]
build = "build.rs"

[dependencies]
juniper = { path = "../../../juniper" }
juniper = { path = "../../../juniper", features = ["default", "tracing"] }
juniper_iron = { path = "../../../juniper_iron" }
juniper_subscriptions = { path = "../../../juniper_subscriptions" }

Expand All @@ -16,9 +16,12 @@ iron = "0.5"
mount = "0.4"
skeptic = "0.13"
serde_json = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
uuid = "0.8"

tracing = "0.1.26"
tracing-subscriber = "0.2.1"

[build-dependencies]
skeptic = "0.13"

Expand Down
4 changes: 4 additions & 0 deletions examples/tracing_support/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock

Loading