<?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: Mannie Schumpert</title>
    <description>The latest articles on DEV Community by Mannie Schumpert (@mannieschumpert).</description>
    <link>https://dev.to/mannieschumpert</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%2F524874%2Fa1332e3c-e601-4897-a2b9-f9eeedb396d2.jpeg</url>
      <title>DEV Community: Mannie Schumpert</title>
      <link>https://dev.to/mannieschumpert</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mannieschumpert"/>
    <language>en</language>
    <item>
      <title>Setting Up a Vercel "Log Drain" with AWS</title>
      <dc:creator>Mannie Schumpert</dc:creator>
      <pubDate>Mon, 23 Jan 2023 13:46:58 +0000</pubDate>
      <link>https://dev.to/mannieschumpert/setting-up-a-vercel-log-drain-with-aws-4a9c</link>
      <guid>https://dev.to/mannieschumpert/setting-up-a-vercel-log-drain-with-aws-4a9c</guid>
      <description>&lt;p&gt;If you're hosting apps on &lt;a href="https://vercel.com" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, you might have noticed that they have a number of quality logging integrations in their Marketplace. But if those don't suit your needs, or if you're already using AWS services (especially Lambda and CloudWatch) and want to keep things lean and simple, this article is for you!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This might seem a bit involved, but the code is simple and it really only takes about 15 minutes! 😎&lt;br&gt;
Also, searching API logs from Vercel in CloudWatch is a breeze! 🏄🏻 I'll tell you more about that at the end of the article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Create a Lambda to process your logs
&lt;/h2&gt;

&lt;p&gt;In your AWS console, go to Services -&amp;gt; Lambda -&amp;gt; Functions, and click "Create Function". Name your function as appropriate, but leave everything else in this first section at the defaults.&lt;br&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%2Fuvmi205ou4i21q1yiw8q.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%2Fuvmi205ou4i21q1yiw8q.png" title="Create Lambda function" alt="Image description" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under "Advanced Settings", you need to select "Enable function URL", choose "NONE" for "Auth type" (we'll be handling authentication ourselves), and check the CORS button.&lt;br&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%2Ffq9v63okk9xkfm2gndku.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%2Ffq9v63okk9xkfm2gndku.png" title="Create Function Advanced Settings" alt="Image description" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click "Create Function". Once you're redirected to your new lambda, find "Function URL" in the upper right under "Function Overview", and copy it to your clipboard. You'll need this for the next step.&lt;/p&gt;

&lt;p&gt;⛔️ Don't close this tab! You've got more work to do here very soon.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Create a new Log Drain in Vercel
&lt;/h2&gt;

&lt;p&gt;In a different browser tab, go to your Vercel dashboard, then go to Settings (this is settings for your whole team in Vercel), then click "Log Drains". Here you'll see the "Add a Log Drain" form.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose your desired sources. (I just wanted to log Next.js API requests, so I only chose "Lambda").&lt;/li&gt;
&lt;li&gt;For delivery format, we will use "NDJSON" format which is easy for us to process in our logging lambda. (More on that later.)&lt;/li&gt;
&lt;li&gt;Choose "all projects" or specific projects as you wish. Note that you can easily filter logs by project later from the CloudWatch console. (See end of article for details.)
&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%2Fzv4ov07todj7cmu8dlni.png" title="Log Drain Sources, etc" alt="Image description" width="800" height="466"&gt;
&lt;/li&gt;
&lt;li&gt;Under "Endpoint" paste your lambda function URL. (You'll now see a new panel with a "Verify" button. ✋ Don't do this yet!)
&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%2Fzeezbiuurvdv089ifnhi.png" title="Log Drain Endpoint" alt="Image description" width="800" height="221"&gt;
&lt;/li&gt;
&lt;li&gt;Click the toggle for "Custom Headers". We will be authenticating requests to our logging lambda via an "authentication" header.&lt;/li&gt;
&lt;li&gt;Enter "authentication" in the Header Key. Then create a token (using something like the &lt;a href="https://www.lastpass.com/features/password-generator" rel="noopener noreferrer"&gt;LastPass generator&lt;/a&gt;), and paste it into "Header Value", prepending "Bearer ". (This is an authentication header convention.) Now click "Add" to add the header to the Log Drain.
&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%2F4s4md526r20d4hls4hp1.png" title="Custom Headers Form" alt="Image description" width="800" height="248"&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%2Fzysvvv0w3ntq09iang89.png" title="Custom Header Added" alt="Image description" width="800" height="249"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;⛔️ Don't close this tab!&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Verify your logging URL
&lt;/h2&gt;

&lt;p&gt;Vercel requires that you verify your ownership of your logging URL, and to do this, you'll need to modify your lambda to return a header that Vercel specifies.&lt;br&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%2Foy79i7lv2om360q65j0q.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%2Foy79i7lv2om360q65j0q.png" title="URL Verification Panel" alt="Image description" width="800" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to your lambda console in AWS, and replace &lt;code&gt;index.mjs&lt;/code&gt; with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello from Lambda!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-vercel-verify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;vercel-verification-string&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&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;🟢  Be sure to click "Deploy" to deploy your changes to your live lambda.&lt;/p&gt;

&lt;p&gt;Now go back to Vercel and click "Verify". The verification panel should now go away and you'll see a blue check mark next to your URL.&lt;/p&gt;

&lt;p&gt;🙌  We're &lt;em&gt;almost&lt;/em&gt; ready to "Add Log Drain", but not quite yet!&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Complete your lambda, adding authentication and logging functions
&lt;/h2&gt;

&lt;p&gt;Back in your lambda console tab, you'll need to add the authentication token you generated earlier to your lambda's environment variables. Go to "Configuration -&amp;gt; Environment Variables" then click "Edit". Add your token with the key "AUTH_TOKEN" and click "Save".&lt;br&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%2Fl5l8ljpyw99jgofm83ez.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%2Fl5l8ljpyw99jgofm83ez.png" title="Add Token Env Variable" alt="Image description" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, replace your function's &lt;code&gt;index.mjs&lt;/code&gt; with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* global atob */&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Return unauthorized if missing auth header&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authentication&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authentication&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🔴 Unauthorized request&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&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="cm"&gt;/**
     * We convert the request body from Base64,
     * then split the resulting string by new lines.
     * (The NDJSON format sends line-delimited JSON logs.)
     */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// We create a log entry for each item received.&lt;/span&gt;
    &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * The split function will leave an empty item
         * at the end of the array. We don't want to log this.
         */&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&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="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;🟢  Again, be sure to click "Deploy" to deploy your changes to your live lambda.&lt;/p&gt;

&lt;p&gt;Your lambda is now complete! 🕺🏻&lt;/p&gt;

&lt;h2&gt;
  
  
  FINAL STEP: Activate the Log Drain in Vercel
&lt;/h2&gt;

&lt;p&gt;Finally, go back to your Vercel tab and click the "Add Log Drain" button.&lt;/p&gt;

&lt;p&gt;Done! 🎉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(Note that Vercel will show a dialog with information about securing your logging lambda, but we've already handled this, so you can ignore these instructions unless you prefer the authentication method they suggest.)&lt;/p&gt;
&lt;/blockquote&gt;



&lt;h2&gt;
  
  
  🚨 Bonus: Working with API logs in CloudWatch and Logs Insights
&lt;/h2&gt;

&lt;p&gt;To see the logs you've ingested from Vercel, go to your lambda's console, click "Monitor" then click "View CloudWatch logs". To view all logs, click "Search all log streams", then apply filters as needed to find the logs you're looking for.&lt;/p&gt;

&lt;p&gt;The way we've set up our logging, with one entry per log sent by Vercel, logs can be filtered as though they are JSON objects using &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html#matching-terms-events" rel="noopener noreferrer"&gt;CloudWatch filter syntax&lt;/a&gt;. For example, to view all &lt;code&gt;GET&lt;/code&gt; requests, add this to the search/filter input:&lt;br&gt;
&lt;code&gt;{$.proxy.method = "GET"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or to filter by project, use:&lt;br&gt;
&lt;code&gt;{$.projectName = "my-nextjs-project"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In Log Insights, you can create some valuable visualizations, like how many requests missed the cache per hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @message, @timestamp
|  filter(proxy.vercelCache = "MISS")
|  stats count() by bin(1h)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...or stats for requests for specific project grouped by request method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @message, @timestamp
|  filter(projectName = "my-nextjs-project")
|  stats count() by proxy.method
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this helps!&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
      <category>career</category>
    </item>
  </channel>
</rss>
