From 922be0c244338c58f4a3055792499a46dc7baae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 15 Dec 2025 00:27:26 +0100 Subject: [PATCH 01/14] First release candidate to provide the possibility of using tracing instead of log/env_logger as the log layer --- Cargo.lock | 50 ++++++++++++-- Cargo.toml | 11 +++- lambda-appsync-proc/Cargo.toml | 2 +- .../src/appsync_lambda_main/mod.rs | 65 +++++++++++++++++-- .../tests/fail/invalid_log_init_args.rs | 8 +++ .../tests/fail/invalid_log_init_args.stderr | 14 ++++ .../tests/fail/invalid_log_init_ret.rs | 9 +++ .../tests/fail/invalid_log_init_ret.stderr | 11 ++++ lambda-appsync-proc/tests/pass/log_init.rs | 9 +++ lambda-appsync/Cargo.toml | 15 ++++- lambda-appsync/src/lib.rs | 12 +++- 11 files changed, 186 insertions(+), 20 deletions(-) create mode 100644 lambda-appsync-proc/tests/fail/invalid_log_init_args.rs create mode 100644 lambda-appsync-proc/tests/fail/invalid_log_init_args.stderr create mode 100644 lambda-appsync-proc/tests/fail/invalid_log_init_ret.rs create mode 100644 lambda-appsync-proc/tests/fail/invalid_log_init_ret.stderr create mode 100644 lambda-appsync-proc/tests/pass/log_init.rs diff --git a/Cargo.lock b/Cargo.lock index 2d9e9c5..ca9fae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1231,6 +1231,8 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "tracing", + "tracing-subscriber", "uuid", ] @@ -1368,6 +1370,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1405,6 +1417,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460b31760eb56186dc8006c41b807a59c25dda5aad08eb90c55d84d19e8aff40" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "p256" version = "0.11.0" @@ -1796,6 +1814,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "socket2" version = "0.5.5" @@ -2110,6 +2134,17 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -2127,14 +2162,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", + "nu-ansi-term", "once_cell", "regex", "serde", "serde_json", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", "tracing-serde", ] @@ -2290,9 +2328,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" -version = "0.3.0" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ad91d846a4a5342c1fb7008d26124ee6cf94a3953751618577295373b32117" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", @@ -2300,15 +2338,15 @@ dependencies = [ [[package]] name = "winapi-i686-pc-windows-gnu" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a16a8e2ebfc883e2b1771c6482b1fb3c6831eab289ba391619a2d93a7356220f" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca29cb03c8ceaf20f8224a18a530938305e9872b1478ea24ff44b4f503a1d1d" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "wincolor" diff --git a/Cargo.toml b/Cargo.toml index cde9255..a29ddcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,13 +28,20 @@ tokio = { version = "1", features = ["macros"] } lambda_runtime = "1.0" aws-config = { version = "1.5", features = ["behavior-version-latest"] } aws-smithy-types = "1.3" -log = "0.4" -env_logger = "0.11" thiserror = "1.0" uuid = { version = "1.11", features = ["v4"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +# Default Lambda logging is based on log/env_logger +log = { version = "0.4"} +env_logger = { version = "0.11" } + +# Alternatively, a feature allow the use of tracing/tracing-subscriber +tracing = { version = "0.1" } +tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] } + + # Proc-macro crate dependencies syn = { version = "2.0", default-features = false, features = [ "parsing", diff --git a/lambda-appsync-proc/Cargo.toml b/lambda-appsync-proc/Cargo.toml index a8e5585..a833eb2 100644 --- a/lambda-appsync-proc/Cargo.toml +++ b/lambda-appsync-proc/Cargo.toml @@ -21,7 +21,7 @@ proc-macro2 = { workspace = true } graphql-parser = { workspace = true } [dev-dependencies] -lambda-appsync = { path = "../lambda-appsync" } +lambda-appsync = { path = "../lambda-appsync", default-features = false, features = ["env_logger", "tracing"] } aws-sdk-s3 = { workspace = true } aws-sdk-dynamodb = { workspace = true } serde = { workspace = true } diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index 3bff57f..549f7c5 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -70,6 +70,7 @@ enum OptionalParameter { ExcludeAppsyncOperations(bool), OnlyAppsyncOperations(bool), Hook(Ident), + LogInit(Ident), TypeOverride(TypeOverride), NameOverride(NameOverride), } @@ -94,6 +95,7 @@ impl Parse for OptionalParameter { input.parse::()?.value(), )), "hook" => Ok(Self::Hook(input.parse()?)), + "log_init" => Ok(Self::LogInit(input.parse()?)), "type_override" => Ok(Self::TypeOverride(input.parse()?)), "name_override" => Ok(Self::NameOverride(input.parse()?)), // Deprecated options @@ -148,6 +150,7 @@ struct OptionalParameters { appsync_operations: bool, lambda_handler: bool, hook: Option, + log_init: Option, tos: TypeOverrides, nos: NameOverrides, } @@ -159,6 +162,7 @@ impl Default for OptionalParameters { appsync_operations: true, lambda_handler: true, hook: None, + log_init: None, tos: TypeOverrides::new(), nos: NameOverrides::new(), } @@ -189,6 +193,9 @@ impl OptionalParameters { OptionalParameter::Hook(ident) => { self.hook.replace(ident); } + OptionalParameter::LogInit(ident) => { + self.log_init.replace(ident); + } OptionalParameter::TypeOverride(to) => { // Retrieve the entry corresponding to `Type.field` let to_field_entry = self @@ -327,8 +334,11 @@ impl AppsyncLambdaMain { quote! {} }; tokens.extend(quote! { + #[cfg_attr(feature = "tracing", ::lambda_appsync::tracing::instrument(fields(operation = ?event.info.operation)))] async fn appsync_handler(event: ::lambda_appsync::AppsyncEvent) -> ::lambda_appsync::AppsyncResponse { + #[cfg(feature = "env_logger")] ::lambda_appsync::log::info!("event={event:?}"); + #[cfg(feature = "env_logger")] ::lambda_appsync::log::info!("operation={:?}", event.info.operation); #call_hook @@ -357,6 +367,37 @@ impl AppsyncLambdaMain { } } + fn default_log_init() -> TokenStream2 { + quote! { + #[cfg(feature = "env_logger")] + ::lambda_appsync::env_logger::Builder::from_env( + ::lambda_appsync::env_logger::Env::default() + .default_filter_or("info,tracing::span=warn") + .default_write_style_or("never"), + ) + .format_timestamp_micros() + .init(); + + #[cfg(feature = "tracing")] + ::lambda_appsync::tracing_subscriber::fmt() + .json() + .with_env_filter( + ::lambda_appsync::tracing_subscriber::EnvFilter::from_default_env() + .add_directive(tracing::Level::INFO.into()), + ) + // this needs to be set to remove duplicated information in the log. + .with_current_span(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + // remove the name of the function from every log entry + .with_target(false) + .init(); + } + } + fn lambda_main(&self, tokens: &mut TokenStream2) { let (config_init, config_getter) = if !self.aws_clients.is_empty() { (AWSClient::aws_config_init(), AWSClient::aws_config_getter()) @@ -377,12 +418,28 @@ impl AppsyncLambdaMain { ) }; + let log_init = if let Some(ref log_init) = self.options.log_init { + quote_spanned! {log_init.span()=> + mod _check_sig { + #[inline(always)] + pub(super) fn call_log_init ()>(f: F) {f()} + } + _check_sig::call_log_init(#log_init); + } + } else { + Self::default_log_init() + }; + tokens.extend(quote! { + #[cfg_attr(feature = "tracing", ::lambda_appsync::tracing::instrument(skip(event), fields(req_id = %event.context.request_id)))] async fn function_handler( event: ::lambda_appsync::lambda_runtime::LambdaEvent<::lambda_appsync::serde_json::Value>, ) -> ::core::result::Result<#ret_type, ::lambda_appsync::lambda_runtime::Error> { + #[cfg(feature = "env_logger")] ::lambda_appsync::log::debug!("{event:?}"); + #[cfg(feature = "env_logger")] ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); + Ok(#appsync_handler(::lambda_appsync::serde_json::from_value(event.payload)?).await) } @@ -393,13 +450,7 @@ impl AppsyncLambdaMain { use ::lambda_appsync::tokio; #[tokio::main] async fn main() -> ::core::result::Result<(), ::lambda_appsync::lambda_runtime::Error> { - ::lambda_appsync::env_logger::Builder::from_env( - ::lambda_appsync::env_logger::Env::default() - .default_filter_or("info,tracing::span=warn") - .default_write_style_or("never"), - ) - .format_timestamp_micros() - .init(); + #log_init #config_init diff --git a/lambda-appsync-proc/tests/fail/invalid_log_init_args.rs b/lambda-appsync-proc/tests/fail/invalid_log_init_args.rs new file mode 100644 index 0000000..17fb867 --- /dev/null +++ b/lambda-appsync-proc/tests/fail/invalid_log_init_args.rs @@ -0,0 +1,8 @@ +mod no_run { + fn custom_log_init(_arg: String) { + // Here would go custom initialization code + } + use lambda_appsync::appsync_lambda_main; + appsync_lambda_main!("../../../../schema.graphql", log_init = custom_log_init); +} +fn main() {} diff --git a/lambda-appsync-proc/tests/fail/invalid_log_init_args.stderr b/lambda-appsync-proc/tests/fail/invalid_log_init_args.stderr new file mode 100644 index 0000000..d001789 --- /dev/null +++ b/lambda-appsync-proc/tests/fail/invalid_log_init_args.stderr @@ -0,0 +1,14 @@ +error[E0593]: function is expected to take 0 arguments, but it takes 1 argument + --> tests/fail/invalid_log_init_args.rs:6:67 + | +2 | fn custom_log_init(_arg: String) { + | -------------------------------- takes 1 argument +... +6 | appsync_lambda_main!("../../../../schema.graphql", log_init = custom_log_init); + | ^^^^^^^^^^^^^^^ expected function that takes 0 arguments + | +note: required by a bound in `call_log_init` + --> tests/fail/invalid_log_init_args.rs:6:67 + | +6 | appsync_lambda_main!("../../../../schema.graphql", log_init = custom_log_init); + | ^^^^^^^^^^^^^^^ required by this bound in `call_log_init` diff --git a/lambda-appsync-proc/tests/fail/invalid_log_init_ret.rs b/lambda-appsync-proc/tests/fail/invalid_log_init_ret.rs new file mode 100644 index 0000000..c21e93d --- /dev/null +++ b/lambda-appsync-proc/tests/fail/invalid_log_init_ret.rs @@ -0,0 +1,9 @@ +mod no_run { + fn custom_log_init() -> &'static str { + // Here would go custom initialization code + "some value" + } + use lambda_appsync::appsync_lambda_main; + appsync_lambda_main!("../../../../schema.graphql", log_init = custom_log_init); +} +fn main() {} diff --git a/lambda-appsync-proc/tests/fail/invalid_log_init_ret.stderr b/lambda-appsync-proc/tests/fail/invalid_log_init_ret.stderr new file mode 100644 index 0000000..d7a6c50 --- /dev/null +++ b/lambda-appsync-proc/tests/fail/invalid_log_init_ret.stderr @@ -0,0 +1,11 @@ +error[E0271]: expected `custom_log_init` to be a fn item that returns `()`, but it returns `&str` + --> tests/fail/invalid_log_init_ret.rs:7:67 + | +7 | appsync_lambda_main!("../../../../schema.graphql", log_init = custom_log_init); + | ^^^^^^^^^^^^^^^ expected `()`, found `&str` + | +note: required by a bound in `call_log_init` + --> tests/fail/invalid_log_init_ret.rs:7:67 + | +7 | appsync_lambda_main!("../../../../schema.graphql", log_init = custom_log_init); + | ^^^^^^^^^^^^^^^ required by this bound in `call_log_init` diff --git a/lambda-appsync-proc/tests/pass/log_init.rs b/lambda-appsync-proc/tests/pass/log_init.rs new file mode 100644 index 0000000..02257ac --- /dev/null +++ b/lambda-appsync-proc/tests/pass/log_init.rs @@ -0,0 +1,9 @@ +mod no_run { + fn custom_log_init() { + // Here would go custom initialization code + } + use lambda_appsync::appsync_lambda_main; + appsync_lambda_main!("../../../../schema.graphql", log_init = custom_log_init); +} + +fn main() {} diff --git a/lambda-appsync/Cargo.toml b/lambda-appsync/Cargo.toml index 71e076b..3641014 100644 --- a/lambda-appsync/Cargo.toml +++ b/lambda-appsync/Cargo.toml @@ -18,12 +18,23 @@ tokio = { workspace = true } lambda_runtime = { workspace = true } aws-config = { workspace = true } aws-smithy-types = { workspace = true } -log = { workspace = true } -env_logger = { workspace = true } thiserror = { workspace = true } uuid = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +# For feature env_logger (defautl) +log = { workspace = true, optional = true } +env_logger = { workspace = true, optional = true } + +# For feature tracing +tracing = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, optional = true } + [dev-dependencies] aws-sdk-dynamodb = { workspace = true } + +[features] +default = ["env_logger"] +env_logger = ["dep:log", "dep:env_logger"] +tracing = ["dep:tracing", "dep:tracing-subscriber"] diff --git a/lambda-appsync/src/lib.rs b/lambda-appsync/src/lib.rs index 3d25981..708f537 100644 --- a/lambda-appsync/src/lib.rs +++ b/lambda-appsync/src/lib.rs @@ -103,13 +103,21 @@ pub use lambda_appsync_proc::appsync_operation; // Re-export crates that are mandatory for the proc_macro to succeed pub use aws_config; -pub use env_logger; pub use lambda_runtime; -pub use log; pub use serde; pub use serde_json; pub use tokio; +#[cfg(feature = "env_logger")] +pub use env_logger; +#[cfg(feature = "env_logger")] +pub use log; + +#[cfg(feature = "tracing")] +pub use tracing; +#[cfg(feature = "tracing")] +pub use tracing_subscriber; + /// Authorization strategy for AppSync operations. /// /// It determines whether operations are allowed or denied based on the From 7e580337ed60df017252c633f217d9ca9f73fb26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 15 Dec 2025 08:39:52 +0100 Subject: [PATCH 02/14] Merge doc-fix from master --- lambda-appsync-proc/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/lambda-appsync-proc/src/lib.rs b/lambda-appsync-proc/src/lib.rs index 4e62992..6858753 100644 --- a/lambda-appsync-proc/src/lib.rs +++ b/lambda-appsync-proc/src/lib.rs @@ -194,9 +194,6 @@ use proc_macro::TokenStream; /// name_override = Player.name: email, /// // Override team `PYTHON` to be `Snake` (instead of `Python`) /// name_override = Team.PYTHON: Snake, -/// name_override = WeirdFieldNames.await: no_await, -/// name_override = WeirdFieldNames.crate: no_crate, -/// name_override = WeirdFieldNames.u8: no_u8, /// // MUST also override ALL the operations return type !!! /// type_override = Query.players: NewPlayer, /// type_override = Query.player: NewPlayer, From 6cba18dec7cb365a4526fe5a8d4ccdd7fda1ee70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 15 Dec 2025 09:00:04 +0100 Subject: [PATCH 03/14] Fix tracing init code --- lambda-appsync-proc/src/appsync_lambda_main/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index 549f7c5..82d57f5 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -383,7 +383,7 @@ impl AppsyncLambdaMain { .json() .with_env_filter( ::lambda_appsync::tracing_subscriber::EnvFilter::from_default_env() - .add_directive(tracing::Level::INFO.into()), + .add_directive(::lambda_appsync::tracing::Level::INFO.into()), ) // this needs to be set to remove duplicated information in the log. .with_current_span(false) From 3a56496dfb0e893f6a761cde73a6d937ce4358ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 15 Dec 2025 09:02:44 +0100 Subject: [PATCH 04/14] Documentation of the new log_init features --- lambda-appsync-proc/src/lib.rs | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/lambda-appsync-proc/src/lib.rs b/lambda-appsync-proc/src/lib.rs index 6858753..78a98f5 100644 --- a/lambda-appsync-proc/src/lib.rs +++ b/lambda-appsync-proc/src/lib.rs @@ -29,6 +29,7 @@ use proc_macro::TokenStream; /// /// - `batch = bool`: Enable/disable batch request handling (default: true) /// - `hook = fn_name`: Add a custom hook function for request validation/auth +/// - `log_init = fn_name`: Use a custom log initialization function instead of the default one /// - `exclude_lambda_handler = bool`: Skip generation of Lambda handler code /// - `only_lambda_handler = bool`: Only generate Lambda handler code /// - `exclude_appsync_types = bool`: Skip generation of GraphQL type definitions @@ -206,6 +207,70 @@ use proc_macro::TokenStream; /// Note that when using `name_override`, the macro does not automatically change the case: /// you are responsible to provide the appropriate casing or Clippy will complain. /// +/// ## Use a custom log initialization function: +/// +/// ### Feature `env_logger` (default) +/// +/// By default, `lambda_appsync` exposes and uses `log` and `env_logger`. You can override the +/// initialization code if you wish: +/// +/// ```no_run +/// # mod sub { +/// // This is in fact equivalent to the default initialization code +/// fn log_init_fct() { +/// use lambda_appsync::env_logger; +/// env_logger::Builder::from_env( +/// env_logger::Env::default() +/// // Default log level is info, expect tracing::span is warn +/// .default_filter_or("info,tracing::span=warn") +/// .default_write_style_or("never"), +/// ) +/// // Format timestamps with microseconds +/// .format_timestamp_micros() +/// .init(); +/// } +/// lambda_appsync::appsync_lambda_main!( +/// "schema.graphql", +/// log_init = log_init_fct +/// ); +/// # } +/// # fn main() {} +/// ``` +/// +/// ### Feature `tracing` +/// +/// Alternatively, you can use the `tracing` feature so `lambda_appsync` exposes and uses `tracing` and `tracing-subscriber` +/// +/// ```no_run +/// # mod sub { +/// // This is in fact equivalent to the default initialization code +/// fn tracing_init_fct() { +/// use lambda_appsync::{tracing, tracing_subscriber}; +/// tracing_subscriber::fmt() +/// .json() +/// .with_env_filter( +/// tracing_subscriber::EnvFilter::from_default_env() +/// .add_directive(tracing::Level::INFO.into()), +/// ) +/// // this needs to be set to remove duplicated information in the log. +/// .with_current_span(false) +/// // this needs to be set to false, otherwise ANSI color codes will +/// // show up in a confusing manner in CloudWatch logs. +/// .with_ansi(false) +/// // disabling time is handy because CloudWatch will add the ingestion time. +/// .without_time() +/// // remove the name of the function from every log entry +/// .with_target(false) +/// .init(); +/// } +/// lambda_appsync::appsync_lambda_main!( +/// "schema.graphql", +/// log_init = tracing_init_fct +/// ); +/// # } +/// # fn main() {} +/// ``` +/// /// ## Disable batch processing: /// ```no_run /// # mod sub { From e96c1f7e89518fcda880e4f40d9706d8870e228f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 15 Dec 2025 16:52:21 +0100 Subject: [PATCH 05/14] Redesign so the proc-macro is also "feature-aware", previously I was generating code with #[cfg(feature=...)] directives but that was failing because that ended-up in the user crate which did not have the features defined. Now the proc-macro crate also have the features, it inherits them from the lib crate and generates code as requested by the features. --- Cargo.lock | 1 + Cargo.toml | 6 +- lambda-appsync-proc/Cargo.toml | 6 + .../src/appsync_lambda_main/graphql.rs | 15 ++- .../src/appsync_lambda_main/mod.rs | 105 +++++++++++++----- lambda-appsync/Cargo.toml | 6 +- 6 files changed, 102 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca9fae9..451d2f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1248,6 +1248,7 @@ dependencies = [ "quote", "serde", "syn 2.0.52", + "tracing", "trybuild", ] diff --git a/Cargo.toml b/Cargo.toml index a29ddcf..8a56b8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,11 +34,11 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # Default Lambda logging is based on log/env_logger -log = { version = "0.4"} -env_logger = { version = "0.11" } +log = "0.4" +env_logger = "0.11" # Alternatively, a feature allow the use of tracing/tracing-subscriber -tracing = { version = "0.1" } +tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] } diff --git a/lambda-appsync-proc/Cargo.toml b/lambda-appsync-proc/Cargo.toml index a833eb2..d56f2fc 100644 --- a/lambda-appsync-proc/Cargo.toml +++ b/lambda-appsync-proc/Cargo.toml @@ -22,7 +22,13 @@ graphql-parser = { workspace = true } [dev-dependencies] lambda-appsync = { path = "../lambda-appsync", default-features = false, features = ["env_logger", "tracing"] } +tracing = { workspace = true } aws-sdk-s3 = { workspace = true } aws-sdk-dynamodb = { workspace = true } serde = { workspace = true } trybuild = { workspace = true } + +[features] +default = ["env_logger", "tracing"] +env_logger = [] +tracing = [] diff --git a/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs b/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs index e33696f..dc0f94a 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs @@ -971,7 +971,20 @@ impl GraphQLSchema { let subscription_field_execute_match_arm = self .subscriptions .execute_match_arm_iter(OperationKind::Subscription); + let span = current_span(); + + #[allow(unused_mut)] + let mut log_lines = proc_macro2::TokenStream::new(); + #[cfg(feature = "env_logger")] + log_lines.extend(quote_spanned! {span=> + ::lambda_appsync::log::error!("{e}"); + }); + #[cfg(feature = "tracing")] + log_lines.extend(quote_spanned! {span=> + ::lambda_appsync::tracing::error!("{e}"); + }); + tokens.extend(quote_spanned! {span=> impl Operation { async fn execute(self, @@ -980,7 +993,7 @@ impl GraphQLSchema { match self._execute(event).await { ::core::result::Result::Ok(v) => v.into(), ::core::result::Result::Err(e) => { - ::lambda_appsync::log::error!("{e}"); + #log_lines e.into() } } diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index 82d57f5..e34f69d 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -312,6 +312,19 @@ impl Parse for AppsyncLambdaMain { impl AppsyncLambdaMain { fn appsync_event_handler(&self, tokens: &mut TokenStream2) { + #[cfg(feature = "tracing")] + tokens.extend(quote! { + #[::lambda_appsync::tracing::instrument(fields(operation = ?event.info.operation))] + }); + + #[allow(unused_mut)] + let mut log_lines = proc_macro2::TokenStream::new(); + #[cfg(feature = "env_logger")] + log_lines.extend(quote! { + ::lambda_appsync::log::info!("event={event:?}"); + ::lambda_appsync::log::info!("operation={:?}", event.info.operation); + }); + let call_hook = if let Some(ref hook) = self.options.hook { quote_spanned! {hook.span()=> mod _check_sig { @@ -333,13 +346,10 @@ impl AppsyncLambdaMain { } else { quote! {} }; + tokens.extend(quote! { - #[cfg_attr(feature = "tracing", ::lambda_appsync::tracing::instrument(fields(operation = ?event.info.operation)))] async fn appsync_handler(event: ::lambda_appsync::AppsyncEvent) -> ::lambda_appsync::AppsyncResponse { - #[cfg(feature = "env_logger")] - ::lambda_appsync::log::info!("event={event:?}"); - #[cfg(feature = "env_logger")] - ::lambda_appsync::log::info!("operation={:?}", event.info.operation); + #log_lines #call_hook @@ -367,9 +377,9 @@ impl AppsyncLambdaMain { } } - fn default_log_init() -> TokenStream2 { + #[allow(dead_code)] + fn default_env_logger_init() -> TokenStream2 { quote! { - #[cfg(feature = "env_logger")] ::lambda_appsync::env_logger::Builder::from_env( ::lambda_appsync::env_logger::Env::default() .default_filter_or("info,tracing::span=warn") @@ -377,13 +387,17 @@ impl AppsyncLambdaMain { ) .format_timestamp_micros() .init(); + } + } - #[cfg(feature = "tracing")] + #[allow(dead_code)] + fn default_tracing_init() -> TokenStream2 { + quote! { ::lambda_appsync::tracing_subscriber::fmt() .json() .with_env_filter( ::lambda_appsync::tracing_subscriber::EnvFilter::from_default_env() - .add_directive(::lambda_appsync::tracing::Level::INFO.into()), + .add_directive(tracing::Level::INFO.into()), ) // this needs to be set to remove duplicated information in the log. .with_current_span(false) @@ -398,14 +412,7 @@ impl AppsyncLambdaMain { } } - fn lambda_main(&self, tokens: &mut TokenStream2) { - let (config_init, config_getter) = if !self.aws_clients.is_empty() { - (AWSClient::aws_config_init(), AWSClient::aws_config_getter()) - } else { - (TokenStream2::new(), TokenStream2::new()) - }; - let aws_client_getters = self.aws_clients.iter().map(|ac| ac.aws_client_getter()); - + fn lambda_function_handler(&self, tokens: &mut TokenStream2) { let (appsync_handler, ret_type) = if self.options.batch { ( format_ident!("appsync_batch_handler"), @@ -418,6 +425,37 @@ impl AppsyncLambdaMain { ) }; + #[cfg(feature = "tracing")] + tokens.extend(quote! { + #[::lambda_appsync::tracing::instrument(skip(event), fields(req_id = %event.context.request_id))] + }); + + #[allow(unused_mut)] + let mut log_lines = proc_macro2::TokenStream::new(); + #[cfg(feature = "env_logger")] + log_lines.extend(quote! { + ::lambda_appsync::log::debug!("{event:?}"); + ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); + }); + + tokens.extend(quote! { + async fn function_handler( + event: ::lambda_appsync::lambda_runtime::LambdaEvent<::lambda_appsync::serde_json::Value>, + ) -> ::core::result::Result<#ret_type, ::lambda_appsync::lambda_runtime::Error> { + #log_lines + Ok(#appsync_handler(::lambda_appsync::serde_json::from_value(event.payload)?).await) + } + }); + } + + fn lambda_main(&self, tokens: &mut TokenStream2) { + let (config_init, config_getter) = if !self.aws_clients.is_empty() { + (AWSClient::aws_config_init(), AWSClient::aws_config_getter()) + } else { + (TokenStream2::new(), TokenStream2::new()) + }; + let aws_client_getters = self.aws_clients.iter().map(|ac| ac.aws_client_getter()); + let log_init = if let Some(ref log_init) = self.options.log_init { quote_spanned! {log_init.span()=> mod _check_sig { @@ -427,27 +465,33 @@ impl AppsyncLambdaMain { _check_sig::call_log_init(#log_init); } } else { - Self::default_log_init() + #[allow(unused_mut)] + let mut default_log_init = proc_macro2::TokenStream::new(); + #[cfg(feature = "env_logger")] + default_log_init.extend(Self::default_env_logger_init()); + #[cfg(feature = "tracing")] + default_log_init.extend(Self::default_tracing_init()); + default_log_init }; - tokens.extend(quote! { - #[cfg_attr(feature = "tracing", ::lambda_appsync::tracing::instrument(skip(event), fields(req_id = %event.context.request_id)))] - async fn function_handler( - event: ::lambda_appsync::lambda_runtime::LambdaEvent<::lambda_appsync::serde_json::Value>, - ) -> ::core::result::Result<#ret_type, ::lambda_appsync::lambda_runtime::Error> { - #[cfg(feature = "env_logger")] - ::lambda_appsync::log::debug!("{event:?}"); - #[cfg(feature = "env_logger")] - ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); + #[allow(unused_mut)] + let mut bing_in_scope = TokenStream2::new(); + bing_in_scope.extend(quote! { + use ::lambda_appsync::tokio; + }); + #[cfg(feature = "tracing")] + bing_in_scope.extend(quote! { + use ::lambda_appsync::tracing; + }); - Ok(#appsync_handler(::lambda_appsync::serde_json::from_value(event.payload)?).await) - } + tokens.extend(quote! { #config_getter #(#aws_client_getters)* - use ::lambda_appsync::tokio; + #bing_in_scope + #[tokio::main] async fn main() -> ::core::result::Result<(), ::lambda_appsync::lambda_runtime::Error> { #log_init @@ -470,6 +514,7 @@ impl ToTokens for AppsyncLambdaMain { } if self.options.lambda_handler { self.appsync_event_handler(tokens); + self.lambda_function_handler(tokens); self.lambda_main(tokens); } } diff --git a/lambda-appsync/Cargo.toml b/lambda-appsync/Cargo.toml index 3641014..3a0f048 100644 --- a/lambda-appsync/Cargo.toml +++ b/lambda-appsync/Cargo.toml @@ -13,7 +13,7 @@ keywords.workspace = true categories.workspace = true [dependencies] -lambda-appsync-proc = { path = "../lambda-appsync-proc" } +lambda-appsync-proc = { path = "../lambda-appsync-proc", default-features = false } tokio = { workspace = true } lambda_runtime = { workspace = true } aws-config = { workspace = true } @@ -36,5 +36,5 @@ aws-sdk-dynamodb = { workspace = true } [features] default = ["env_logger"] -env_logger = ["dep:log", "dep:env_logger"] -tracing = ["dep:tracing", "dep:tracing-subscriber"] +env_logger = ["dep:log", "dep:env_logger", "lambda-appsync-proc/env_logger"] +tracing = ["dep:tracing", "dep:tracing-subscriber", "lambda-appsync-proc/tracing"] From b9dd6d87157d410abb8bbf8221751556986c9987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 15 Dec 2025 17:32:44 +0100 Subject: [PATCH 06/14] Trying to improve the tracing experience --- .../src/appsync_lambda_main/mod.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index e34f69d..fc0bfaf 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -312,11 +312,6 @@ impl Parse for AppsyncLambdaMain { impl AppsyncLambdaMain { fn appsync_event_handler(&self, tokens: &mut TokenStream2) { - #[cfg(feature = "tracing")] - tokens.extend(quote! { - #[::lambda_appsync::tracing::instrument(fields(operation = ?event.info.operation))] - }); - #[allow(unused_mut)] let mut log_lines = proc_macro2::TokenStream::new(); #[cfg(feature = "env_logger")] @@ -324,6 +319,10 @@ impl AppsyncLambdaMain { ::lambda_appsync::log::info!("event={event:?}"); ::lambda_appsync::log::info!("operation={:?}", event.info.operation); }); + #[cfg(feature = "tracing")] + log_lines.extend(quote! { + ::lambda_appsync::tracing::info!("event={event:?}"); + }); let call_hook = if let Some(ref hook) = self.options.hook { quote_spanned! {hook.span()=> @@ -347,6 +346,11 @@ impl AppsyncLambdaMain { quote! {} }; + #[cfg(feature = "tracing")] + tokens.extend(quote! { + #[::lambda_appsync::tracing::instrument(skip(event), fields(operation = ?event.info.operation))] + }); + tokens.extend(quote! { async fn appsync_handler(event: ::lambda_appsync::AppsyncEvent) -> ::lambda_appsync::AppsyncResponse { #log_lines @@ -437,6 +441,11 @@ impl AppsyncLambdaMain { ::lambda_appsync::log::debug!("{event:?}"); ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); }); + #[cfg(feature = "tracing")] + log_lines.extend(quote! { + ::lambda_appsync::tracing::debug!("{event:?}"); + ::lambda_appsync::tracing::info!({payload = %::lambda_appsync::serde_json::json!(event.payload)}); + }); tokens.extend(quote! { async fn function_handler( From d9f30c3ddbfcfc0ec53e7cce9d684d57a8582c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 15 Dec 2025 17:59:08 +0100 Subject: [PATCH 07/14] Re-enable time --- lambda-appsync-proc/src/appsync_lambda_main/mod.rs | 2 -- lambda-appsync-proc/src/lib.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index fc0bfaf..9b7d864 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -408,8 +408,6 @@ impl AppsyncLambdaMain { // this needs to be set to false, otherwise ANSI color codes will // show up in a confusing manner in CloudWatch logs. .with_ansi(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() // remove the name of the function from every log entry .with_target(false) .init(); diff --git a/lambda-appsync-proc/src/lib.rs b/lambda-appsync-proc/src/lib.rs index 78a98f5..8506300 100644 --- a/lambda-appsync-proc/src/lib.rs +++ b/lambda-appsync-proc/src/lib.rs @@ -257,8 +257,6 @@ use proc_macro::TokenStream; /// // this needs to be set to false, otherwise ANSI color codes will /// // show up in a confusing manner in CloudWatch logs. /// .with_ansi(false) -/// // disabling time is handy because CloudWatch will add the ingestion time. -/// .without_time() /// // remove the name of the function from every log entry /// .with_target(false) /// .init(); From e6b1424280db6287307b97734f59ddd73395bf9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Tue, 16 Dec 2025 09:56:34 +0100 Subject: [PATCH 08/14] Fix: Both feature can be enabled at the same time, but I still have to make sure only one initialization code runs to avoid a panic --- lambda-appsync-proc/src/appsync_lambda_main/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index 9b7d864..8440bdb 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -476,8 +476,12 @@ impl AppsyncLambdaMain { let mut default_log_init = proc_macro2::TokenStream::new(); #[cfg(feature = "env_logger")] default_log_init.extend(Self::default_env_logger_init()); - #[cfg(feature = "tracing")] + // The code initializing tracing fails if the env_logger initialization already happened + #[cfg(all(feature = "tracing", not(any(feature = "env_logger"))))] default_log_init.extend(Self::default_tracing_init()); + // Future default inits can be inserted here like that for feature "fastrace" (for example): + // #[cfg(all(feature = "fastrace", not(any(feature = "env_logger", feature = "tracing"))))] + // default_log_init.extend(Self::default_fastrace_init()); default_log_init }; From e9d37eec4a6308deed7b1b2cc02099724bfcb7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Tue, 16 Dec 2025 10:15:35 +0100 Subject: [PATCH 09/14] Feat: Creating an independant feature flag for 'log' and making it enabled for both env_logger and tracing feature. This feature flag controls weither or not the generated code uses log::info!(...) and log::debug!(...) statements in the 'function_handler' and the 'appsync_handler'. --- lambda-appsync-proc/Cargo.toml | 5 +++-- .../src/appsync_lambda_main/graphql.rs | 6 +----- .../src/appsync_lambda_main/mod.rs | 21 +++++++++---------- lambda-appsync-proc/src/lib.rs | 4 ++-- lambda-appsync/Cargo.toml | 5 +++-- lambda-appsync/src/lib.rs | 5 +++-- 6 files changed, 22 insertions(+), 24 deletions(-) diff --git a/lambda-appsync-proc/Cargo.toml b/lambda-appsync-proc/Cargo.toml index d56f2fc..6889ac1 100644 --- a/lambda-appsync-proc/Cargo.toml +++ b/lambda-appsync-proc/Cargo.toml @@ -21,7 +21,7 @@ proc-macro2 = { workspace = true } graphql-parser = { workspace = true } [dev-dependencies] -lambda-appsync = { path = "../lambda-appsync", default-features = false, features = ["env_logger", "tracing"] } +lambda-appsync = { path = "../lambda-appsync", default-features = false, features = ["log", "env_logger", "tracing"] } tracing = { workspace = true } aws-sdk-s3 = { workspace = true } aws-sdk-dynamodb = { workspace = true } @@ -29,6 +29,7 @@ serde = { workspace = true } trybuild = { workspace = true } [features] -default = ["env_logger", "tracing"] +default = ["log", "env_logger", "tracing"] +log = [] env_logger = [] tracing = [] diff --git a/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs b/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs index dc0f94a..29c8bb0 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/graphql.rs @@ -976,14 +976,10 @@ impl GraphQLSchema { #[allow(unused_mut)] let mut log_lines = proc_macro2::TokenStream::new(); - #[cfg(feature = "env_logger")] + #[cfg(feature = "log")] log_lines.extend(quote_spanned! {span=> ::lambda_appsync::log::error!("{e}"); }); - #[cfg(feature = "tracing")] - log_lines.extend(quote_spanned! {span=> - ::lambda_appsync::tracing::error!("{e}"); - }); tokens.extend(quote_spanned! {span=> impl Operation { diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index 8440bdb..a3c7728 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -70,6 +70,7 @@ enum OptionalParameter { ExcludeAppsyncOperations(bool), OnlyAppsyncOperations(bool), Hook(Ident), + #[cfg(feature = "log")] LogInit(Ident), TypeOverride(TypeOverride), NameOverride(NameOverride), @@ -95,6 +96,7 @@ impl Parse for OptionalParameter { input.parse::()?.value(), )), "hook" => Ok(Self::Hook(input.parse()?)), + #[cfg(feature = "log")] "log_init" => Ok(Self::LogInit(input.parse()?)), "type_override" => Ok(Self::TypeOverride(input.parse()?)), "name_override" => Ok(Self::NameOverride(input.parse()?)), @@ -150,6 +152,7 @@ struct OptionalParameters { appsync_operations: bool, lambda_handler: bool, hook: Option, + #[cfg(feature = "log")] log_init: Option, tos: TypeOverrides, nos: NameOverrides, @@ -162,6 +165,7 @@ impl Default for OptionalParameters { appsync_operations: true, lambda_handler: true, hook: None, + #[cfg(feature = "log")] log_init: None, tos: TypeOverrides::new(), nos: NameOverrides::new(), @@ -193,6 +197,7 @@ impl OptionalParameters { OptionalParameter::Hook(ident) => { self.hook.replace(ident); } + #[cfg(feature = "log")] OptionalParameter::LogInit(ident) => { self.log_init.replace(ident); } @@ -314,15 +319,11 @@ impl AppsyncLambdaMain { fn appsync_event_handler(&self, tokens: &mut TokenStream2) { #[allow(unused_mut)] let mut log_lines = proc_macro2::TokenStream::new(); - #[cfg(feature = "env_logger")] + #[cfg(feature = "log")] log_lines.extend(quote! { ::lambda_appsync::log::info!("event={event:?}"); ::lambda_appsync::log::info!("operation={:?}", event.info.operation); }); - #[cfg(feature = "tracing")] - log_lines.extend(quote! { - ::lambda_appsync::tracing::info!("event={event:?}"); - }); let call_hook = if let Some(ref hook) = self.options.hook { quote_spanned! {hook.span()=> @@ -434,16 +435,11 @@ impl AppsyncLambdaMain { #[allow(unused_mut)] let mut log_lines = proc_macro2::TokenStream::new(); - #[cfg(feature = "env_logger")] + #[cfg(feature = "log")] log_lines.extend(quote! { ::lambda_appsync::log::debug!("{event:?}"); ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); }); - #[cfg(feature = "tracing")] - log_lines.extend(quote! { - ::lambda_appsync::tracing::debug!("{event:?}"); - ::lambda_appsync::tracing::info!({payload = %::lambda_appsync::serde_json::json!(event.payload)}); - }); tokens.extend(quote! { async fn function_handler( @@ -463,6 +459,7 @@ impl AppsyncLambdaMain { }; let aws_client_getters = self.aws_clients.iter().map(|ac| ac.aws_client_getter()); + #[cfg(feature = "log")] let log_init = if let Some(ref log_init) = self.options.log_init { quote_spanned! {log_init.span()=> mod _check_sig { @@ -484,6 +481,8 @@ impl AppsyncLambdaMain { // default_log_init.extend(Self::default_fastrace_init()); default_log_init }; + #[cfg(not(feature = "log"))] + let log_init = TokenStream2::new(); #[allow(unused_mut)] let mut bing_in_scope = TokenStream2::new(); diff --git a/lambda-appsync-proc/src/lib.rs b/lambda-appsync-proc/src/lib.rs index 8506300..b579c5c 100644 --- a/lambda-appsync-proc/src/lib.rs +++ b/lambda-appsync-proc/src/lib.rs @@ -29,7 +29,7 @@ use proc_macro::TokenStream; /// /// - `batch = bool`: Enable/disable batch request handling (default: true) /// - `hook = fn_name`: Add a custom hook function for request validation/auth -/// - `log_init = fn_name`: Use a custom log initialization function instead of the default one +/// - (feature: `log`) `log_init = fn_name`: Use a custom log initialization function instead of the default one /// - `exclude_lambda_handler = bool`: Skip generation of Lambda handler code /// - `only_lambda_handler = bool`: Only generate Lambda handler code /// - `exclude_appsync_types = bool`: Skip generation of GraphQL type definitions @@ -239,7 +239,7 @@ use proc_macro::TokenStream; /// /// ### Feature `tracing` /// -/// Alternatively, you can use the `tracing` feature so `lambda_appsync` exposes and uses `tracing` and `tracing-subscriber` +/// Alternatively, you can use the `tracing` feature so `lambda_appsync` exposes and uses `log`, `tracing` and `tracing-subscriber` /// /// ```no_run /// # mod sub { diff --git a/lambda-appsync/Cargo.toml b/lambda-appsync/Cargo.toml index 3a0f048..52b287d 100644 --- a/lambda-appsync/Cargo.toml +++ b/lambda-appsync/Cargo.toml @@ -36,5 +36,6 @@ aws-sdk-dynamodb = { workspace = true } [features] default = ["env_logger"] -env_logger = ["dep:log", "dep:env_logger", "lambda-appsync-proc/env_logger"] -tracing = ["dep:tracing", "dep:tracing-subscriber", "lambda-appsync-proc/tracing"] +env_logger = ["log", "dep:env_logger", "lambda-appsync-proc/env_logger"] +tracing = ["log", "dep:tracing", "dep:tracing-subscriber", "lambda-appsync-proc/tracing"] +log = ["dep:log", "lambda-appsync-proc/log"] diff --git a/lambda-appsync/src/lib.rs b/lambda-appsync/src/lib.rs index 708f537..b754279 100644 --- a/lambda-appsync/src/lib.rs +++ b/lambda-appsync/src/lib.rs @@ -108,10 +108,11 @@ pub use serde; pub use serde_json; pub use tokio; +#[cfg(feature = "log")] +pub use log; + #[cfg(feature = "env_logger")] pub use env_logger; -#[cfg(feature = "env_logger")] -pub use log; #[cfg(feature = "tracing")] pub use tracing; From d3209c4efe8baa3a9ed9ae070dc590912621c43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Tue, 16 Dec 2025 10:26:05 +0100 Subject: [PATCH 10/14] Feat: Added and documented an optional 'event_logging' parameter to the macro to control weither or not the Lambda event is dumped in the log or not (default to true) --- .../src/appsync_lambda_main/mod.rs | 29 +++++++++++++++---- lambda-appsync-proc/src/lib.rs | 1 + 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index a3c7728..f687961 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -72,6 +72,8 @@ enum OptionalParameter { Hook(Ident), #[cfg(feature = "log")] LogInit(Ident), + #[cfg(feature = "log")] + EventLogging(bool), TypeOverride(TypeOverride), NameOverride(NameOverride), } @@ -98,6 +100,8 @@ impl Parse for OptionalParameter { "hook" => Ok(Self::Hook(input.parse()?)), #[cfg(feature = "log")] "log_init" => Ok(Self::LogInit(input.parse()?)), + #[cfg(feature = "log")] + "event_logging" => Ok(Self::EventLogging(input.parse::()?.value())), "type_override" => Ok(Self::TypeOverride(input.parse()?)), "name_override" => Ok(Self::NameOverride(input.parse()?)), // Deprecated options @@ -154,6 +158,8 @@ struct OptionalParameters { hook: Option, #[cfg(feature = "log")] log_init: Option, + #[cfg(feature = "log")] + event_logging: bool, tos: TypeOverrides, nos: NameOverrides, } @@ -167,6 +173,8 @@ impl Default for OptionalParameters { hook: None, #[cfg(feature = "log")] log_init: None, + #[cfg(feature = "log")] + event_logging: true, tos: TypeOverrides::new(), nos: NameOverrides::new(), } @@ -201,6 +209,10 @@ impl OptionalParameters { OptionalParameter::LogInit(ident) => { self.log_init.replace(ident); } + #[cfg(feature = "log")] + OptionalParameter::EventLogging(b) => { + self.event_logging = b; + } OptionalParameter::TypeOverride(to) => { // Retrieve the entry corresponding to `Type.field` let to_field_entry = self @@ -320,8 +332,13 @@ impl AppsyncLambdaMain { #[allow(unused_mut)] let mut log_lines = proc_macro2::TokenStream::new(); #[cfg(feature = "log")] + if self.options.event_logging { + log_lines.extend(quote! { + ::lambda_appsync::log::info!("event={event:?}"); + }); + } + #[cfg(feature = "log")] log_lines.extend(quote! { - ::lambda_appsync::log::info!("event={event:?}"); ::lambda_appsync::log::info!("operation={:?}", event.info.operation); }); @@ -436,10 +453,12 @@ impl AppsyncLambdaMain { #[allow(unused_mut)] let mut log_lines = proc_macro2::TokenStream::new(); #[cfg(feature = "log")] - log_lines.extend(quote! { - ::lambda_appsync::log::debug!("{event:?}"); - ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); - }); + if self.options.event_logging { + log_lines.extend(quote! { + ::lambda_appsync::log::debug!("{event:?}"); + ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); + }); + } tokens.extend(quote! { async fn function_handler( diff --git a/lambda-appsync-proc/src/lib.rs b/lambda-appsync-proc/src/lib.rs index b579c5c..12187cb 100644 --- a/lambda-appsync-proc/src/lib.rs +++ b/lambda-appsync-proc/src/lib.rs @@ -30,6 +30,7 @@ use proc_macro::TokenStream; /// - `batch = bool`: Enable/disable batch request handling (default: true) /// - `hook = fn_name`: Add a custom hook function for request validation/auth /// - (feature: `log`) `log_init = fn_name`: Use a custom log initialization function instead of the default one +/// - (feature: `log`) `event_logging = bool`: If true, the macro will generate code to dump the entire event in the logs (default: `true`) /// - `exclude_lambda_handler = bool`: Skip generation of Lambda handler code /// - `only_lambda_handler = bool`: Only generate Lambda handler code /// - `exclude_appsync_types = bool`: Skip generation of GraphQL type definitions From 3a342a305a425a7c8c0cf7112df38eae1a0b6808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Tue, 16 Dec 2025 10:38:17 +0100 Subject: [PATCH 11/14] Fix: Typo in variable name --- lambda-appsync-proc/src/appsync_lambda_main/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index f687961..4af1f9c 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -504,12 +504,12 @@ impl AppsyncLambdaMain { let log_init = TokenStream2::new(); #[allow(unused_mut)] - let mut bing_in_scope = TokenStream2::new(); - bing_in_scope.extend(quote! { + let mut bring_in_scope = TokenStream2::new(); + bring_in_scope.extend(quote! { use ::lambda_appsync::tokio; }); #[cfg(feature = "tracing")] - bing_in_scope.extend(quote! { + bring_in_scope.extend(quote! { use ::lambda_appsync::tracing; }); @@ -519,7 +519,7 @@ impl AppsyncLambdaMain { #(#aws_client_getters)* - #bing_in_scope + #bring_in_scope #[tokio::main] async fn main() -> ::core::result::Result<(), ::lambda_appsync::lambda_runtime::Error> { From 1cdfabbe8cf27b22306a012aca41ad642c469d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Tue, 16 Dec 2025 11:01:14 +0100 Subject: [PATCH 12/14] Adding a compilation test using the new event_logging flag --- lambda-appsync-proc/tests/pass/event_logging_option.rs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 lambda-appsync-proc/tests/pass/event_logging_option.rs diff --git a/lambda-appsync-proc/tests/pass/event_logging_option.rs b/lambda-appsync-proc/tests/pass/event_logging_option.rs new file mode 100644 index 0000000..1283785 --- /dev/null +++ b/lambda-appsync-proc/tests/pass/event_logging_option.rs @@ -0,0 +1,6 @@ +mod no_run { + // Test with event_logging disabled + lambda_appsync::appsync_lambda_main!("../../../../schema.graphql", event_logging = false); +} + +fn main() {} From f5f4c0f780cb7c2e060896d7056e949276d7437e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Wed, 7 Jan 2026 19:53:33 +0100 Subject: [PATCH 13/14] Fix: Making the log_init option available even if the log feature is not enabled --- lambda-appsync-proc/src/appsync_lambda_main/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index 4af1f9c..bb2beab 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -70,7 +70,6 @@ enum OptionalParameter { ExcludeAppsyncOperations(bool), OnlyAppsyncOperations(bool), Hook(Ident), - #[cfg(feature = "log")] LogInit(Ident), #[cfg(feature = "log")] EventLogging(bool), @@ -98,7 +97,6 @@ impl Parse for OptionalParameter { input.parse::()?.value(), )), "hook" => Ok(Self::Hook(input.parse()?)), - #[cfg(feature = "log")] "log_init" => Ok(Self::LogInit(input.parse()?)), #[cfg(feature = "log")] "event_logging" => Ok(Self::EventLogging(input.parse::()?.value())), @@ -156,7 +154,6 @@ struct OptionalParameters { appsync_operations: bool, lambda_handler: bool, hook: Option, - #[cfg(feature = "log")] log_init: Option, #[cfg(feature = "log")] event_logging: bool, @@ -171,7 +168,6 @@ impl Default for OptionalParameters { appsync_operations: true, lambda_handler: true, hook: None, - #[cfg(feature = "log")] log_init: None, #[cfg(feature = "log")] event_logging: true, @@ -205,7 +201,6 @@ impl OptionalParameters { OptionalParameter::Hook(ident) => { self.hook.replace(ident); } - #[cfg(feature = "log")] OptionalParameter::LogInit(ident) => { self.log_init.replace(ident); } @@ -478,7 +473,6 @@ impl AppsyncLambdaMain { }; let aws_client_getters = self.aws_clients.iter().map(|ac| ac.aws_client_getter()); - #[cfg(feature = "log")] let log_init = if let Some(ref log_init) = self.options.log_init { quote_spanned! {log_init.span()=> mod _check_sig { @@ -500,8 +494,6 @@ impl AppsyncLambdaMain { // default_log_init.extend(Self::default_fastrace_init()); default_log_init }; - #[cfg(not(feature = "log"))] - let log_init = TokenStream2::new(); #[allow(unused_mut)] let mut bring_in_scope = TokenStream2::new(); From 252e222f3b9df99749994ec4df7c903c017937ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Wed, 7 Jan 2026 21:37:40 +0100 Subject: [PATCH 14/14] Fix: Changing the default event_logging value to 'false' and ensuring that, if true, it logs at debug level instead of info. --- lambda-appsync-proc/src/appsync_lambda_main/mod.rs | 7 +++---- lambda-appsync-proc/src/lib.rs | 5 +++-- lambda-appsync-proc/tests/pass/event_logging_option.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs index bb2beab..0958438 100644 --- a/lambda-appsync-proc/src/appsync_lambda_main/mod.rs +++ b/lambda-appsync-proc/src/appsync_lambda_main/mod.rs @@ -170,7 +170,7 @@ impl Default for OptionalParameters { hook: None, log_init: None, #[cfg(feature = "log")] - event_logging: true, + event_logging: false, tos: TypeOverrides::new(), nos: NameOverrides::new(), } @@ -329,7 +329,7 @@ impl AppsyncLambdaMain { #[cfg(feature = "log")] if self.options.event_logging { log_lines.extend(quote! { - ::lambda_appsync::log::info!("event={event:?}"); + ::lambda_appsync::log::debug!("event={event:?}"); }); } #[cfg(feature = "log")] @@ -450,8 +450,7 @@ impl AppsyncLambdaMain { #[cfg(feature = "log")] if self.options.event_logging { log_lines.extend(quote! { - ::lambda_appsync::log::debug!("{event:?}"); - ::lambda_appsync::log::info!("{}", ::lambda_appsync::serde_json::json!(event.payload)); + ::lambda_appsync::log::debug!("{}", ::lambda_appsync::serde_json::json!(event.payload)); }); } diff --git a/lambda-appsync-proc/src/lib.rs b/lambda-appsync-proc/src/lib.rs index 12187cb..75a113d 100644 --- a/lambda-appsync-proc/src/lib.rs +++ b/lambda-appsync-proc/src/lib.rs @@ -29,8 +29,9 @@ use proc_macro::TokenStream; /// /// - `batch = bool`: Enable/disable batch request handling (default: true) /// - `hook = fn_name`: Add a custom hook function for request validation/auth -/// - (feature: `log`) `log_init = fn_name`: Use a custom log initialization function instead of the default one -/// - (feature: `log`) `event_logging = bool`: If true, the macro will generate code to dump the entire event in the logs (default: `true`) +/// - `log_init = fn_name`: Use a custom log initialization function instead of the default one +/// - (feature: `log`) `event_logging = bool`: If true, the macro will generate code to dump the +/// lambda payload JSON as well as parsed `AppsyncEvent`s in the logs at debug level (default: `false`) /// - `exclude_lambda_handler = bool`: Skip generation of Lambda handler code /// - `only_lambda_handler = bool`: Only generate Lambda handler code /// - `exclude_appsync_types = bool`: Skip generation of GraphQL type definitions diff --git a/lambda-appsync-proc/tests/pass/event_logging_option.rs b/lambda-appsync-proc/tests/pass/event_logging_option.rs index 1283785..86ab463 100644 --- a/lambda-appsync-proc/tests/pass/event_logging_option.rs +++ b/lambda-appsync-proc/tests/pass/event_logging_option.rs @@ -1,6 +1,6 @@ mod no_run { - // Test with event_logging disabled - lambda_appsync::appsync_lambda_main!("../../../../schema.graphql", event_logging = false); + // Test with event_logging enabled + lambda_appsync::appsync_lambda_main!("../../../../schema.graphql", event_logging = true); } fn main() {}