Skip to content

Commit

Permalink
Merge pull request #214 from zorko-io/yzhbankov/gh-200
Browse files Browse the repository at this point in the history
gh-200. Client Side Logger.
  • Loading branch information
nesterone authored Feb 22, 2021
2 parents 83aa312 + 1909983 commit c642264
Show file tree
Hide file tree
Showing 33 changed files with 224 additions and 17 deletions.
7 changes: 5 additions & 2 deletions apps/web-portal-client/lib/api/index.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {createClient} from '@util-web-api-client'
import {ClientTypes, createClient} from '@util-web-api-client'

export const client = createClient()
export const client = createClient({
type: ClientTypes.Axios,
options: {baseURL: 'http://localhost:7777'},
})
1 change: 0 additions & 1 deletion apps/web-portal-client/lib/app/AppContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import {Switch, Route} from 'react-router-dom'

import {App} from './App'
import {LoginPage, PrivateRoute} from '../features/auth/containers'

Expand Down
2 changes: 1 addition & 1 deletion apps/web-portal-client/lib/components/Button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import className from 'classnames'

import {childrenPropTypes} from '../utils/childrenPropTypes'
import {childrenPropTypes} from '../utils'

import './button.css'

Expand Down
40 changes: 40 additions & 0 deletions apps/web-portal-client/lib/components/ErrorBoundary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, {Component} from 'react'
import {AppContext} from '../context'
import {childrenPropTypes} from '../utils'

export class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = {hasError: false}
}

static getDerivedStateFromError() {
return {hasError: true}
}

componentDidCatch(error) {
const {logger} = this.context
logger.error({message: error.message, stack: JSON.stringify(error.stack)})
}

render() {
const {hasError} = this.state
const {children} = this.props

if (hasError) {
return <h1>Something went wrong.</h1>
}

return children
}
}

ErrorBoundary.contextType = AppContext

ErrorBoundary.defaultProps = {
children: childrenPropTypes,
}

ErrorBoundary.propTypes = {
children: null,
}
2 changes: 1 addition & 1 deletion apps/web-portal-client/lib/components/Popover.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Tippy from '@tippy.js/react'
import 'tippy.js/dist/tippy.css'
import 'tippy.js/themes/light-border.css'

import {childrenPropTypes} from '../utils/childrenPropTypes'
import {childrenPropTypes} from '../utils'

export function Popover({title, text, duration, delay, children}) {
const [visible, setVisible] = useState(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import {childrenPropTypes} from '../../utils/childrenPropTypes'
import {childrenPropTypes} from '../../utils'

import {Dialog} from './Dialog'
import {Button} from '../Button'
Expand Down
2 changes: 1 addition & 1 deletion apps/web-portal-client/lib/components/dialog/Dialog.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import {childrenPropTypes} from '../../utils/childrenPropTypes'
import {childrenPropTypes} from '../../utils'

import {Button} from '../Button'
import {Image, ImageShapes} from '../Image'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import {childrenPropTypes} from '../../../utils/childrenPropTypes'
import {childrenPropTypes} from '../../../utils'

export function DesktopMenu({children}) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import className from 'classnames'
import {childrenPropTypes} from '../../../utils/childrenPropTypes'
import {childrenPropTypes} from '../../../utils'

export function MobileMenu({isShown, children}) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import {childrenPropTypes} from '../../../utils/childrenPropTypes'
import {childrenPropTypes} from '../../../utils'

export function Sidebar({title, children}) {
return (
Expand Down
1 change: 1 addition & 0 deletions apps/web-portal-client/lib/context/AppContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import {createContext} from 'react'

export const AppContext = createContext({
api: null,
logger: null,
})
3 changes: 2 additions & 1 deletion apps/web-portal-client/lib/context/AppProvider.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import {client} from '../api'
import {logger} from '../logger'
import {AppContext} from './AppContext'

export function AppProvider(props) {
const {children} = props
return <AppContext.Provider value={{api: client}}>{children}</AppContext.Provider>
return <AppContext.Provider value={{api: client, logger}}>{children}</AppContext.Provider>
}

AppProvider.propTypes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@ export function ValidatorExample() {

const validator = createValidator(rules)
const {error, result} = validator.parseSync(value)

return !error ? <div>{JSON.stringify(result)}</div> : <div>{error.message}</div>
}
5 changes: 4 additions & 1 deletion apps/web-portal-client/lib/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {Provider} from 'react-redux'
import {AppProvider} from './context'
import store from './store'
import {AppContainer} from './app/AppContainer'
import {ErrorBoundary} from './components/ErrorBoundary'
import './index.css'

ReactDOM.render(
<HashRouter>
<Provider store={store}>
<AppProvider>
<AppContainer />
<ErrorBoundary>
<AppContainer />
</ErrorBoundary>
</AppProvider>
</Provider>
</HashRouter>,
Expand Down
19 changes: 19 additions & 0 deletions apps/web-portal-client/lib/logger/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {createLogger} from '@zorko-io/util-logger'
import {MessageQueue} from '../utils'
import {client} from '../api'

const LOGS_MAX_SIZE = 10
const messageQueue = new MessageQueue(LOGS_MAX_SIZE)

export const logger = createLogger({
context: {
browser: {
write: async (message) => {
messageQueue.push(message)
if (message.level >= 50) {
await client.log.save(messageQueue.messages)
}
},
},
},
})
7 changes: 7 additions & 0 deletions apps/web-portal-client/lib/store/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {configureStore} from '@reduxjs/toolkit'
import {logger} from '../logger'
import authReducer from '../features/auth/slices'

const loggerMiddleware = () => (next) => (action) => {
logger.info(action)
return next(action)
}

export default configureStore({
reducer: {
auth: authReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(loggerMiddleware),
})
34 changes: 34 additions & 0 deletions apps/web-portal-client/lib/utils/MessageQueue.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export class MessageQueue {
#messages = []

constructor(size) {
this.size = size
}

enqueue(message) {
this.#messages.push(message)
}

dequeue() {
this.#messages.shift()
}

get isEmpty() {
return !!this.#messages.length
}

get queueLength() {
return this.#messages.length
}

push(message) {
if (!this.isEmpty && this.queueLength >= this.size) {
this.dequeue()
}
this.enqueue(message)
}

get messages() {
return this.#messages
}
}
2 changes: 2 additions & 0 deletions apps/web-portal-client/lib/utils/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './childrenPropTypes'
export * from './MessageQueue'
3 changes: 3 additions & 0 deletions apps/web-portal/lib/rest-api-v1/index.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import preview from './preview'
import auth from './auth'
import log from './log'

// TODO: Provide helpers/utilities for Rest API
// - jsdocs
Expand All @@ -10,9 +11,11 @@ export function route(deps) {
const router = deps.createRouter()
const previewController = preview(deps)
const authController = auth(deps)
const logController = log(deps)

router.get('/previews', previewController.list)
router.post('/auth/login', authController.login)
router.post('/log', logController.save)

return router
}
9 changes: 9 additions & 0 deletions apps/web-portal/lib/rest-api-v1/log.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {LogSave} from '../use-cases/log'

export default ({makeRunner}) => {
return {
save: makeRunner(LogSave, {
toParams: (req) => ({...req.body}),
}),
}
}
11 changes: 11 additions & 0 deletions apps/web-portal/lib/use-cases/log/LogSave.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {UseCase} from '@zorko-io/util-use-case'

// TODO: add integration tests for api/v1/log endpoint
export class LogSave extends UseCase {
// eslint-disable-next-line no-unused-vars
async run(logs) {
// TODO: wire with log 'pino' instance
console.log('logs ', logs)
return {}
}
}
1 change: 1 addition & 0 deletions apps/web-portal/lib/use-cases/log/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './LogSave'
7 changes: 5 additions & 2 deletions packages/util-logger/lib/createLogger.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ const cache = {}
/**
* Creates Logger
* @param {Object} options
* @param {LoggerTypes} [options.type] - logger type, PINO by default
* @param {<LoggerTypes>} [options.type] - logger type, PINO by default
* @param {Boolean} [options.isPrettyPrint] - turn on/off pretty print
* @param {Boolean} [options.shared] - create a shared, singleton instance, true by default
* @param {Object} [options.context] - contain logger context
* @returns {PinoLogger|MockLogger|ConsoleLogger|*}
*/

Expand All @@ -28,9 +29,10 @@ export function createLogger(
isPrettyPrint: false,
type: LoggerTypes.Pino,
shared: true,
context: {},
}
) {
let {type, shared} = options
let {type, shared, context} = options
let logger

type = type || LoggerTypes.Pino
Expand All @@ -46,6 +48,7 @@ export function createLogger(
if (type === LoggerTypes.Pino) {
logger = new PinoLogger({
isPrettyPrint: options.isPrettyPrint,
...context,
})
} else if (type === LoggerTypes.Console) {
logger = new ConsoleLogger()
Expand Down
2 changes: 1 addition & 1 deletion packages/util-logger/lib/types/MockLogger.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-unused-vars */
import {CoreLogger} from '../..'
import {CoreLogger} from '../core'

export class MockLogger extends CoreLogger {
info(...args) {}
Expand Down
3 changes: 2 additions & 1 deletion packages/util-logger/lib/types/PinoLogger.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pino from 'pino'
import {CoreLogger} from '../..'
import {CoreLogger} from '../core'

export class PinoLogger extends CoreLogger {
#pino = null
Expand Down Expand Up @@ -30,6 +30,7 @@ export class PinoLogger extends CoreLogger {
},
// TODO: configure log level over env vars
level: context.level || 'info',
browser: context.browser || null,
}

if (context.isPrettyPrint) {
Expand Down
8 changes: 8 additions & 0 deletions packages/util-web-api-client/lib/axios/AxiosClientApi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import {v4 as uuid} from 'uuid'
import {ClientApi} from '../core'
import {AxiosAuthApi} from './AxiosAuthApi'
import {AxiosPreviewApi} from './AxiosPreviewApi'
import {AxiosLogApi} from './AxiosLogApi'

export class AxiosClientApi extends ClientApi {
#auth = null

#preview = null

#log = null

constructor(options) {
super()
const instance = axios.create({
Expand All @@ -23,6 +26,7 @@ export class AxiosClientApi extends ClientApi {

this.#auth = new AxiosAuthApi(instance)
this.#preview = new AxiosPreviewApi(instance)
this.#log = new AxiosLogApi(instance)
}

get auth() {
Expand All @@ -32,4 +36,8 @@ export class AxiosClientApi extends ClientApi {
get preview() {
return this.#preview
}

get log() {
return this.#log
}
}
19 changes: 19 additions & 0 deletions packages/util-web-api-client/lib/axios/AxiosLogApi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable no-unused-vars */
import {LogApi} from '../core'

export class AxiosLogApi extends LogApi {
#http = null
/**
* @param http - Axios instance
*/

constructor(http) {
super()
this.#http = http
}

async save(logs) {
const response = await this.#http.post(`/api/v1/log`, logs)
return response ? response.data : {status: 1}
}
}
1 change: 1 addition & 0 deletions packages/util-web-api-client/lib/axios/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './AxiosClientApi'
export * from './AxiosAuthApi'
export * from './AxiosPreviewApi'
export * from './AxiosLogApi'
Loading

0 comments on commit c642264

Please sign in to comment.