{"id":2952,"date":"2024-09-24T20:36:40","date_gmt":"2024-09-25T01:36:40","guid":{"rendered":"https:\/\/badecho.com\/?p=2952"},"modified":"2024-09-24T20:36:47","modified_gmt":"2024-09-25T01:36:47","slug":"aws-lambda-json-logs","status":"publish","type":"post","link":"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/","title":{"rendered":"Simple JSON Logging with AWS Lambda Running .NET"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/SimpleJsonLogging.png\" alt=\"Simple JSON Logging with AWS Lambda Running .NET\" class=\"wp-image-2954\" width=\"856\" height=\"448\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/SimpleJsonLogging.png 856w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/SimpleJsonLogging-300x157.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/SimpleJsonLogging-768x402.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/SimpleJsonLogging-480x251.png 480w\" sizes=\"(max-width: 856px) 100vw, 856px\" \/><\/figure><\/div>\n\n\n\n<p>Greetings, dear readers. It&#8217;s been a minute since I last wrote, so I figured I&#8217;d break the silence with a brief article on a problem I was recently working on.<\/p>\n\n\n\n<p>I&#8217;ve been doing a lot of AWS cloud infrastructure design lately; in fact, I&#8217;ve had the pleasure of rearchitecting and implementing new infrastructure (this time following best practices) for an existing widely used product (whose cloud implementation was left unfinished).<\/p>\n\n\n\n<p>This product has several AWS Lambda functions running .NET that handle various REST API calls. One issue I had with the previous iteration of the product was its disgusting plaintext log entries being fed to CloudWatch by the Lambda functions.<\/p>\n\n\n\n<p>So, this article will go over a very simple and quick way to get your logs outputted in JSON, which looks much better and is easier to query than the plaintext mess you get by default.<\/p>\n\n\n\n<h2>How a Lambda Function is Logged<\/h2>\n\n\n\n<p>Assuming the Lambda function&#8217;s execution role allows for it, logs are automatically captured for all requests handled by your function and sent to CloudWatch.<\/p>\n\n\n\n<p>Anything emitted to a standard output stream will wind up in a log stream on CloudWatch. That&#8217;s it; there&#8217;s really no magic in it.<\/p>\n\n\n\n<p>How do these logs look?<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/GrossLogs-1024x273.png\" alt=\"A plain ol' blob of plaintext.\" class=\"wp-image-2959\" width=\"1024\" height=\"273\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/GrossLogs-1024x273.png 1024w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/GrossLogs-300x80.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/GrossLogs-768x205.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/GrossLogs-480x128.png 480w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/GrossLogs.png 1188w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption>A dreary, gray blob of plaintext junk.<\/figcaption><\/figure><\/div>\n\n\n\n<p>Eww. The above entries are pretty spartan; things begin to get painful when we need to dig through more complicated transactions.<\/p>\n\n\n\n<h2>Never Fear! JSON Is Here<\/h2>\n\n\n\n<p>Thankfully, AWS gives us a gift from above: the ability to change a Lambda function&#8217;s log format.<\/p>\n\n\n\n<p>If using the AWS console, from the  Lambda function, go to <strong>Configuration<\/strong> -&gt; <strong>Monitoring and operations tools<\/strong> and then hit <strong>Edit<\/strong> in the <strong>Logging configuration<\/strong> box.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/EditLambdaLogging.png\" alt=\"Here we can change the log format of our Lambda function.\" class=\"wp-image-2960\" width=\"842\" height=\"295\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/EditLambdaLogging.png 842w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/EditLambdaLogging-300x105.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/EditLambdaLogging-768x269.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/EditLambdaLogging-480x168.png 480w\" sizes=\"(max-width: 842px) 100vw, 842px\" \/><figcaption>Here we can change the log format of our Lambda function.<\/figcaption><\/figure><\/div>\n\n\n\n<p>Or, if you&#8217;re like me and use Terraform for pretty much everything, you can add a <code>logging_config<\/code> block to your <code>aws_lambda_function<\/code> resource:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>resource \"aws_lambda_function\" \"foo\" {\n  function_name = \"foo\"\n  ...\n  ...\n  logging_config {\n    log_format = \"JSON\"\n  }\n}<\/code><\/pre>\n\n\n\n<p>Doing the above will cause all system logs to be captured in JSON structured format. System logs include messages such as the Lambda function&#8217;s container spinning up and the usage report at the end of execution.<\/p>\n\n\n\n<p>On the other hand, application logs (i.e., the messages generated by the function&#8217;s code) will only be captured in JSON structured format out of the box for a select few runtimes, namely Python, Node.js, and Java.<\/p>\n\n\n\n<p><em><em><em>This is accurate at the time of writing; I expect support for additional runtimes to be added as time goes on.<\/em><\/em><\/em><\/p>\n\n\n\n<p>So that&#8217;s a big stinker; if you&#8217;re using Amazon&#8217;s &#8220;recommended&#8221; way of logging with .NET (<code>logging.AddLambdaLogger()<\/code>), your logs will continue to be plaintext.<\/p>\n\n\n\n<h2>.NET Has Lambda JSON Support&#8230;Sort Of<\/h2>\n\n\n\n<p>The folks from Amazon recently <a href=\"https:\/\/github.com\/aws\/aws-lambda-dotnet\/issues\/1747\" target=\"_blank\" rel=\"noreferrer noopener\">created a PR that seems to add JSON structured support<\/a> for Lambda functions using the .NET runtime.<\/p>\n\n\n\n<p>And indeed, it probably does just that! However, after looking at it a bit closer, it appears that the only logger granted this support is the one provided by the <code>ILambdaContext<\/code> passed into the Lambda function&#8217;s handler.<\/p>\n\n\n\n<p>This is something you&#8217;re more likely to use if your Lambda function is just a simple routine; this won&#8217;t help you if your Lambda function is hosting a lightweight ASP.NET API that relies on <code>ILogger<\/code>, which the logger exposed by <code>ILambdaContext<\/code> does not implement.<\/p>\n\n\n\n<h2>What Does the Lambda ILogger Even Do?<\/h2>\n\n\n\n<p>Amazon provides the <code>ILoggingBuilder.AddLambdaLogger<\/code> extension method with their <code>Amazon.Lambda.Logging.AspNetCore<\/code> package, recommending it as the &#8220;way to lay down them logs&#8221; (my words, not theirs) when running a .NET host.<\/p>\n\n\n\n<p>While looking into what Amazon&#8217;s Lambda <code>ILogger<\/code> implementation does exactly and if the changes to <code>ILambdaContext<\/code>&#8216;s logger affect it, I found that, in the end\u2026it really doesn&#8217;t do that much.<\/p>\n\n\n\n<p>I&#8217;m sure it has some fun bells and whistles that tie into Lambda functionality, but literally, here&#8217;s what logging with the Lambda logger boils down to:<\/p>\n\n\n\n<h6>LambaLogger.cs<\/h6>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic static class LambdaLogger\n{\n    private static Action&lt;string&gt; _loggingAction = new Action&lt;string&gt;(LambdaLogger.LogToConsole);\n\n    private static void LogToConsole(string message) =&gt; Console.WriteLine(message);\n\n    public static void Log(string message) =&gt; LambdaLogger._loggingAction(message);\n}\n<\/pre>\n\n\n<p>All of the Lambda <code>ILogger<\/code> implementation&#8217;s logging methods call this static class, resulting in everything just ending up\u2026being written to the console.<\/p>\n\n\n\n<h2>So&#8230;Just Use a JSON Console<\/h2>\n\n\n\n<p>Take the line of code that says <code>logging.AddLambdaLogger()<\/code> in your function, throw it out the window, and just slap on a call to <code>AddJsonConsole()<\/code> instead.<\/p>\n\n\n\n<p>That&#8217;s it. This change and the configuration change to the Lambda function&#8217;s log format will give you purty JSON logs.<\/p>\n\n\n\n<h6>LambdaEntryPoint.cs<\/h6>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nbuilder.ConfigureLogging((logging =&gt;\n                         {\n                             \/\/ Any other logic you have here to account for\n                             \/\/ local developer sessions, etc....\n                             logging.ClearProviders();\n                             logging.AddJsonConsole();\n                         })\n<\/pre>\n\n\n<p>Throw in some scopes while you&#8217;re at it!<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/NiceLogs.png\" alt=\"Nice logs.\" class=\"wp-image-2961\" width=\"940\" height=\"381\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/NiceLogs.png 940w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/NiceLogs-300x122.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/NiceLogs-768x311.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/NiceLogs-480x195.png 480w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><figcaption>Oh, to gaze upon such comely logs in CloudWatch.<\/figcaption><\/figure><\/div>\n\n\n\n<h2>Probably Should Use Serilog<\/h2>\n\n\n\n<p>The stock JSON console and accompanying formatter provided by Microsoft do their job, but they are pretty limited in regards to customization, etc. If you want some more serious structured logging support, you&#8217;ll probably want to look into more powerful solutions like Serilog.<\/p>\n\n\n\n<p>Regardless, the quick bit of code provided above provides logs that check all my boxes, at least at the time of writing.<\/p>\n\n\n\n<p>Happy coding, travelers.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Greetings, dear readers. It&#8217;s been a minute since I last wrote, so I figured I&#8217;d break the silence with a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10],"tags":[41,86,42,87,88],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\r\n<title>Simple JSON Logging with AWS Lambda Running .NET - omni&#039;s hackpad<\/title>\r\n<meta name=\"description\" content=\"Demonstrates a quick and easy way to capture logs generated by AWS Lambda functions running .NET in JSON structured format.\" \/>\r\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\r\n<link rel=\"canonical\" href=\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/\" \/>\r\n<meta property=\"og:locale\" content=\"en_US\" \/>\r\n<meta property=\"og:type\" content=\"article\" \/>\r\n<meta property=\"og:title\" content=\"Simple JSON Logging with AWS Lambda Running .NET - omni&#039;s hackpad\" \/>\r\n<meta property=\"og:description\" content=\"Demonstrates a quick and easy way to capture logs generated by AWS Lambda functions running .NET in JSON structured format.\" \/>\r\n<meta property=\"og:url\" content=\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/\" \/>\r\n<meta property=\"og:site_name\" content=\"omni&#039;s hackpad\" \/>\r\n<meta property=\"article:published_time\" content=\"2024-09-25T01:36:40+00:00\" \/>\r\n<meta property=\"article:modified_time\" content=\"2024-09-25T01:36:47+00:00\" \/>\r\n<meta property=\"og:image\" content=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/SimpleJsonLogging.png\" \/>\r\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\r\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/badecho.com\/#website\",\"url\":\"https:\/\/badecho.com\/\",\"name\":\"omni&#039;s hackpad\",\"description\":\"Game Code Disassembly. Omnified Modification. Madness.\",\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/badecho.com\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/09\/SimpleJsonLogging.png\",\"width\":856,\"height\":448,\"caption\":\"Simple JSON Logging with AWS Lambda Running .NET\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#webpage\",\"url\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/\",\"name\":\"Simple JSON Logging with AWS Lambda Running .NET - omni&#039;s hackpad\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#primaryimage\"},\"datePublished\":\"2024-09-25T01:36:40+00:00\",\"dateModified\":\"2024-09-25T01:36:47+00:00\",\"description\":\"Demonstrates a quick and easy way to capture logs generated by AWS Lambda functions running .NET in JSON structured format.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/\"]}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#webpage\"},\"author\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"headline\":\"Simple JSON Logging with AWS Lambda Running .NET\",\"datePublished\":\"2024-09-25T01:36:40+00:00\",\"dateModified\":\"2024-09-25T01:36:47+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#webpage\"},\"commentCount\":2,\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"image\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#primaryimage\"},\"keywords\":\".NET,AWS,C#,Lambda,Terraform\",\"articleSection\":\"General Dev\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/badecho.com\/index.php\/2024\/09\/24\/aws-lambda-json-logs\/#respond\"]}]},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\",\"name\":\"Matt Weber\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7e345ac2708b3a41c7bd70a4a0440d41?s=96&d=mm&r=g\",\"caption\":\"Matt Weber\"},\"logo\":{\"@id\":\"https:\/\/badecho.com\/#personlogo\"}}]}<\/script>\r\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2952"}],"collection":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/comments?post=2952"}],"version-history":[{"count":8,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2952\/revisions"}],"predecessor-version":[{"id":2964,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2952\/revisions\/2964"}],"wp:attachment":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/media?parent=2952"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/categories?post=2952"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/tags?post=2952"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}