Save your browser console logs to AWS CloudWatch (Inspired by agea/console-cloud-watch)
npm i cloudwatch-front-logger
Go to CloudWatch console and create Log Group for this purpose.
Go to IAM Console and create restricted policy for CloudWatch Logs.
logs:CreateLogStream
logs:PutLogEvents
{
"Version": "2019-12-08",
"Statement": [
{
"Sid": "CloudWatchFrontLoggerSid",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:log-group:<LOG_GROUP_NAME>:*:*",
"arn:aws:logs:*:*:log-group:<LOG_GROUP_NAME>"
]
}
]
}
Go to IAM Console and create user with the restricted policy.
import { Logger } from 'cloudwatch-front-logger'
const accessKeyId = 'XXXXXXXXXXXXXXXXXXXX'
const secretAccessKey = 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'
const region = 'ap-northeast-1'
const logGroupName = '<LOG_GROUP_NAME>'
const logger = new Logger(accessKeyId, secretAccessKey, region, logGroupName)
logger.install()
Logs are collected from the following sources.
- Uncaught Error
console.error()
call- Manual
logger.onError()
call
(See integration examples)
By default, "anonymous"
is used for logStreamName
value.
If you wish allocating a unique stream for each user, you can use a method such as Canvas Fingerprint.
Pass a resolver function as logStreamNameResolver
option value on install()
call.
import FingerprintJS from 'fingerprintjs'
logger.install({
async logStreamNameResolver() {
const fp = await FingerprintJS.load()
const { visitorId } = await fp.get()
return visitorId
},
})
By default, messages are formatted into JSON string which has message
and type
.
{
"message": "Err: Something went wrong",
"type": "uncaught"
}
{
"message": "Something went wrong",
"type": "console",
"level": "error"
}
If you wish formatting them by yourself, pass a formatter function as messageFormatter
option value on install()
call. Note that you can cancel by returning null
from the fuunction.
import StackTrace from 'stacktrace-js'
logger.install({
async messageFormatter(e, info = { type: 'unknown' }) {
if (!e.message) {
return null
}
let stack = null
if (e.stack) {
stack = e.stack
try {
stack = await StackTrace.fromError(e, { offline: true })
} catch (_) {
}
}
return JSON.stringify({
message: e.message,
timestamp: new Date().getTime(),
userAgent: window.navigator.userAgent,
stack,
...info,
})
},
})
By default, localStorage
is used for caching logStreamName
and nextSequenceToken
.
Still localStorage
has only synchronous API, asynchronous interfaces are also supported.
If you need to change storage implementation from localStorage
, pass an instance as storage
option value on install()
call.
import { AsyncStorage } from 'react-native'
logger.install({
storage: AsyncStorage,
})
class LoggerComponent extends React.component {
componentDidCatch(e, info) {
this.props.logger.onError(e, {
...info,
type: 'react',
})
}
render() {
return this.props.children
}
}
<LoggerComponent logger={logger}>
<App />
</LoggerComponent>
const createLoggerMiddleware = (logger) => (store) => (next) => (action) => {
try {
return next(action)
} catch (e) {
logger.onError(e, {
action,
type: 'redux',
})
}
}
const store = createStore(
combineReducers(reducers),
applyMiddleware(createLoggerMiddleware(logger))
)