Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import React, { Component } from 'react'
- import CloudWatchLogs from 'aws-sdk/clients/cloudwatchlogs'
- import Fingerprint2 from 'fingerprintjs2'
- import StackTrace from 'stacktrace-js'
- import { promisify } from 'es6-promisify'
- export default class Logger {
- events = []
- originalConsole = null
- intervalId = null
- constructor(accessKeyId, secretAccessKey, region, group, levels = ['error'], interval = 10000, mute = false) {
- this.valid = accessKeyId && secretAccessKey && region && group
- this.client = new CloudWatchLogs({ accessKeyId, secretAccessKey, region })
- this.client.createLogStreamAsync = promisify(this.client.createLogStream)
- this.client.putLogEventsAsync = promisify(this.client.putLogEvents)
- this.group = group
- this.levels = levels
- this.interval = interval
- this.mute = mute
- }
- setCache(key, value) {
- global.localStorage.setItem(`ConsoleCloudWatch:${key}`, value)
- }
- getCache(key) {
- return global.localStorage.getItem(`ConsoleCloudWatch:${key}`)
- }
- deleteCache(key) {
- return global.localStorage.removeItem(`ConsoleCloudWatch:${key}`)
- }
- init() {
- const original = {}
- for (const level of this.levels) {
- original[level] = global.console[level]
- global.console[level] = (message, ...args) => {
- this.onError(message)
- if (!this.mute) {
- original[level](message, ...args)
- }
- }
- }
- this.originalConsole = original
- this.intervalId = global.setInterval(this.onInterval.bind(this), this.interval)
- global.addEventListener('error', this.onError.bind(this))
- }
- refresh() {
- this.deleteCache('key')
- this.deleteCache('sequenceToken')
- this.events.splice(0)
- }
- async onError(e, info = {}) {
- if (!this.valid) {
- return
- }
- this.events.push({
- message: await this.createPushMessageFromError(e, info),
- timestamp: new Date().getTime(),
- })
- }
- async onInterval() {
- if (!this.valid) {
- return
- }
- const pendingEvents = this.events.splice(0)
- if (!pendingEvents.length) {
- return
- }
- const key = await this.createOrRetrieveKey()
- if (!key) {
- return
- }
- const params = {
- logEvents: pendingEvents,
- logGroupName: this.group,
- logStreamName: key,
- }
- const sequenceToken = this.getCache('sequenceToken')
- if (sequenceToken) {
- params.sequenceToken = sequenceToken
- }
- let nextSequenceToken, match
- try {
- ({ nextSequenceToken } = await this.client.putLogEventsAsync(params))
- } catch (e) {
- if (!e || e.code !== 'InvalidSequenceTokenException' || !(match = e.message.match(/The next expected sequenceToken is: (\w+)/))) {
- this.originalConsole.error(e)
- this.refresh()
- return
- }
- }
- this.setCache('sequenceToken', nextSequenceToken || match[1])
- }
- async createOrRetrieveKey() {
- let key
- if ((key = this.getCache('key'))) {
- return key
- }
- try {
- key = await new Promise((resolve) => new Fingerprint2().get(resolve))
- await this.client.createLogStreamAsync({
- logGroupName: this.group,
- logStreamName: key,
- })
- } catch (e) {
- if (!e || e.code !== 'ResourceAlreadyExistsException') {
- this.originalConsole.error(e)
- this.refresh()
- return
- }
- }
- this.setCache('key', key)
- return key
- }
- async createPushMessageFromError(e, info = {}) {
- const message = e && e.message ? e.message : e
- const timestamp = new Date().getTime()
- const userAgent = global.navigator.userAgent
- let stack = null
- if (e && e.message && e.stack) {
- stack = e.stack
- try {
- stack = await StackTrace.fromError(e, { offline: true })
- } catch (_) {
- }
- }
- return JSON.stringify({
- message,
- timestamp,
- userAgent,
- stack,
- ...info,
- })
- }
- createLoggerMiddleware() {
- return (store) => (next) => (action) => {
- try {
- return next(action)
- } catch (e) {
- this.onError(e, {
- action,
- state: store.getState(),
- category: 'redux',
- })
- }
- }
- }
- createLoggerComponent() {
- const logger = this
- return class LoggerComponent extends Component {
- state = {
- e: null,
- }
- componentDidCatch(e, info) {
- this.setState({ e })
- logger.onError(e, {
- info,
- category: 'react',
- })
- }
- render() {
- if (this.state.e) {
- return <div>Fatal Error: {this.state.e.message}</div>
- }
- return this.props.children
- }
- }
- }
- }
Add Comment
Please, Sign In to add comment