-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from palantir/macros
Add a macro to span functions and methods
- Loading branch information
Showing
7 changed files
with
357 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,6 @@ | |
members = [ | ||
"http-zipkin", | ||
"zipkin", | ||
"zipkin-macros", | ||
"zipkin-types", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "zipkin-macros" | ||
version = "0.1.0" | ||
authors = ["Steven Fackler <sfackler@palantir.com>"] | ||
edition = "2018" | ||
license = "Apache-2.0" | ||
description = "Macros for use with `zipkin`" | ||
repository = "https://github.com/palantir/rust-zipkin" | ||
readme = "../README.md" | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
quote = "1.0" | ||
proc-macro2 = "1.0" | ||
syn = { version = "1.0", features = ["full"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Copyright 2020 Palantir Technologies, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
//! Macros for use with `zipkin`. | ||
//! | ||
//! You should not depend on this crate directly. | ||
extern crate proc_macro; | ||
|
||
use proc_macro::TokenStream; | ||
use proc_macro2::Span; | ||
use quote::{quote, ToTokens}; | ||
use syn::{ | ||
parse_macro_input, AttributeArgs, Error, ImplItemMethod, Lit, LitStr, Meta, NestedMeta, Stmt, | ||
}; | ||
|
||
struct Options { | ||
name: LitStr, | ||
} | ||
|
||
/// Wraps the execution of a function or method in a span. | ||
/// | ||
/// Both normal and `async` methods and functions are supported. The name of the span is specified as an argument | ||
/// to the macro attribute. | ||
/// | ||
/// Requires the `macros` Cargo feature. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```ignore | ||
/// #[zipkin::spanned(name = "shave yaks")] | ||
/// fn shave_some_yaks(yaks: &mut [Yak]) { | ||
/// // ... | ||
/// } | ||
/// | ||
/// #[zipkin::spanned(name = "asynchronously shave yaks")] | ||
/// async fn shave_some_other_yaks(yaks: &mut [Yak]) { | ||
/// // ... | ||
/// } | ||
/// | ||
/// struct Yak; | ||
/// | ||
/// impl Yak { | ||
/// #[zipkin::spanned(name = "shave a yak")] | ||
/// fn shave(&mut self) { | ||
/// // ... | ||
/// } | ||
/// | ||
/// #[zipkin::spanned(name = "asynchronously shave a yak")] | ||
/// async fn shave_nonblocking(&mut self) { | ||
/// // ... | ||
/// } | ||
/// } | ||
/// ``` | ||
#[proc_macro_attribute] | ||
pub fn spanned(args: TokenStream, item: TokenStream) -> TokenStream { | ||
let args = parse_macro_input!(args as AttributeArgs); | ||
let func = parse_macro_input!(item as ImplItemMethod); | ||
|
||
spanned_impl(args, func).unwrap_or_else(|e| e.to_compile_error().into()) | ||
} | ||
|
||
fn spanned_impl(args: AttributeArgs, mut func: ImplItemMethod) -> Result<TokenStream, Error> { | ||
let options = parse_options(args)?; | ||
let name = &options.name; | ||
|
||
if func.sig.asyncness.is_some() { | ||
let stmts = &func.block.stmts; | ||
let stmt = quote! { | ||
zipkin::next_span() | ||
.with_name(#name) | ||
.detach() | ||
.bind(async move { #(#stmts)* }) | ||
.await | ||
}; | ||
func.block.stmts = vec![Stmt::Expr(syn::parse2(stmt).unwrap())]; | ||
} else { | ||
let stmt = quote! { | ||
let __macro_impl_span = zipkin::next_span().with_name(#name); | ||
}; | ||
func.block.stmts.insert(0, syn::parse2(stmt).unwrap()); | ||
}; | ||
|
||
Ok(func.into_token_stream().into()) | ||
} | ||
|
||
fn parse_options(args: AttributeArgs) -> Result<Options, Error> { | ||
let mut name = None; | ||
|
||
for arg in args { | ||
let meta = match arg { | ||
NestedMeta::Meta(meta) => meta, | ||
NestedMeta::Lit(lit) => { | ||
return Err(Error::new_spanned(&lit, "invalid attribute syntax")) | ||
} | ||
}; | ||
|
||
if meta.path().is_ident("name") { | ||
match meta { | ||
Meta::NameValue(meta) => match meta.lit { | ||
Lit::Str(lit) => name = Some(lit), | ||
lit => return Err(Error::new_spanned(&lit, "expected a string literal")), | ||
}, | ||
_ => return Err(Error::new_spanned(meta, "expected `name = \"...\"`")), | ||
} | ||
} else { | ||
return Err(Error::new_spanned(meta.path(), "unknown option")); | ||
} | ||
} | ||
|
||
Ok(Options { | ||
name: name.ok_or_else(|| Error::new(Span::call_site(), "missing `name` option"))?, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// Copyright 2020 Palantir Technologies, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
use crate as zipkin; // hack to get the macro codegen to work in the same crate | ||
use crate::{spanned, test}; | ||
use futures::executor; | ||
|
||
#[test] | ||
fn blocking_free_function() { | ||
#[spanned(name = "foobar")] | ||
fn foo() { | ||
zipkin::next_span().with_name("fizzbuzz"); | ||
} | ||
|
||
test::init(); | ||
|
||
let span = zipkin::next_span().with_name("root"); | ||
foo(); | ||
drop(span); | ||
|
||
let spans = test::take(); | ||
assert_eq!(spans.len(), 3); | ||
assert_eq!(spans[0].name(), Some("fizzbuzz")); | ||
assert_eq!(spans[1].name(), Some("foobar")); | ||
assert_eq!(spans[2].name(), Some("root")); | ||
assert_eq!(spans[0].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[1].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[0].parent_id(), Some(spans[1].id())); | ||
assert_eq!(spans[1].parent_id(), Some(spans[2].id())); | ||
assert_eq!(spans[2].parent_id(), None); | ||
} | ||
|
||
#[test] | ||
fn blocking_associated_function() { | ||
struct Foo; | ||
|
||
impl Foo { | ||
#[spanned(name = "foobar")] | ||
fn foo() { | ||
zipkin::next_span().with_name("fizzbuzz"); | ||
} | ||
} | ||
|
||
test::init(); | ||
|
||
let span = zipkin::next_span().with_name("root"); | ||
Foo::foo(); | ||
drop(span); | ||
|
||
let spans = test::take(); | ||
assert_eq!(spans.len(), 3); | ||
assert_eq!(spans[0].name(), Some("fizzbuzz")); | ||
assert_eq!(spans[1].name(), Some("foobar")); | ||
assert_eq!(spans[2].name(), Some("root")); | ||
assert_eq!(spans[0].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[1].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[0].parent_id(), Some(spans[1].id())); | ||
assert_eq!(spans[1].parent_id(), Some(spans[2].id())); | ||
assert_eq!(spans[2].parent_id(), None); | ||
} | ||
|
||
#[test] | ||
fn blocking_method() { | ||
struct Foo; | ||
|
||
impl Foo { | ||
#[spanned(name = "foobar")] | ||
fn foo(&self) { | ||
zipkin::next_span().with_name("fizzbuzz"); | ||
} | ||
} | ||
|
||
test::init(); | ||
|
||
let span = zipkin::next_span().with_name("root"); | ||
Foo.foo(); | ||
drop(span); | ||
|
||
let spans = test::take(); | ||
assert_eq!(spans.len(), 3); | ||
assert_eq!(spans[0].name(), Some("fizzbuzz")); | ||
assert_eq!(spans[1].name(), Some("foobar")); | ||
assert_eq!(spans[2].name(), Some("root")); | ||
assert_eq!(spans[0].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[1].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[0].parent_id(), Some(spans[1].id())); | ||
assert_eq!(spans[1].parent_id(), Some(spans[2].id())); | ||
assert_eq!(spans[2].parent_id(), None); | ||
} | ||
|
||
#[test] | ||
fn async_free_function() { | ||
#[spanned(name = "foobar")] | ||
async fn foo() { | ||
zipkin::next_span().with_name("fizzbuzz"); | ||
} | ||
|
||
test::init(); | ||
|
||
let future = zipkin::next_span().with_name("root").detach().bind(foo()); | ||
executor::block_on(future); | ||
|
||
let spans = test::take(); | ||
assert_eq!(spans.len(), 3); | ||
assert_eq!(spans[0].name(), Some("fizzbuzz")); | ||
assert_eq!(spans[1].name(), Some("foobar")); | ||
assert_eq!(spans[2].name(), Some("root")); | ||
assert_eq!(spans[0].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[1].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[0].parent_id(), Some(spans[1].id())); | ||
assert_eq!(spans[1].parent_id(), Some(spans[2].id())); | ||
assert_eq!(spans[2].parent_id(), None); | ||
} | ||
|
||
#[test] | ||
fn async_associated_function() { | ||
struct Foo; | ||
|
||
impl Foo { | ||
#[spanned(name = "foobar")] | ||
async fn foo() { | ||
zipkin::next_span().with_name("fizzbuzz"); | ||
} | ||
} | ||
|
||
test::init(); | ||
|
||
let future = zipkin::next_span() | ||
.with_name("root") | ||
.detach() | ||
.bind(Foo::foo()); | ||
executor::block_on(future); | ||
|
||
let spans = test::take(); | ||
assert_eq!(spans.len(), 3); | ||
assert_eq!(spans[0].name(), Some("fizzbuzz")); | ||
assert_eq!(spans[1].name(), Some("foobar")); | ||
assert_eq!(spans[2].name(), Some("root")); | ||
assert_eq!(spans[0].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[1].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[0].parent_id(), Some(spans[1].id())); | ||
assert_eq!(spans[1].parent_id(), Some(spans[2].id())); | ||
assert_eq!(spans[2].parent_id(), None); | ||
} | ||
|
||
#[test] | ||
fn async_method() { | ||
struct Foo; | ||
|
||
impl Foo { | ||
#[spanned(name = "foobar")] | ||
async fn foo(&self) { | ||
zipkin::next_span().with_name("fizzbuzz"); | ||
} | ||
} | ||
|
||
test::init(); | ||
|
||
let future = zipkin::next_span() | ||
.with_name("root") | ||
.detach() | ||
.bind(Foo.foo()); | ||
executor::block_on(future); | ||
|
||
let spans = test::take(); | ||
assert_eq!(spans.len(), 3); | ||
assert_eq!(spans[0].name(), Some("fizzbuzz")); | ||
assert_eq!(spans[1].name(), Some("foobar")); | ||
assert_eq!(spans[2].name(), Some("root")); | ||
assert_eq!(spans[0].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[1].trace_id(), spans[2].trace_id()); | ||
assert_eq!(spans[0].parent_id(), Some(spans[1].id())); | ||
assert_eq!(spans[1].parent_id(), Some(spans[2].id())); | ||
assert_eq!(spans[2].parent_id(), None); | ||
} |
Oops, something went wrong.