DEV Community

Cover image for Rust Lambda Hello World Logging
James Miller
James Miller

Posted on • Originally published at millerjam.github.io on

Rust Lambda Hello World Logging

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();
}
Enter fullscreen mode Exit fullscreen mode

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 prefix tracing_subscriber::....

Lets go ahead and add the 2 dependencies: tracing, and tracing-subscriber

cargo add tracing

cargo add tracing-subscriber
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

We will also need to import

use tracing::info;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Update the existing function

aws lambda update-function-code --function-name rustTest \
  --zip-file fileb://./lambda.zip
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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   
Enter fullscreen mode Exit fullscreen mode

Success! We now have our log,

INFO lambda_test: going to say hello to James
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

We replaced the hardcoded set_max_level... with

.with_env_filter(EnvFilter::from_default_env())
Enter fullscreen mode Exit fullscreen mode

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"]}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Update the existing function

aws lambda update-function-code --function-name rustTest \
  --zip-file fileb://./lambda.zip
Enter fullscreen mode Exit fullscreen mode

Update the existing function configuration

aws lambda update-function-configuration \
    --function-name  rustTest \
    --environment Variables="{RUST_BACKTRACE=1,RUST_LOG=debug}"
Enter fullscreen mode Exit fullscreen mode

Test invoke again

aws lambda invoke \
  --cli-binary-format raw-in-base64-out \
  --function-name rustTest \
  --payload '{"firstName": "James"}' \
  output.json
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)