In my previous article, I started a tutorial on creating a lambda in Rust. If you missed it, you can catch up on that article here -> Rust Lambda Hello World
Lets add some logging
Before we go much further with our Rust code, let's add some logging. We are take advantage of the tracing library which is already integrated into many of the lambda dependencies.
Details about the tracing library are out of scope for this article, but you can learn more from the tracing crate docs
Init the logger
Setting up the logger requires initializing the tracing subscriber library, the AWS Lambda Rust Runtime Examples has a examples with some details about how to get started.
Here is the code that we will have to add:
fn init_lambda_tracing() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
// 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()
.init();
}
I have created a small function init_lambda_tracing()
with all the details for setting up the logging. This function will setup the default tracing_subscriber
which prints logs to stdout. The additional methods called on the builder, set the default logging level to "info" and disable some of the defaults to make things nicer in the Lambda environment. Finally, I added additional code to the main()
to call this function before the lambda runtime setup.
Fix the compile issues
Even though we did not add any additional import ...
lines, we still need to add our new tracing library dependencies to Cargo.toml
, before our code will build.
The reason we did not add
import..
statements is that we fully qualified the crate directly in the code with the prefixtracing_subscriber::...
.
Lets go ahead and add the 2 dependencies: tracing, and tracing-subscriber
cargo add tracing
cargo add tracing-subscriber
Lets test it!
Ok now that we have logging setup, let's add some logs and try it. The tracing library provides logging macros such as, debug!
, info!
, warn!
, error!
We will add an new "info" log to the func
method.
info!("Going to say hello to {}!", first_name);
We will also need to import
use tracing::info;
Now we can update the function and test.
Compile & update the function
Just like in the previous article, we need to compile for the lambda env and update the function.
Compile
cargo zigbuild --release --target x86_64-unknown-linux-gnu
Update the existing function
aws lambda update-function-code --function-name rustTest \
--zip-file fileb://./lambda.zip
Test invoke & check the logs
Now that the lambda is update with the new code, we can go ahead and test and check for our new logs.
Invoke
aws lambda invoke \
--cli-binary-format raw-in-base64-out \
--function-name rustTest \
--payload '{"firstName": "James"}' \
output.json
Now check the cloudwatch logs again
START RequestId: 93bcbe8c-4b2e-4af3-b00d-3b4b27083ebe Version: $LATEST
INFO lambda_test: going to say hello to James
END RequestId: 93bcbe8c-4b2e-4af3-b00d-3b4b27083ebe
REPORT RequestId: 93bcbe8c-4b2e-4af3-b00d-3b4b27083ebe Duration: 1.16 ms Billed Duration: 33 ms Memory Size: 128 MB Max Memory Used: 17 MB Init Duration: 31.20 ms
XRAY TraceId: 1-622553a3-5644934610a4952626f10403 SegmentId: 3b7b79993cba7894 Sampled: true
Success! We now have our log,
INFO lambda_test: going to say hello to James
Reconfigure the logger
Update the code for dynamic log levels
Now that we have logging working, let's reconfigure it so that it reads the log level
from an environment variable. This is will be much more flexible and give us the ability to turn logging levels up and down while we are developing and debugging, without having to recompile the code.
We can update the init_lambda_tracing
like this:
fn init_lambda_tracing() {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
// 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()
.init();
}
We replaced the hardcoded set_max_level...
with
.with_env_filter(EnvFilter::from_default_env())
this will read the logging level from the
environment variable RUST_LOG
. Now we can change the log level dynamically without having the recompile and update the running code.
We will also have to update the Cargo.toml
to make sure the "EnvFilter" feature is available from the tracing_subscriber
library.
tracing-subscriber = {version = "0.3.9", features = ["env-filter"]}
Compile & update the function configuration
Now that code is update, we need to rebuild, update the function, and then also the config
to add the new "RUST_LOG"
environment variable. Let's set the logging level to "DEBUG" for testing.
Compile
cargo zigbuild --release --target x86_64-unknown-linux-gnu
Update the existing function
aws lambda update-function-code --function-name rustTest \
--zip-file fileb://./lambda.zip
Update the existing function configuration
aws lambda update-function-configuration \
--function-name rustTest \
--environment Variables="{RUST_BACKTRACE=1,RUST_LOG=debug}"
Test invoke again
aws lambda invoke \
--cli-binary-format raw-in-base64-out \
--function-name rustTest \
--payload '{"firstName": "James"}' \
output.json
Check the cloudwatch logs again
Logs are much more verbose, we can see lots of new logs from the included dependencies.
START RequestId: 9ebad5dc-3cee-4302-a0f8-0e592e432f41 Version: $LATEST
DEBUG hyper::client::connect::http: connecting to 127.0.0.1:9001
DEBUG hyper::client::connect::http: connected to 127.0.0.1:9001
DEBUG hyper::proto::h1::io: flushed 109 bytes
DEBUG hyper::proto::h1::io: parsed 7 headers
DEBUG hyper::proto::h1::conn: incoming body is content-length (21 bytes)
DEBUG hyper::proto::h1::conn: incoming body completed
DEBUG hyper::client::pool: pooling idle connection for ("http", 127.0.0.1:9001)
INFO lambda_test: going to say hello to James
DEBUG hyper::client::pool: reuse idle connection for ("http", 127.0.0.1:9001)
DEBUG hyper::proto::h1::io: flushed 198 bytes
DEBUG hyper::proto::h1::io: parsed 3 headers
DEBUG hyper::proto::h1::conn: incoming body is content-length (16 bytes)
DEBUG hyper::client::connect::http: connecting to 127.0.0.1:9001
DEBUG hyper::proto::h1::conn: incoming body completed
DEBUG hyper::client::pool: reuse idle connection for ("http", 127.0.0.1:9001)
DEBUG hyper::proto::h1::io: flushed 109 bytes
END RequestId: 9ebad5dc-3cee-4302-a0f8-0e592e432f41
REPORT RequestId: 9ebad5dc-3cee-4302-a0f8-0e592e432f41 Duration: 1.46 ms Billed Duration: 38 ms Memory Size: 128 MB Max Memory Used: 17 MB Init Duration: 36.11 ms
XRAY TraceId: 1-622551be-6d58480b496655162937c59c SegmentId: 03a6c5576dc28250 Sampled: true
DEBUG hyper::client::connect::http: connected to 127.0.0.1:9001
DEBUG hyper::client::pool: pooling idle connection for ("http", 127.0.0.1:9001)
Conclusions
We were able to successfully add logging to our "hello world" lambda, and make it possible to dynamically change the logging level based on an environment variable. Using the tracing library made this easy, and the code is straight forward.
Finally the Source Code
The complete project is here in my github repo https://github.com/millerjam/rust_lambda_hello_world
Next Time
Next time, we will look at adding a unit test and finally try switching from building x86 to ARM for even more savings on our lambda executions.
Top comments (0)