How to make client-side logger with Amplify and CloudWatch
Tags: aws, amplify, cloudwatch, log
Published: 2023-03-29 00:00:00

---
title: How to make client-side logger with Amplify and CloudWatch
tags: 
  - aws
  - amplify
  - cloudwatch
  - log
draft: false
summary: I will show you how to make client-side logger with Amplify and CloudWatch without any need from SaaS logger
---

The client-side log is different from the server-side log because it didn't write into the file system and it's not easy to control it. I'm using AWS and I don't want to use SaaS service to feed the log into CloudWatch (why do I have to send the log to other services and send it back into CloudWatch!)

## Goal

- Run Next.js SSG (Static Site Generator) on Amplify Hosting (or S3 CloudFront)
- Enable client-side log without sending API to lambda

## TLDR

- Amplify has a Logger class and CloudWatch provider, so we can feed log from the browser to CloudWatch
- Need extra effort to use AWS Cognito to allow an unauthorized users to send log events to CloudWatch

## Note

This tutorial runs on

- Node.js version `v18.15.0`
- Next.js version `13.2.4`
- Amplify version `11.0.2`

## Prerequisite

- [Registered AWS account](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html)
- [Installed Amplify cli](https://docs.amplify.aws/cli/start/install/)

## Steps

### 1. Setup Next.js project

Just create a new Next.js project

```sh
npx create-next-app@latest
```

Enter Next.js wizard

- What is your project named? -> "PROJECT-NAME"
- Would you like to use TypeScript with this project? -> "Yes"
- Would you like to use ESLint with this project? -> "Yes"
- Would you like to use `src/` directory with this project? -> "Yes"
- Would you like to use experimental `app/` directory with this project? -> "Yes"
- What import alias would you like configured? -> "@/*"

Then verify (it suppose to run Next.js app)

```sh
cd PROJECT-NAME/
npm run dev
```

### 2. Setup Amplify

Initiate Amplify project

```sh
amplify init 
```

Enter wizard input

- Enter a name for the project -> "PROJECT-NAME"
- Initialize the project with the above configuration? -> "Yes"
- Select the authentication method you want to use: -> "AWS profile"
- Please choose the profile you want to use -> "default"

When we submit the input, Amplify will generate a project directory on `amplify/` and a resources file on `src/aws-exports.js`, the last one will re-generate when the project resource has changed.

Inside `amplify/`, there are some files we can modify, one of which is `/amplify/.config/project-config.json` which is the configuration about the environment and build script that we can update later.

```txt
Project information
| Name: PROJECT-NAME
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start
```

Then install `aws-amplify` package

```sh
npm i aws-amplify
```

We can verify by cli (suppose to show empty resource, since we didn't do anything yet)

```sh
amplify status 

    Current Environment: dev

┌──────────┬───────────────┬───────────┬─────────────────┐
│ Category │ Resource name │ Operation │ Provider plugin │
└──────────┴───────────────┴───────────┴─────────────────┘
```

We can go check on AWS CloudTrail (suppose to show that we have created resources on Amplify, CloudFormation, and S3)

### 3. Deploy SSG

We are going to deploy Next.js in SSG (Static Site Generator) on Amplify Hosting (alternatively, we can deploy with S3 + CloudFront)

#### Add hosting resource

```sh
# this is how we add resource with Amplify cli
amplify add hosting
```

Enter wizard input

- Select the plugin module to execute -> "Hosting with Amplify Console" (Managed hosting with custom domains, Continuous deployment)
- Choose a type -> "Manual deployment"

Verify (suppose to show that we have created a new resource, which is Amplify Hosting)

```sh
amplify status

    Current Environment: dev

┌──────────┬────────────────┬───────────┬───────────────────┐
│ Category │ Resource name  │ Operation │ Provider plugin   │
├──────────┼────────────────┼───────────┼───────────────────┤
│ Hosting  │ amplifyhosting │ Create    │ awscloudformation │
└──────────┴────────────────┴───────────┴───────────────────┘


No amplify console domain detected
```

#### Run deploy

Before deploying, we have to update the build script to support SSG, tell Next.js to build for the static host, by editing `package.json`

```json
{
  "script": {
    "build": "next build && next export"
  }
}
```

Verify by build and run on the static host tool (we might need to install serve cli by `npm i -g serve`)

```sh
npm run build 

serve out # we should have the same local as the previous step
```

We have to update Amplify config to use `dist` folder as `out` since the default exported SSG folder will be `out` by editing `amplify/.config/project-config.json`

```json
{
  "javascript": {
    "config": {
      "DistributeDir": "out"
    }
  }
}
```

<small>Note: I left some content in file to reduce confusion.</small>

Then deploy

```sh
amplify publish
```

Verify again with `amplify status` (suppose to update Amplify Hosting status and show hosting url)

```sh
amplify status

    Current Environment: dev

┌──────────┬────────────────┬───────────┬───────────────────┐
│ Category │ Resource name  │ Operation │ Provider plugin   │
├──────────┼────────────────┼───────────┼───────────────────┤
│ Hosting  │ amplifyhosting │ No Change │ awscloudformation │
└──────────┴────────────────┴───────────┴───────────────────┘


Amplify hosting urls:
┌──────────────┬───────────────────────────────────────────┐
│ FrontEnd Env │ Domain                                    │
├──────────────┼───────────────────────────────────────────┤
│ dev          │ https://dev.--------------.amplifyapp.com │
└──────────────┴───────────────────────────────────────────┘
```

Note:

- When changing a build script to support SSG, we might encounter the error “Error: Image Optimization using Next.js' default loader is not compatible with `next export`.” fix it by editing `next.config.js` by adding

  ```js
  const nextConfig = {
    images: {
      unoptimized: true,
    },
  }
  ```

- Next.js are going to deprecate `next export`, so this solution might not work in the future
- I prefer Amplify Hosting to S3+CloudFront because I don’t want to maintain and it has advanced features like preview or [deploy on new branch](https://docs.amplify.aws/cli/teams/sandbox/).

### 4. Enable client-side log

#### Adding some log for test

Added `console.log` on `src/pages/index.tsx` when page render (or rerender)

```tsx
export default function Home() {
  useEffort(() => {
    console.log('log created on', Date.now())
  }, [])
}
```

Explain: log will show when this page render or rerender (in development it will show 2 lines of log)

#### Add auth resource

```sh
amplify add auth
```

Enter wizard input

- Do you want to use the default authentication and security configuration? -> "Default configuration"
- How do you want users to be able to sign in? -> "Email"
- Do you want to configure advanced settings? -> "No"

Verify

```sh
amplify status

    Current Environment: dev

┌──────────┬─────────────────────┬───────────┬───────────────────┐
│ Category │ Resource name       │ Operation │ Provider plugin   │
├──────────┼─────────────────────┼───────────┼───────────────────┤
│ Auth     │ ___________________ │ Create    │ awscloudformation │
├──────────┼─────────────────────┼───────────┼───────────────────┤
│ Hosting  │ amplifyhosting      │ No Change │ awscloudformation │
└──────────┴─────────────────────┴───────────┴───────────────────┘


Amplify hosting urls:
┌──────────────┬───────────────────────────────────────────┐
│ FrontEnd Env │ Domain                                    │
├──────────────┼───────────────────────────────────────────┤
│ dev          │ https://dev.______________.amplifyapp.com │
└──────────────┴───────────────────────────────────────────┘
```

We’ll see newly created resource “Auth”

Next, we’ll have to enter the AWS Console to modify and add some resources to enable the client-side log

#### Update identities setting

Enable unauthenticated identities on AWS Cognito / Federated Identities

- goto AWS Console / Cognito / Federated Identities (on the left menu)
- choose identities pool
- click edit identity pool (on the right side)
- checked on “Enable access to unauthenticated identities” under “Unauthenticated identities”
- don't forget to "Save"

#### Update identities role permission

Update role permission to allow user to writing log

- go to AWS Console / IAM / Roles (on the left menu)
- search role with project name. It should show 3 roles
  - `amplify-PROJECT-NAME-ENV-ID-authRole` — for authenticated identities
  - `amplify-PROJECT-NAME-ENV-ID-authRole-idp` — for lambda of authentication service
  - `amplify-PROJECT-NAME-ENV-ID-unauthRole` — for unauthenticated identities
- attach these policies into `authRole` and `unauthRole` to allowed write log on every resources of current account

  ```json
  {
    "Action": [
      "logs:DescribeLogGroup",
      "logs:DescribeLogStream",
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ],
    "Resource": "arn:aws:logs:*:ACCOUNT-ID:*",
    "Effect": "Allow"
  }
  ```

#### Create log group and log stream

[Create log group and log stream in CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html), we will use log group name and log stream name as config in our code.

Next, it's a time to write code 😝

### 5. Update code to use Amplify Logger

#### Create utilities file

create `src/aws.ts`

```ts
import { Amplify, AWSCloudWatchProvider, Logger } from 'aws-amplify'
import awsExports from './aws-exports'

// initiate Amplify
Amplify.configure(awsExports)

// initiate logger
const logger = new Logger('PROJECT-NAME', 'DEBUG')
Amplify.register(logger)
logger.addPluggable(new AWSCloudWatchProvider({
  logGroupName: 'LOG-GROUP-NAME',
  logStreamName: 'LOG-STREAM-NAME',
  region: awsExports.aws_project_region,
}))

export {
  logger,
}
```

#### Use logger

Switch from `console.log` to `logger.info` on `src/pages/index.tsx`

```tsx
import { logger } from '@/aws'

export default function Home() {
  useEffect(() => {
    logger.info('log created on', Date.now())
  }, [])
}
```

Test it

```sh
npm run dev
```

Then check log on CloudWatch and wait for the magic

### 6. Deploy

```sh
amplify publish
```

## Summary

- We have deployed static React into Amplify Hosting
- We have used Amplify Logger and CloudWatch provider to feed log to CloudWatch
- We have allowed the unauthorized users to write log

## Improvement

- Instead of manually modifying resources on AWS Console, use AWS CDK
- Use logger library instead of Amplify Logger i.g., loglevel (supported browser and Node.js)
- Add some images into blog post 😝