<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jaya Ganesh</title>
    <description>The latest articles on DEV Community by Jaya Ganesh (@jayaganeshk).</description>
    <link>https://dev.to/jayaganeshk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3205369%2Fcb085935-a09b-4a2c-82d0-1c130ec4943f.jpg</url>
      <title>DEV Community: Jaya Ganesh</title>
      <link>https://dev.to/jayaganeshk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jayaganeshk"/>
    <language>en</language>
    <item>
      <title>The Hidden Cost of AWS Lambda SnapStart for Python, and How I Fixed It with Durable Functions</title>
      <dc:creator>Jaya Ganesh</dc:creator>
      <pubDate>Mon, 20 Apr 2026 13:32:50 +0000</pubDate>
      <link>https://dev.to/jayaganeshk/the-hidden-cost-of-aws-lambda-snapstart-for-python-and-how-i-fixed-it-with-durable-functions-2ba4</link>
      <guid>https://dev.to/jayaganeshk/the-hidden-cost-of-aws-lambda-snapstart-for-python-and-how-i-fixed-it-with-durable-functions-2ba4</guid>
      <description>&lt;p&gt;Recently, I opened our AWS bill and found that thousands of dollars had been charged toward AWS Lambda. I was sure we did not have that much traffic. So I started digging through the bill, and that is when I found the real reason. It was Lambda SnapStart.&lt;/p&gt;

&lt;p&gt;Lambda SnapStart is a great feature. For latency-sensitive applications, especially APIs, it can reduce cold start duration from seconds to sub-seconds by creating a snapshot of the initialized function and caching it. When invoked, Lambda restores the execution environment from that snapshot and resumes from that point to serve traffic. But there was one key detail we had missed. Every time we publish a Lambda version with SnapStart enabled, a new snapshot is created and cached. We pay for both cache and restore. AWS documents that caching is billed for as long as the version remains active, with a minimum of 3 hours, and the pricing page example for US East, N. Virginia shows cache at $0.0000015046 per GB-second and restore at $0.0001397998 per GB restored.&lt;/p&gt;

&lt;p&gt;In our case, SnapStart itself was not the problem. Unused versions were.&lt;/p&gt;

&lt;p&gt;We had enabled SnapStart for many Python APIs. Over time, repeated deployments created more and more published versions with SnapStart enabled. The older versions were not referenced anywhere and were not receiving traffic, but they were still sitting there and continuing to accumulate snapshot cache charges. AWS documents a 14-day inactive cleanup behavior for Java SnapStart versions, but that is not something we could rely on for our Python functions. That was the real bill shock.&lt;/p&gt;

&lt;p&gt;So I built a cleanup workflow for it. Not a blind cleanup script. A controlled workflow with human approval. And this is where AWS Lambda Durable Functions turned out to be a perfect fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I used a Durable Function
&lt;/h2&gt;

&lt;p&gt;At first, this looked like a simple cleanup script. List the old versions, check if they are unused, and delete them.&lt;/p&gt;

&lt;p&gt;But I did not want a script that blindly deletes Lambda versions. Even if a version looks old, it could still be referenced by an alias or still be important for rollback. So I wanted this to be a workflow, not just a script.&lt;/p&gt;

&lt;p&gt;That is why I used AWS Lambda Durable Functions.&lt;/p&gt;

&lt;p&gt;With a durable orchestrator, I could break the process into clear steps: scan the SnapStart-enabled versions, filter the old and unused ones, generate a report, wait for approval, and only then delete them. The best part is that the approval step is part of the workflow itself. The function can pause, wait for a human decision, and then continue from the same point.&lt;/p&gt;

&lt;p&gt;At a high level, the workflow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqo5d4si0apt74apgsb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqo5d4si0apt74apgsb9.png" alt=" " width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the workflow works
&lt;/h2&gt;

&lt;p&gt;The workflow starts by listing all published Lambda versions with SnapStart enabled. Then it filters out versions that are newer than 14 days and also keeps the latest few versions as a safety measure. After that, it checks whether those versions are still being used by looking at invocation metrics and alias references. A version is considered a candidate only if it is old enough, not among the latest kept versions, not protected by an alias, and not showing recent usage.&lt;/p&gt;

&lt;p&gt;If a version is old, not protected, and not used, it is added to the candidate list.&lt;/p&gt;

&lt;p&gt;Once the list is ready, the workflow builds a report and sends an approval email. At this point, the durable function pauses and waits. If approved, it resumes and deletes the versions. If rejected, it simply exits without deleting anything. That is what made Durable Functions such a good fit for this use case.&lt;/p&gt;

&lt;p&gt;It gave me a clean human-in-the-loop workflow instead of a risky cleanup script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgeuk0r3gsacrbq8ulmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgeuk0r3gsacrbq8ulmf.png" alt=" " width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Approval flow
&lt;/h2&gt;

&lt;p&gt;The approval flow is simple.&lt;/p&gt;

&lt;p&gt;Once the candidate list is ready, the workflow uploads the report and sends an email with Approve and Reject links. Then the durable function pauses and waits for a callback.&lt;/p&gt;

&lt;p&gt;One detail I liked here is that the link itself does not directly take action.&lt;/p&gt;

&lt;p&gt;When the approver opens the link, it first shows a confirmation page. The actual approve or reject action happens only after clicking confirm. This is useful because email scanners and safe-link checkers often open links automatically, and I did not want them to accidentally approve a cleanup request.&lt;/p&gt;

&lt;p&gt;Once the approver confirms, the callback is sent back to the durable workflow. If approved, the orchestrator resumes and deletes the selected versions. If rejected, it exits safely without deleting anything.&lt;/p&gt;

&lt;p&gt;That made the whole process much safer and easier to trust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbx70zckwmvcbasv7ch5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbx70zckwmvcbasv7ch5.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;SnapStart is a really useful feature, especially for Python APIs where cold starts matter.&lt;/p&gt;

&lt;p&gt;But like many good AWS features, it needs the right cleanup around it. In our case, the problem was not SnapStart itself. It was the old published versions that kept piling up and quietly adding to the bill.&lt;/p&gt;

&lt;p&gt;That is why I built this workflow.&lt;/p&gt;

&lt;p&gt;Not just to delete unused versions, but to do it in a safe and controlled way. The Durable Function gave me exactly that. It let me build a proper workflow with scanning, reporting, human approval, and deletion only after confirmation.&lt;/p&gt;

&lt;p&gt;For me, this was the real value of the solution.&lt;/p&gt;

&lt;p&gt;It solved the cost problem, and at the same time showed how useful Durable Functions can be when we need automation with a human in the loop.&lt;/p&gt;

&lt;p&gt;If you are using SnapStart for Python, it is worth checking how many old versions are still sitting there. You may find that the real cost is not in your traffic, but in the versions you forgot to clean up.&lt;/p&gt;

&lt;p&gt;If you are interested in trying this approach or exploring the implementation in more detail, you can find the complete source code in my GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/jayaganeshk/aws-lambda-snapstart-version-cleaner" rel="noopener noreferrer"&gt;https://github.com/jayaganeshk/aws-lambda-snapstart-version-cleaner&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>azure</category>
      <category>python</category>
      <category>serverless</category>
    </item>
    <item>
      <title>A Practical Guide to Building Bidirectional Pagination in AWS DynamoDB</title>
      <dc:creator>Jaya Ganesh</dc:creator>
      <pubDate>Tue, 24 Jun 2025 12:38:31 +0000</pubDate>
      <link>https://dev.to/jayaganeshk/a-practical-guide-to-building-bidirectional-pagination-in-aws-dynamodb-3dam</link>
      <guid>https://dev.to/jayaganeshk/a-practical-guide-to-building-bidirectional-pagination-in-aws-dynamodb-3dam</guid>
      <description>&lt;p&gt;Pagination is the most common requirement for any API built. Usually, in the query parameter of the API, we will pass the required page number and the number of results to be present in the response.&lt;/p&gt;

&lt;p&gt;But, in DynamoDB, we won’t be able to jump to a specific page. That’s a design approach taken to maintain the same millisecond response time from the database, even if it has millions of records.&lt;/p&gt;

&lt;p&gt;So, how does Pagination work in DynamoDB? We can pass the number of items required in a response using Limit. And then we can request the next page and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwt79asd7g5rb6zjl2s66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwt79asd7g5rb6zjl2s66.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Unidirectional Pagination
&lt;/h2&gt;

&lt;p&gt;Let us create a DynamoDB Table to store Invoice details. For now, we can keep the table simple with Invoice Number as the partition key and Invoice Date as the sort key. And, let's create a GSI with partition key as entityType, which will be set to “invoice”, and sort key as invoice date.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frza6igarayhz2adgk3si.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frza6igarayhz2adgk3si.png" alt="Image description" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6euud2ypodxdhjej9jr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6euud2ypodxdhjej9jr.png" alt="Image description" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's use GSI to get invoices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F003ekr9eag3fuoeomvxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F003ekr9eag3fuoeomvxc.png" alt="Image description" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This query returns the first 10 invoices based on the ascending order of the invoice date. And, an interesting part in the response is the “Last Evaluated Key”. This key gives the primary key of the last element which was returned in the result. It is like an address, which you can use to fetch the next page.&lt;/p&gt;

&lt;p&gt;Now, let's use this last evaluated key and get the next page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxg5ks8jre7bgh2ibfkq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxg5ks8jre7bgh2ibfkq.png" alt="Image description" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you observe the response of the second page, it is from the 11th invoice to the 20th invoice and has the last evaluated key value in the response for the next page.&lt;/p&gt;

&lt;p&gt;Great, now we have achieved Unidirectional pagination, which allows us to go in the forward direction to get data in small chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bidirectional Pagination
&lt;/h2&gt;

&lt;p&gt;We know how to utilise the limit and Exclusive Start key parameter to achieve pagination. Is it possible to go to the previous page?&lt;/p&gt;

&lt;p&gt;The client can store the Last Evaluated Key on its side and get the previous page content. That looks simple, doesn't it? But, what if the number of pages is more, and it's complicated to store the keys for each page and maintain them? Instead, let's see how to handle it on the API side.&lt;/p&gt;

&lt;p&gt;We have the last evaluated key for a given page, and it is passed as the exclusive start key. DynamoDB goes in ascending order of sort key to get the data for the next page. So the simple approach would be to ask DynamoDB to get the items from a specific item in descending order, so that we would be able to get the previous page data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu93hz9eiu4k3tnatnvkq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu93hz9eiu4k3tnatnvkq.png" alt="Image description" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are able to retrieve the previous page, but the results are not sorted in ascending order. We can just reverse the response list to get the data in the desired format.&lt;/p&gt;

&lt;p&gt;If we observe carefully, the page 2 data doesn't have the 20th invoice in the response and also has the 10th invoice, which is supposed to be in 1st first. So, we can have a simple update done to get the response in the expected format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflickxupvamwl3ewh417.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflickxupvamwl3ewh417.png" alt="Image description" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faertodzva3asbi0377yc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faertodzva3asbi0377yc.png" alt="Image description" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, now we have achieved bidirectional pagination.&lt;/p&gt;

&lt;p&gt;Complete Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;dynamodb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get invoices with pagination support.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IndexName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entityType-invoiceDate-index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entityType = :entityType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:entityType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# if before is provided, we need to reduce the limit by 1 to account for the item that will be used as ExclusiveStartKey
&lt;/span&gt;    &lt;span class="n"&gt;scanIndexForward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageSize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;scanIndexForward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;

    &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ScanIndexForward&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scanIndexForward&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;last_evaluated_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LastEvaluatedKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# if before is provided, we need to get the complete item which is mentioned in ExclusiveStartKey
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;get_param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;get_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;get_param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# set the next cursor value to return in response   
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scanIndexForward&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;next_cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_evaluated_key&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;last_evaluated_key&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;previous_cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;next_cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entityType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;previous_cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_evaluated_key&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;last_evaluated_key&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;       

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;next_cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;previous_cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get 1st Page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyug5a3i50sdbe45okoi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyug5a3i50sdbe45okoi.png" alt="Image description" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Getting 2nd Page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hdx3czdexpm6rgr4eeh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hdx3czdexpm6rgr4eeh.png" alt="Image description" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's get the previous page — 1st page — using the before key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsumpr3hntv85cxo4ps1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsumpr3hntv85cxo4ps1o.png" alt="Image description" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Magic and Logic behind fetching the previous page
&lt;/h2&gt;

&lt;p&gt;When going to the previous page, DynamoDB doesn't give us a direct way. To achieve this, we have hacked a workaround.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We fetch pageSize - 1 records to skip the overlapping item between pages.&lt;/li&gt;
&lt;li&gt;We fetch that skipped item separately (using get_item).&lt;/li&gt;
&lt;li&gt;We reverse the list (since the query runs backwards) and add the skipped item back, giving us a proper previous page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Terminology of DynamoDB Query.
&lt;/h2&gt;

&lt;p&gt;Limit: While querying, we can pass a limit parameter, which decides the maximum number of items to be present in the response. If we don't pass a limit, DynamoDB will try to get as many items till the total response size reaches 1MB in size.&lt;/p&gt;

&lt;p&gt;Last Evaluated Key: This gives the primary key of the last element which is present in the response for that given page.&lt;/p&gt;

&lt;p&gt;Exclusive Start Key: We can ask DynamoDB to start getting items from a specific record instead of getting them from the beginning.&lt;/p&gt;

&lt;p&gt;Scan Index forward: While getting the items from a specific item, if scan index forward is set to true, then DynamoDB fetches the item in ascending order based on sort key and in reverse when it is set to false.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion:
&lt;/h2&gt;

&lt;p&gt;By using &lt;code&gt;ExclusiveStartKey&lt;/code&gt; and playing with the sort direction, we can build clean, reliable B*&lt;em&gt;idirectional pagination&lt;/em&gt;* on top of DynamoDB.&lt;/p&gt;

&lt;p&gt;It’s fast and works well, though DynamoDB doesn’t support "go to page 10" style jumps (by design, to stay super fast). But for "next" and "previous" style UIs, this pattern works great.&lt;/p&gt;

&lt;p&gt;The only thing to watch out for is if new items get added or removed, in between pages might shift a bit. But for most apps, this is perfectly fine.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>BattleShip — Sink Enemy Ships — Game Rebuilt by Amazon Q CLI</title>
      <dc:creator>Jaya Ganesh</dc:creator>
      <pubDate>Sat, 07 Jun 2025 09:32:39 +0000</pubDate>
      <link>https://dev.to/jayaganeshk/battleship-sink-enemy-ships-game-rebuilt-by-amazon-q-cli-519e</link>
      <guid>https://dev.to/jayaganeshk/battleship-sink-enemy-ships-game-rebuilt-by-amazon-q-cli-519e</guid>
      <description>&lt;p&gt;In my school days, during summer season, I used to go to my friends home and play video games in his computer. There were many games I used to like; one such game was BattleShip.&lt;/p&gt;

&lt;p&gt;Fast forward to the current summer, I got to rebuild the same game from scratch. I got introduced to Amazon Q. It is a generative AI-powered assistant which I used as my coding copilot. It helped me with code completion, saved me from writing boilerplate code, and accelerated my ability to write better code.&lt;/p&gt;

&lt;p&gt;Then, I got introduced to Amazon Q CLI, an Agentic Assistant. Just give it a command and let it take care of the rest. Without writing a single line of code, we can create an entire application from scratch.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Setting Up&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;To install Amazon Q CLI in Windows, you must enable &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install" rel="noopener noreferrer"&gt;WSL (Windows Subsystem for Linux).&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wsl --install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once the Machine is restarted, you can access the WSL.&lt;/p&gt;

&lt;p&gt;We can install Amazon Q CLI by downloading the ZIP file and installing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl --proto '=https' --tlsv1.2 -sSf "&amp;lt;https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip&amp;gt;" -o "q.zip"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, unzip it and run the installation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;unzip q.zip&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./q/install.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, we can run &lt;code&gt;q doctor&lt;/code&gt; to check the installation status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv18t7mzxwdnlz203m9ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv18t7mzxwdnlz203m9ui.png" alt="Image description" width="793" height="172"&gt;&lt;/a&gt;&lt;br&gt;
You can login to Amazon Q CLI using &lt;code&gt;q login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fioscfw4xd63h39ug60up.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fioscfw4xd63h39ug60up.png" alt="Image description" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Prompt To Generate the game:&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;You can select from the available models to help with the task. I selected Claude 4 Sonnet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5nae0q1ihocshx0izbr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5nae0q1ihocshx0izbr.png" alt="Image description" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used below prompt to generate the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a Complete Browser-Based Battleship Game

Technical Requirements:
• Use HTML, CSS, and vanilla JavaScript only (no frameworks or libraries)
• Implement as multiple project files for better maintainability
• Ensure responsive design that works on different screen sizes

Game Features:
• Single-player gameplay against a computer opponent
• 10x10 grid boards for both player and computer
• 5 ships of different sizes: Carrier (5), Battleship (4), Cruiser (3), Submarine (3), Destroyer (2)

Ship Placement Phase:

• Manual placement by clicking on cells
• Option to randomize ship positions
• Ability to rotate ships before and after placement
• Ability to move ships after placement but before game start
• Visual selection and highlighting of ships for editing
• No ships should touch (including diagonally)

Gameplay Mechanics:

• Turn-based gameplay where players alternate firing at opponent's grid
• Visual indication of hits and misses with animations
• Ship status tracking (active vs. sunk)
• Game over detection when all ships of either player are sunk
• Clear feedback messages throughout gameplay

Visual Elements:

• Detailed top-view representations for different ship types
• Visual differentiation between ship parts (bow, middle, stern)
• Ship legend showing all ship types and their current status
• Clear visual feedback for hits, misses, and selected ships
• Seamless ship appearance without gaps between cells
• Animation for ship blasts when hit
• Cross marker over ships when completely sunk
• Only the latest hit cell should animate/blink
• Clean, intuitive user interface with appropriate buttons and controls

Additional Requirements:

• Include detailed comments in the code to explain implementation logic
• Ensure proper visual feedback when ships are hit or sunk
• Implement proper ship damage visualization
• Ensure no duplicate ships appear when sunk
• Make sure hit markers are clearly visible on ships

The game should provide an engaging and visually appealing experience while following classic Battleship rules.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After I entered the prompt, Amazon Q started doing its magic by “&lt;strong&gt;Thinking”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnvqbd647wsp4xsshhjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnvqbd647wsp4xsshhjn.png" alt="Image description" width="800" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And after a few minutes, it created the entire game with a proper folder structure.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;I opened the index.html file that was created, and boom!&lt;/p&gt;

&lt;p&gt;The game was ready to play!&lt;/p&gt;

&lt;p&gt;You can experience the game from the URL below: &lt;a href="https://jayaganeshk.github.io/battleship/" rel="noopener noreferrer"&gt;https://jayaganeshk.github.io/battleship/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find the code that was generated in the repo below: &lt;a href="https://github.com/jayaganeshk/battleship" rel="noopener noreferrer"&gt;https://github.com/jayaganeshk/battleship&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgu4j168v7vv0itkumxti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgu4j168v7vv0itkumxti.png" alt="Image description" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>amazonqcli</category>
    </item>
    <item>
      <title>Don’t log it... Just Trace it! OpenTelemetry in AWS Lambda - Part 3</title>
      <dc:creator>Jaya Ganesh</dc:creator>
      <pubDate>Sun, 01 Jun 2025 07:52:50 +0000</pubDate>
      <link>https://dev.to/jayaganeshk/dont-log-it-just-trace-it-opentelemetry-in-aws-lambda-part-3-3kib</link>
      <guid>https://dev.to/jayaganeshk/dont-log-it-just-trace-it-opentelemetry-in-aws-lambda-part-3-3kib</guid>
      <description>&lt;p&gt;In the Previous article, we have set up OpenTelemetry in an AWS Lambda function and saw the traces in Honeycomb. In this article, let's see how to customise the OTel data by sending custom application-related metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Update Lambda Code
&lt;/h2&gt;

&lt;p&gt;We can add a few business logics to our Lambda and also make external API calls.&lt;/p&gt;

&lt;p&gt;Update the lambda function with the below code. It makes an API call to get a list of products and returns.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡For the requests to work, you need to add the requests package via lambda layers. You either package your function with requests, or you can get lambda layer ARNs from the below URL.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_products&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://fakerapi.it/api/v2/products?_quantity=1&amp;amp;_taxes=12&amp;amp;_categories_type=uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_products&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;There was some error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Invoke and see the trace
&lt;/h2&gt;

&lt;p&gt;Load the URL in browser and we will be able to see the data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hgx8j85dax5zq1m56m9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hgx8j85dax5zq1m56m9.png" alt="Image description" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Honeycomb, open the trace and see it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzz6qvkgzqznquz0ugfrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzz6qvkgzqznquz0ugfrg.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;if you notice, we are able to see the GET call that was made, time taken and also few metadata like URL and status..&lt;/p&gt;

&lt;p&gt;We achieved all of these without make any changes to code. By just adding few more lines of code we would be able to enrich the telemetry data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡You get this level of detail &lt;strong&gt;without writing any additional instrumentation code&lt;/strong&gt;, thanks to OpenTelemetry’s auto-instrumentation!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: Create custom spans
&lt;/h2&gt;

&lt;p&gt;In Trace, we are able to see the API call that was made, but we don't have info about what was the function name which invoked the GET call, get_products function. &lt;/p&gt;

&lt;p&gt;And, we have added a print statement in lambda handler for the incoming event. But that is not visible in Trace. &lt;/p&gt;

&lt;p&gt;Let’s update the lambda function with the code below to add Span Attributes and Span Events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;

&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@tracer.start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_products&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://fakerapi.it/api/v2/products?_quantity=1&amp;amp;_taxes=12&amp;amp;_categories_type=uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;process_data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_products&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;There was some error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After invoking the API again, head to Honeycomb and inspect the trace.&lt;/p&gt;

&lt;p&gt;You would be able to see the span in a hierarchical format. We can see that the span event with the name log was added to the process_data span.&lt;/p&gt;

&lt;p&gt;You should now see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;custom span&lt;/strong&gt; for &lt;code&gt;get_products&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;parent span&lt;/strong&gt; named &lt;code&gt;process_data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;event&lt;/strong&gt; named &lt;code&gt;log&lt;/code&gt; with the Lambda input event&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;span attribute&lt;/strong&gt; called &lt;code&gt;products.length&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3106agjzsixpf2yo1as.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3106agjzsixpf2yo1as.png" alt="Image description" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have also added a span attribute with the name “product length”. We can see it in the fields section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsibup5z1lj7h84m2son6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsibup5z1lj7h84m2son6.png" alt="Image description" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Explanation:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Starting a new span&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can start a custom span in our code and wrap it around our function or part of a function which we want to track separately in OTel.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@tracer.start_as_current_span("get_products")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or &lt;/p&gt;

&lt;p&gt;&lt;code&gt;with tracer.start_as_current_span("process_data") as span:&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add custom span attribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can add our custom data in a key-value pair to Span as a span attribute, which would help us in querying the data efficiently and know the system status better&lt;/p&gt;

&lt;p&gt;&lt;code&gt;span.set_attribute("products length",  str(len(products)))&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add Span Events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use events to log contextual information like errors or input payloads:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;span.add_event("log", {"event": str(event)})&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion:
&lt;/h2&gt;

&lt;p&gt;In this article, we learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create &lt;strong&gt;custom spans&lt;/strong&gt; in AWS Lambda&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;span events&lt;/strong&gt; to track log-like info&lt;/li&gt;
&lt;li&gt;Enrich spans with &lt;strong&gt;custom attributes&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These enhancements help us better understand our application's behaviour, performance, and internal state using distributed tracing tools like Honeycomb.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Don't log it… Just Trace it! OpenTelemetry in AWS Lambda - Part 2</title>
      <dc:creator>Jaya Ganesh</dc:creator>
      <pubDate>Sun, 25 May 2025 07:07:14 +0000</pubDate>
      <link>https://dev.to/jayaganeshk/dont-log-it-just-trace-it-opentelemetry-in-aws-lambda-part-2-3n6j</link>
      <guid>https://dev.to/jayaganeshk/dont-log-it-just-trace-it-opentelemetry-in-aws-lambda-part-2-3n6j</guid>
      <description>&lt;p&gt;In the previous article, we were introduced to OpenTelemetry and AWS Distro for OpenTelemetry. In this article, we will set up OpenTelemetry in AWS Lambda for Python and observe it in Honeycomb, an observability tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpevac1pmt09cjc3vntw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpevac1pmt09cjc3vntw.png" alt="ADOT arch" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS Lambda: A serverless Function as a service provided by AWS where we can execute our application code without thinking about Servers&lt;/li&gt;
&lt;li&gt;Amazon API Gateway: A fully managed API solution that lets us create and publish REST and HTTP APIs.&lt;/li&gt;
&lt;li&gt;ADOT Lambda Layer: An AWS-supported distribution of the OpenTelemetry project that includes SDKs, agents, and collectors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting up AWS Lambda Function and API Gateway:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create an AWS Lambda Python Function in the AWS Console.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c1ov3yqinqnwg376x8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c1ov3yqinqnwg376x8j.png" alt="Image description" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now, let's create an API gateway.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftg9s2rr42hqnbyri56tk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftg9s2rr42hqnbyri56tk.png" alt="Image description" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let's add a GET method to the API and attach the Lambda which was created in step 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wa6c3v93gm7unl81gta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wa6c3v93gm7unl81gta.png" alt="Image description" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now, deploy it and test it from a browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faae7581tt0409amweupc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faae7581tt0409amweupc.png" alt="Image description" width="763" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have got the response from Lambda. We can go to the AWS Lambda console and look into the Logs. We can see the event from the API Gateway from the print statement in the logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5swe4v5k00tj2kp0btww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5swe4v5k00tj2kp0btww.png" alt="Image description" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up ADOT in AWS Lambda
&lt;/h2&gt;

&lt;p&gt;We have successfully created an API Endpoint and saw the logs in CloudWatch. Now let us set up AWS Distro for OpenTelemetry on this lambda and export the telemetry data to Honeycomb.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add AWS Lambda Layer:&lt;/p&gt;

&lt;p&gt;You can get the latest Lambda Layer ARN from &lt;a href="https://aws-otel.github.io/docs/getting-started/lambda/lambda-python#add-the-arn-of-the-lambda-layer" rel="noopener noreferrer"&gt;ADOT documentation&lt;/a&gt; and add attached it to our lambda function.&lt;/p&gt;

&lt;p&gt;Lambda Layer format (arn:aws:lambda::901920570463:layer:aws-otel-python--ver-1-29-0:1)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1uw937t4u6lhzk472vn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1uw937t4u6lhzk472vn8.png" alt="Image description" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Configure Lambda Environment Variables:&lt;/p&gt;

&lt;p&gt;Add the below environment variables to our Lambda function&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS_LAMBDA_EXEC_WRAPPER&lt;/th&gt;
&lt;th&gt;/opt/otel-instrument&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OTEL_PROPAGATORS&lt;/td&gt;
&lt;td&gt;tracecontext,xray&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTEL_SERVICE_NAME&lt;/td&gt;
&lt;td&gt;learn-adot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a Custom OTel Collector config file with below data and replace your Honeycomb API Key.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#collector.yaml in the root directory&lt;/span&gt;
&lt;span class="c1"&gt;#Set an environemnt variable 'OPENTELEMETRY_COLLECTOR_CONFIG_FILE' to '/var/task/collector.yaml'&lt;/span&gt;
&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# port 4317&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# port 4318&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Data sources: traces, metrics, logs&lt;/span&gt;
  &lt;span class="c1"&gt;# NOTE: Prior to v0.86.0 use `logging` instead of `debug`&lt;/span&gt;
  &lt;span class="c1"&gt;# logging:&lt;/span&gt;
  &lt;span class="c1"&gt;#   verbosity: detailed&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api.honeycomb.io:443"&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-honeycomb-team"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;HONEYCOMB API KEY&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get a Honeycomb API Key using steps below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on Manage Data&lt;/li&gt;
&lt;li&gt;Select environments&lt;/li&gt;
&lt;li&gt;And click API Keys and generate an Ingest Key.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotmfhxtidxuixo5ba566.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotmfhxtidxuixo5ba566.png" alt="Image description" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We can add this config YAML file to the AWS Lambda code directly. In production scenarios, we can upload it to an S3 Bucket and add its URI to the Lambda environment variable. For this, we need to add get S3 Permission to the Lambda.&lt;/p&gt;

&lt;p&gt;Add below environment variable to Lambda:&lt;/p&gt;

&lt;p&gt;OPENTELEMETRY_COLLECTOR_CONFIG_URI: s3://.s3..amazonaws.com/collector_config.yaml&lt;/p&gt;

&lt;p&gt;Now our AWS Lambda environment variables look like this.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe0vnid2ccqfhrshly36m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe0vnid2ccqfhrshly36m.png" alt="Image description" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Now Let's invoke the same API again and see the Magic happen.&lt;/p&gt;

&lt;p&gt;We can see a few more logs in our CloudWatch Logs indicating that our collector has started.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n7sg6telqjxmfm63gu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n7sg6telqjxmfm63gu5.png" alt="Image description" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Honeycomb console, we can see our incoming requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6b5zyxe3r8z3vrw9iew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6b5zyxe3r8z3vrw9iew.png" alt="Image description" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the graph and select view trace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fft3d82bh3dbyvgewp79z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fft3d82bh3dbyvgewp79z.png" alt="Image description" width="387" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Trace, we can see the Span Fields and other meta data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepgswd0gialqaz92x1gy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepgswd0gialqaz92x1gy.png" alt="Image description" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we've successfully implemented basic OpenTelemetry observability in an AWS Lambda function using ADOT, transforming standard CloudWatch logs into rich, context-aware telemetry visible in Honeycomb.&lt;/p&gt;

&lt;p&gt;While this setup already provides significant advantages—including end-to-end request visibility, automatic AWS metadata collection, and cross-service tracing capabilities—we've only scratched the surface of what's possible. &lt;/p&gt;

&lt;p&gt;In the next article, we'll enhance our observability by adding custom instrumentation with span attributes and events to capture business-relevant context, measure specific operations, and correlate traces across services, enabling you to answer critical questions about your application's performance and behaviour.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Don’t log it… Just Trace it! OpenTelemetry in AWS — Part 1</title>
      <dc:creator>Jaya Ganesh</dc:creator>
      <pubDate>Sun, 25 May 2025 07:01:33 +0000</pubDate>
      <link>https://dev.to/jayaganeshk/dont-log-it-just-trace-it-opentelemetry-in-aws-part-1-1ja1</link>
      <guid>https://dev.to/jayaganeshk/dont-log-it-just-trace-it-opentelemetry-in-aws-part-1-1ja1</guid>
      <description>&lt;p&gt;Traditionally, we rely on print or log statements in our code to debug or understand the system’s current status. But does that really solve the problem?&lt;/p&gt;

&lt;p&gt;AWS offers tools like CloudWatch Insights, dashboards, and alarms to meet basic monitoring needs. However, it doesn’t allow us to visualise the time taken for a particular part of the function or where there are bottlenecks. We could use AWS X-Ray. But what if your application interacts with services owned by other teams — possibly in different AWS accounts or entirely different environments?&lt;/p&gt;

&lt;p&gt;What if you could see the entire path (or trace) of events that happened when a user clicked on a website, to all the backend services that were triggered as part of it? This is where OpenTelemetry comes to the rescue, ushering in the concept of observability.&lt;/p&gt;

&lt;p&gt;Observability is the ability to see the current state or context of the system using the data it generates. We would be able to see all the incoming and outgoing requests in our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OpenTelemetry (OTel)?
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry is an open-source observability framework for instrumenting, gathering and exporting telemetry data like traces, metrics and logs from our application.&lt;/p&gt;

&lt;p&gt;OTel is a vendor-agnostic framework, which means that we can export the logs to various observability tools like Honeycomb, New Relic, Grafana, Datadog, etc.&lt;/p&gt;

&lt;p&gt;OpenTelemetry is a CNCF (Cloud Native Computing Foundation) project and is the result of a merger of two projects, OpenCensus and OpenTracing, unified to create a global standard for telemetry&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Distro For OpenTelemetry (ADOT)
&lt;/h2&gt;

&lt;p&gt;ADOT is a secure, production-ready, AWS-supported distribution for OTel provided by AWS. It abstracts the manual instrumentation required for OpenTelemetry. Using Auto instrumentation in ADOT, without writing any extra lines of code, we can publish basic telemetry data to any Observability tool. It also collects and includes metadata from our AWS Lambda like function name and version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2gompcfo4izyiws0sr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2gompcfo4izyiws0sr7.png" alt="ADOT" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In AWS Lambda, we can just add an ADOT Lambda Layer to our function and just by adding a few environment variables, we would be able to collect telemetry data and export it to any observability tool.&lt;/p&gt;

&lt;p&gt;Now let’s understand more about OpenTelemetry and its core concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Concepts of OpenTelemetry
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Traces
&lt;/h3&gt;

&lt;p&gt;Traces provide end-to-end visibility into request flows across our systems. Think of a trace as the journey of a single request as it travels through your application ecosystem. Each step taken by it is called a Span.&lt;/p&gt;

&lt;p&gt;Trace Contains Unique identifiers known as trace ID, and each Span also has a unique ID known as Span ID.&lt;/p&gt;

&lt;p&gt;A Span contains metadata like timestamp, status, faas (Function As a Service) name and versions.&lt;br&gt;
Key Moments, like exceptions in a span, are captured as Events.&lt;br&gt;
We can add custom Key-Value Pairs to span, which are application-related, and it is called span attributes.&lt;br&gt;
If any external calls are made, this Trace ID is propagated across systems, and this is called as context propagation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics
&lt;/h3&gt;

&lt;p&gt;Metrics are numerical measurements collected to monitor the system. Counter is the most common type of metric. For example, you can have a metric for the number of users who clicked on a button or the number of products that were purchased.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logs
&lt;/h3&gt;

&lt;p&gt;Logging mechanisms are available in all programming languages. OpenTelemetry helps us to correlate logs with Traces and Metrics. And, a centralised location to observe logs from various systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrumentation
&lt;/h3&gt;

&lt;p&gt;It is the process of adding observability to our application code. There are two types available one is Manual instrumentation and another is Auto Instrumentation.&lt;/p&gt;

&lt;p&gt;In Auto Instrumentation, minimal code changes are required, and it enables us to easily set up observability for our application, whereas in manual instrumentation, we need to write custom code to set up collection and transportation of telemetry data.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenTelemetry Collectors
&lt;/h3&gt;

&lt;p&gt;OpenTelmetry collectors can be run as an agent (sidecar) alongside our application or as a standalone service.&lt;/p&gt;

&lt;p&gt;Receiver: Ingests data from sources&lt;br&gt;
Processor: Transforms, filters telemetry data&lt;br&gt;
Exporters: Send data to various Observability tools like AWS X-Ray, Honeycomb, New Relic, Etc.&lt;br&gt;
In AWS Lambda, this can be run as a Lambda extension just by adding the ADOT Lambda Layer.&lt;/p&gt;

&lt;p&gt;Now that we’ve covered the basics of OpenTelemetry, the next part will walk through implementing it in AWS Lambda and exporting telemetry data to an observability tool.&lt;/p&gt;

&lt;p&gt;Until then, Happy Learning!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>observability</category>
      <category>opentelemetr</category>
      <category>lambda</category>
    </item>
  </channel>
</rss>
