mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-22 01:36:29 -05:00
Improve Logging (#1519)
Improve Logging Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
1172a1d7b8
commit
0e512531a0
41 changed files with 361 additions and 92 deletions
|
@ -162,6 +162,16 @@ React components
|
||||||
- should be named in [PascalCase](https://en.wikipedia.org/wiki/Pascal_case).
|
- should be named in [PascalCase](https://en.wikipedia.org/wiki/Pascal_case).
|
||||||
|
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
- Don't log directly to the console. Use our logging class `Logger` in "src/utils".
|
||||||
|
- Create one instance of `Logger` per file. Don't pass or share the instances.
|
||||||
|
- The first argument of the constructor is the scope. Use the name of the class or component whose behaviour you want to log or choose an explanatory name.
|
||||||
|
- If you want to add a sub scope (because e.g. you have two components that are similar or are used together, like the sub-classes of the iframe communicator), separate the main and sub scope with " > ".
|
||||||
|
- Scopes should be upper camel case.
|
||||||
|
- Log messages should never start with a lowercase letter.
|
||||||
|
- Log messages should never end with a colon or white space.
|
||||||
|
|
||||||
#### Example File: `increment-number-button.tsx`:
|
#### Example File: `increment-number-button.tsx`:
|
||||||
```typescript=
|
```typescript=
|
||||||
|
|
||||||
|
@ -172,6 +182,8 @@ export interface IncrementNumberButtonProps {
|
||||||
prefix: string
|
prefix: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logger = new Logger("IncrementNumberButton")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a button that contains a text and a number that gets incremented each time you click it.
|
* Shows a button that contains a text and a number that gets incremented each time you click it.
|
||||||
*
|
*
|
||||||
|
@ -180,7 +192,10 @@ export interface IncrementNumberButtonProps {
|
||||||
export const IncrementNumberButton: React.FC<IncrementNumberButtonProps> = ({ prefix }) => {
|
export const IncrementNumberButton: React.FC<IncrementNumberButtonProps> = ({ prefix }) => {
|
||||||
const [counter, setCounter] = useState(0)
|
const [counter, setCounter] = useState(0)
|
||||||
|
|
||||||
const incrementCounter = useCallback(() => setCounter((lastCounter) => lastCounter + 1), [])
|
const incrementCounter = useCallback(() => {
|
||||||
|
setCounter((lastCounter) => lastCounter + 1)
|
||||||
|
logger.info("Increased counter")
|
||||||
|
}, [])
|
||||||
|
|
||||||
return <button onClick={incrementCounter}>{prefix}: {counter}</button>
|
return <button onClick={incrementCounter}>{prefix}: {counter}</button>
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,9 @@ import { createSetUpTaskList, InitTask } from './initializers'
|
||||||
import { LoadingScreen } from './loading-screen'
|
import { LoadingScreen } from './loading-screen'
|
||||||
import { useCustomizeAssetsUrl } from '../../hooks/common/use-customize-assets-url'
|
import { useCustomizeAssetsUrl } from '../../hooks/common/use-customize-assets-url'
|
||||||
import { useFrontendAssetsUrl } from '../../hooks/common/use-frontend-assets-url'
|
import { useFrontendAssetsUrl } from '../../hooks/common/use-frontend-assets-url'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('ApplicationLoader')
|
||||||
|
|
||||||
export const ApplicationLoader: React.FC = ({ children }) => {
|
export const ApplicationLoader: React.FC = ({ children }) => {
|
||||||
const frontendAssetsUrl = useFrontendAssetsUrl()
|
const frontendAssetsUrl = useFrontendAssetsUrl()
|
||||||
|
@ -36,7 +39,7 @@ export const ApplicationLoader: React.FC = ({ children }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const task of initTasks) {
|
for (const task of initTasks) {
|
||||||
runTask(task.task).catch((reason: Error) => {
|
runTask(task.task).catch((reason: Error) => {
|
||||||
console.error(reason)
|
log.error('Error while initialising application', reason)
|
||||||
setFailedTitle(task.name)
|
setFailedTitle(task.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
|
|
||||||
import { setBanner } from '../../../redux/banner/methods'
|
import { setBanner } from '../../../redux/banner/methods'
|
||||||
import { defaultFetchConfig } from '../../../api/utils'
|
import { defaultFetchConfig } from '../../../api/utils'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
export const BANNER_LOCAL_STORAGE_KEY = 'banner.lastModified'
|
export const BANNER_LOCAL_STORAGE_KEY = 'banner.lastModified'
|
||||||
|
const log = new Logger('Banner')
|
||||||
|
|
||||||
export const fetchAndSetBanner = async (customizeAssetsUrl: string): Promise<void> => {
|
export const fetchAndSetBanner = async (customizeAssetsUrl: string): Promise<void> => {
|
||||||
const cachedLastModified = window.localStorage.getItem(BANNER_LOCAL_STORAGE_KEY)
|
const cachedLastModified = window.localStorage.getItem(BANNER_LOCAL_STORAGE_KEY)
|
||||||
|
@ -42,7 +44,7 @@ export const fetchAndSetBanner = async (customizeAssetsUrl: string): Promise<voi
|
||||||
|
|
||||||
const lastModified = response.headers.get('Last-Modified')
|
const lastModified = response.headers.get('Last-Modified')
|
||||||
if (!lastModified) {
|
if (!lastModified) {
|
||||||
console.warn("'Last-Modified' not found for banner.txt!")
|
log.warn("'Last-Modified' not found for banner.txt!")
|
||||||
}
|
}
|
||||||
|
|
||||||
setBanner({
|
setBanner({
|
||||||
|
|
|
@ -9,12 +9,15 @@ import { Overlay, Tooltip } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { ShowIf } from '../show-if/show-if'
|
import { ShowIf } from '../show-if/show-if'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
export interface CopyOverlayProps {
|
export interface CopyOverlayProps {
|
||||||
content: string
|
content: string
|
||||||
clickComponent: RefObject<HTMLElement>
|
clickComponent: RefObject<HTMLElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const log = new Logger('CopyOverlay')
|
||||||
|
|
||||||
export const CopyOverlay: React.FC<CopyOverlayProps> = ({ content, clickComponent }) => {
|
export const CopyOverlay: React.FC<CopyOverlayProps> = ({ content, clickComponent }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [showCopiedTooltip, setShowCopiedTooltip] = useState(false)
|
const [showCopiedTooltip, setShowCopiedTooltip] = useState(false)
|
||||||
|
@ -27,9 +30,9 @@ export const CopyOverlay: React.FC<CopyOverlayProps> = ({ content, clickComponen
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setError(false)
|
setError(false)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error: Error) => {
|
||||||
setError(true)
|
setError(true)
|
||||||
console.error("couldn't copy")
|
log.error('Copy failed', error)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setShowCopiedTooltip(true)
|
setShowCopiedTooltip(true)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next'
|
||||||
import { ForkAwesomeIcon } from '../../fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../fork-awesome/fork-awesome-icon'
|
||||||
import { ShowIf } from '../../show-if/show-if'
|
import { ShowIf } from '../../show-if/show-if'
|
||||||
import { CopyOverlay } from '../copy-overlay'
|
import { CopyOverlay } from '../copy-overlay'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
export interface CopyableFieldProps {
|
export interface CopyableFieldProps {
|
||||||
content: string
|
content: string
|
||||||
|
@ -17,6 +18,8 @@ export interface CopyableFieldProps {
|
||||||
url?: string
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const log = new Logger('CopyableField')
|
||||||
|
|
||||||
export const CopyableField: React.FC<CopyableFieldProps> = ({ content, nativeShareButton, url }) => {
|
export const CopyableField: React.FC<CopyableFieldProps> = ({ content, nativeShareButton, url }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const copyButton = useRef<HTMLButtonElement>(null)
|
const copyButton = useRef<HTMLButtonElement>(null)
|
||||||
|
@ -27,8 +30,8 @@ export const CopyableField: React.FC<CopyableFieldProps> = ({ content, nativeSha
|
||||||
text: content,
|
text: content,
|
||||||
url: url
|
url: url
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
console.error('Native sharing failed: ', err)
|
log.error('Native sharing failed', error)
|
||||||
})
|
})
|
||||||
}, [content, url])
|
}, [content, url])
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ import { Revision } from '../../../../api/revisions/types'
|
||||||
import { getUserById } from '../../../../api/users'
|
import { getUserById } from '../../../../api/users'
|
||||||
import { UserResponse } from '../../../../api/users/types'
|
import { UserResponse } from '../../../../api/users/types'
|
||||||
import { download } from '../../../common/download/download'
|
import { download } from '../../../common/download/download'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('RevisionsUtils')
|
||||||
|
|
||||||
export const downloadRevision = (noteId: string, revision: Revision | null): void => {
|
export const downloadRevision = (noteId: string, revision: Revision | null): void => {
|
||||||
if (!revision) {
|
if (!revision) {
|
||||||
|
@ -26,7 +29,7 @@ export const getUserDataForRevision = (authors: string[]): UserResponse[] => {
|
||||||
.then((userData) => {
|
.then((userData) => {
|
||||||
users.push(userData)
|
users.push(userData)
|
||||||
})
|
})
|
||||||
.catch((error) => console.error(error))
|
.catch((error) => log.error(error))
|
||||||
})
|
})
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry'
|
||||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
import { EditorDocumentRenderer } from './editor-document-renderer/editor-document-renderer'
|
import { EditorDocumentRenderer } from './editor-document-renderer/editor-document-renderer'
|
||||||
import { EditorToRendererCommunicatorContextProvider } from './render-context/editor-to-renderer-communicator-context-provider'
|
import { EditorToRendererCommunicatorContextProvider } from './render-context/editor-to-renderer-communicator-context-provider'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
export interface EditorPagePathParams {
|
export interface EditorPagePathParams {
|
||||||
id: string
|
id: string
|
||||||
|
@ -39,6 +40,8 @@ export enum ScrollSource {
|
||||||
RENDERER
|
RENDERER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const log = new Logger('EditorPage')
|
||||||
|
|
||||||
export const EditorPage: React.FC = () => {
|
export const EditorPage: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const scrollSource = useRef<ScrollSource>(ScrollSource.EDITOR)
|
const scrollSource = useRef<ScrollSource>(ScrollSource.EDITOR)
|
||||||
|
@ -55,7 +58,7 @@ export const EditorPage: React.FC = () => {
|
||||||
if (scrollSource.current === ScrollSource.RENDERER && editorSyncScroll) {
|
if (scrollSource.current === ScrollSource.RENDERER && editorSyncScroll) {
|
||||||
setScrollState((old) => {
|
setScrollState((old) => {
|
||||||
const newState = { editorScrollState: newScrollState, rendererScrollState: old.rendererScrollState }
|
const newState = { editorScrollState: newScrollState, rendererScrollState: old.rendererScrollState }
|
||||||
console.debug('[EditorPage] set scroll state because of renderer scroll', newState)
|
log.debug('Set scroll state because of renderer scroll', newState)
|
||||||
return newState
|
return newState
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -68,7 +71,7 @@ export const EditorPage: React.FC = () => {
|
||||||
if (scrollSource.current === ScrollSource.EDITOR && editorSyncScroll) {
|
if (scrollSource.current === ScrollSource.EDITOR && editorSyncScroll) {
|
||||||
setScrollState((old) => {
|
setScrollState((old) => {
|
||||||
const newState = { rendererScrollState: newScrollState, editorScrollState: old.editorScrollState }
|
const newState = { rendererScrollState: newScrollState, editorScrollState: old.editorScrollState }
|
||||||
console.debug('[EditorPage] set scroll state because of editor scroll', newState)
|
log.debug('Set scroll state because of editor scroll', newState)
|
||||||
return newState
|
return newState
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -87,12 +90,12 @@ export const EditorPage: React.FC = () => {
|
||||||
|
|
||||||
const setRendererToScrollSource = useCallback(() => {
|
const setRendererToScrollSource = useCallback(() => {
|
||||||
scrollSource.current = ScrollSource.RENDERER
|
scrollSource.current = ScrollSource.RENDERER
|
||||||
console.debug('[EditorPage] Make renderer scroll source')
|
log.debug('Make renderer scroll source')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setEditorToScrollSource = useCallback(() => {
|
const setEditorToScrollSource = useCallback(() => {
|
||||||
scrollSource.current = ScrollSource.EDITOR
|
scrollSource.current = ScrollSource.EDITOR
|
||||||
console.debug('[EditorPage] Make editor scroll source')
|
log.debug('Make editor scroll source')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useNotificationTest()
|
useNotificationTest()
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
import { Editor, Hint, Hints, Pos } from 'codemirror'
|
import { Editor, Hint, Hints, Pos } from 'codemirror'
|
||||||
import { findWordAtCursor, generateHintListByPrefix, Hinter } from './index'
|
import { findWordAtCursor, generateHintListByPrefix, Hinter } from './index'
|
||||||
import { showErrorNotification } from '../../../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../../../redux/ui-notifications/methods'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
type highlightJsImport = typeof import('../../../common/hljs/hljs')
|
type highlightJsImport = typeof import('../../../common/hljs/hljs')
|
||||||
|
|
||||||
|
const log = new Logger('Autocompletion > CodeBlock')
|
||||||
const wordRegExp = /^```((\w|-|_|\+)*)$/
|
const wordRegExp = /^```((\w|-|_|\+)*)$/
|
||||||
let allSupportedLanguages: string[] = []
|
let allSupportedLanguages: string[] = []
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ const loadHighlightJs = async (): Promise<highlightJsImport | null> => {
|
||||||
return await import('../../../common/hljs/hljs')
|
return await import('../../../common/hljs/hljs')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorNotification('common.errorWhileLoadingLibrary', { name: 'highlight.js' })(error as Error)
|
showErrorNotification('common.errorWhileLoadingLibrary', { name: 'highlight.js' })(error as Error)
|
||||||
console.error("can't load highlight js", error)
|
log.error('Error while loading highlight.js', error)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,11 @@ import { Emoji, EmojiClickEventDetail, NativeEmoji } from 'emoji-picker-element/
|
||||||
import { emojiPickerConfig } from '../tool-bar/emoji-picker/emoji-picker'
|
import { emojiPickerConfig } from '../tool-bar/emoji-picker/emoji-picker'
|
||||||
import { getEmojiIcon, getEmojiShortCode } from '../tool-bar/utils/emojiUtils'
|
import { getEmojiIcon, getEmojiShortCode } from '../tool-bar/utils/emojiUtils'
|
||||||
import { findWordAtCursor, Hinter } from './index'
|
import { findWordAtCursor, Hinter } from './index'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
const emojiIndex = new Database(emojiPickerConfig)
|
const emojiIndex = new Database(emojiPickerConfig)
|
||||||
const emojiWordRegex = /^:([\w-_+]*)$/
|
const emojiWordRegex = /^:([\w-_+]*)$/
|
||||||
|
const log = new Logger('Autocompletion > Emoji')
|
||||||
|
|
||||||
const findEmojiInDatabase = async (emojiIndex: Database, term: string): Promise<Emoji[]> => {
|
const findEmojiInDatabase = async (emojiIndex: Database, term: string): Promise<Emoji[]> => {
|
||||||
try {
|
try {
|
||||||
|
@ -26,7 +28,7 @@ const findEmojiInDatabase = async (emojiIndex: Database, term: string): Promise<
|
||||||
return queryResult
|
return queryResult
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
log.error('Error while searching for emoji', term, error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import i18n from 'i18next'
|
||||||
import { uploadFile } from '../../../api/media'
|
import { uploadFile } from '../../../api/media'
|
||||||
import { store } from '../../../redux'
|
import { store } from '../../../redux'
|
||||||
import { supportedMimeTypes } from '../../common/upload-image-mimetypes'
|
import { supportedMimeTypes } from '../../common/upload-image-mimetypes'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('File Uploader Handler')
|
||||||
|
|
||||||
export const handleUpload = (file: File, editor: Editor): void => {
|
export const handleUpload = (file: File, editor: Editor): void => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
|
@ -30,7 +33,7 @@ export const handleUpload = (file: File, editor: Editor): void => {
|
||||||
insertCode(`![](${link})`)
|
insertCode(`![](${link})`)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('error while uploading file', error)
|
log.error('error while uploading file', error)
|
||||||
insertCode('')
|
insertCode('')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import { useParams } from 'react-router'
|
||||||
import { getNote } from '../../../api/notes'
|
import { getNote } from '../../../api/notes'
|
||||||
import { setNoteDataFromServer } from '../../../redux/note-details/methods'
|
import { setNoteDataFromServer } from '../../../redux/note-details/methods'
|
||||||
import { EditorPagePathParams } from '../editor-page'
|
import { EditorPagePathParams } from '../editor-page'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('Load Note From Server')
|
||||||
|
|
||||||
export const useLoadNoteFromServer = (): [boolean, boolean] => {
|
export const useLoadNoteFromServer = (): [boolean, boolean] => {
|
||||||
const { id } = useParams<EditorPagePathParams>()
|
const { id } = useParams<EditorPagePathParams>()
|
||||||
|
@ -21,9 +24,9 @@ export const useLoadNoteFromServer = (): [boolean, boolean] => {
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
setNoteDataFromServer(note)
|
setNoteDataFromServer(note)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((error) => {
|
||||||
setError(true)
|
setError(true)
|
||||||
console.error(e)
|
log.error('Error while fetching note from server', error)
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
import { RefObject, useCallback, useRef } from 'react'
|
import { RefObject, useCallback, useRef } from 'react'
|
||||||
import { EditorToRendererCommunicator } from '../../../render-page/window-post-message-communicator/editor-to-renderer-communicator'
|
import { EditorToRendererCommunicator } from '../../../render-page/window-post-message-communicator/editor-to-renderer-communicator'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('IframeLoader')
|
||||||
|
|
||||||
export const useOnIframeLoad = (
|
export const useOnIframeLoad = (
|
||||||
frameReference: RefObject<HTMLIFrameElement>,
|
frameReference: RefObject<HTMLIFrameElement>,
|
||||||
|
@ -29,7 +32,7 @@ export const useOnIframeLoad = (
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
onNavigateAway()
|
onNavigateAway()
|
||||||
console.error('Navigated away from unknown URL')
|
log.error('Navigated away from unknown URL')
|
||||||
frame.src = renderPageUrl
|
frame.src = renderPageUrl
|
||||||
sendToRenderPage.current = true
|
sendToRenderPage.current = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { MutableRefObject, useCallback, useEffect, useRef } from 'react'
|
import React, { MutableRefObject, useCallback, useEffect, useRef } from 'react'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('UploadInput')
|
||||||
|
|
||||||
export interface UploadInputProps {
|
export interface UploadInputProps {
|
||||||
onLoad: (file: File) => Promise<void>
|
onLoad: (file: File) => Promise<void>
|
||||||
|
@ -30,7 +33,7 @@ export const UploadInput: React.FC<UploadInputProps> = ({ onLoad, acceptedFiles,
|
||||||
fileInput.value = ''
|
fileInput.value = ''
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
log.error('Error while uploading file', error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
fileInput.click()
|
fileInput.click()
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { dispatchUiNotification } from '../../redux/ui-notifications/methods'
|
import { dispatchUiNotification } from '../../redux/ui-notifications/methods'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
const localStorageKey = 'dontshowtestnotification'
|
const localStorageKey = 'dontshowtestnotification'
|
||||||
|
const log = new Logger('Notification Test')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawns a notification to test the system. Only for tech demo show case.
|
* Spawns a notification to test the system. Only for tech demo show case.
|
||||||
|
@ -17,7 +19,7 @@ export const useNotificationTest = (): void => {
|
||||||
if (window.localStorage.getItem(localStorageKey)) {
|
if (window.localStorage.getItem(localStorageKey)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.debug('[Notifications] Dispatched test notification')
|
log.debug('Dispatched test notification')
|
||||||
void dispatchUiNotification('notificationTest.title', 'notificationTest.content', {
|
void dispatchUiNotification('notificationTest.title', 'notificationTest.content', {
|
||||||
icon: 'info-circle',
|
icon: 'info-circle',
|
||||||
buttons: [
|
buttons: [
|
||||||
|
|
|
@ -10,6 +10,9 @@ import links from '../../links.json'
|
||||||
import frontendVersion from '../../version.json'
|
import frontendVersion from '../../version.json'
|
||||||
import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
|
||||||
import { ExternalLink } from '../common/links/external-link'
|
import { ExternalLink } from '../common/links/external-link'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('ErrorBoundary')
|
||||||
|
|
||||||
export class ErrorBoundary extends Component {
|
export class ErrorBoundary extends Component {
|
||||||
state: {
|
state: {
|
||||||
|
@ -27,8 +30,7 @@ export class ErrorBoundary extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
||||||
console.error('error caught', error)
|
log.error('Error catched', error, errorInfo)
|
||||||
console.error('additional information', errorInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshPage(): void {
|
refreshPage(): void {
|
||||||
|
|
|
@ -8,7 +8,9 @@ import { Settings } from 'luxon'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { Form } from 'react-bootstrap'
|
import { Form } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('LanguagePicker')
|
||||||
const languages = {
|
const languages = {
|
||||||
en: 'English',
|
en: 'English',
|
||||||
'zh-CN': '简体中文',
|
'zh-CN': '简体中文',
|
||||||
|
@ -61,7 +63,7 @@ export const LanguagePicker: React.FC = () => {
|
||||||
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const language = event.currentTarget.value
|
const language = event.currentTarget.value
|
||||||
Settings.defaultLocale = language
|
Settings.defaultLocale = language
|
||||||
i18n.changeLanguage(language).catch((error) => console.error('Error while switching language', error))
|
i18n.changeLanguage(language).catch((error) => log.error('Error while switching language', error))
|
||||||
},
|
},
|
||||||
[i18n]
|
[i18n]
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MarkdownIt from 'markdown-it/lib'
|
import MarkdownIt from 'markdown-it/lib'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('MarkdownItParserDebugger')
|
||||||
|
|
||||||
export const MarkdownItParserDebugger: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
|
export const MarkdownItParserDebugger: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
md.core.ruler.push('test', (state) => {
|
md.core.ruler.push('test', (state) => {
|
||||||
console.log(state)
|
log.debug('Current state', state)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import './abc.scss'
|
import './abc.scss'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('AbcFrame')
|
||||||
|
|
||||||
export interface AbcFrameProps {
|
export interface AbcFrameProps {
|
||||||
code: string
|
code: string
|
||||||
|
@ -23,8 +26,8 @@ export const AbcFrame: React.FC<AbcFrameProps> = ({ code }) => {
|
||||||
.then((imp) => {
|
.then((imp) => {
|
||||||
imp.renderAbc(actualContainer, code, {})
|
imp.renderAbc(actualContainer, code, {})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
console.error('error while loading abcjs')
|
log.error('Error while loading abcjs', error)
|
||||||
})
|
})
|
||||||
}, [code])
|
}, [code])
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useIsDarkModeActivated } from '../../../../../hooks/common/use-is-dark-mode-activated'
|
import { useIsDarkModeActivated } from '../../../../../hooks/common/use-is-dark-mode-activated'
|
||||||
|
import { Logger } from '../../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('FlowChart')
|
||||||
|
|
||||||
export interface FlowChartProps {
|
export interface FlowChartProps {
|
||||||
code: string
|
code: string
|
||||||
|
@ -43,7 +46,7 @@ export const FlowChart: React.FC<FlowChartProps> = ({ code }) => {
|
||||||
setError(true)
|
setError(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => console.error('error while loading flowchart.js'))
|
.catch((error) => log.error('Error while loading flowchart.js', error))
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
Array.from(currentDiagramRef.children).forEach((value) => value.remove())
|
Array.from(currentDiagramRef.children).forEach((value) => value.remove())
|
||||||
|
|
|
@ -8,6 +8,9 @@ import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
|
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('GraphvizFrame')
|
||||||
|
|
||||||
export interface GraphvizFrameProps {
|
export interface GraphvizFrameProps {
|
||||||
code: string
|
code: string
|
||||||
|
@ -22,7 +25,7 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setError(error)
|
setError(error)
|
||||||
console.error(error)
|
log.error(error)
|
||||||
container.current.querySelectorAll('svg').forEach((child) => child.remove())
|
container.current.querySelectorAll('svg').forEach((child) => child.remove())
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
@ -53,8 +56,8 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
|
||||||
showError(error as string)
|
showError(error as string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
console.error('error while loading graphviz')
|
log.error('Error while loading graphviz', error)
|
||||||
})
|
})
|
||||||
}, [code, error, frontendBaseUrl, showError])
|
}, [code, error, frontendBaseUrl, showError])
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ import convertHtmlToReact from '@hedgedoc/html-to-react'
|
||||||
import { CopyToClipboardButton } from '../../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
|
import { CopyToClipboardButton } from '../../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
|
||||||
import '../../../utils/button-inside.scss'
|
import '../../../utils/button-inside.scss'
|
||||||
import './highlighted-code.scss'
|
import './highlighted-code.scss'
|
||||||
|
import { Logger } from '../../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('HighlightedCode')
|
||||||
|
|
||||||
export interface HighlightedCodeProps {
|
export interface HighlightedCodeProps {
|
||||||
code: string
|
code: string
|
||||||
|
@ -55,8 +58,8 @@ export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language
|
||||||
))
|
))
|
||||||
setDom(replacedDom)
|
setDom(replacedDom)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
console.error('error while loading highlight.js')
|
log.error('Error while loading highlight.js', error)
|
||||||
})
|
})
|
||||||
}, [code, language, startLineNumber])
|
}, [code, language, startLineNumber])
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { getProxiedUrl } from '../../../../api/media'
|
import { getProxiedUrl } from '../../../../api/media'
|
||||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('ProxyImageFrame')
|
||||||
|
|
||||||
export const ProxyImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ src, title, alt, ...props }) => {
|
export const ProxyImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ src, title, alt, ...props }) => {
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
|
@ -18,7 +21,7 @@ export const ProxyImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>
|
||||||
}
|
}
|
||||||
getProxiedUrl(src)
|
getProxiedUrl(src)
|
||||||
.then((proxyResponse) => setImageUrl(proxyResponse.src))
|
.then((proxyResponse) => setImageUrl(proxyResponse.src))
|
||||||
.catch((err) => console.error(err))
|
.catch((err) => log.error(err))
|
||||||
}, [imageProxyEnabled, src])
|
}, [imageProxyEnabled, src])
|
||||||
|
|
||||||
return <img src={imageProxyEnabled ? imageUrl : src ?? ''} title={title ?? alt ?? ''} alt={alt} {...props} />
|
return <img src={imageProxyEnabled ? imageUrl : src ?? ''} title={title ?? alt ?? ''} alt={alt} {...props} />
|
||||||
|
|
|
@ -8,6 +8,9 @@ import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { LockButton } from '../../../common/lock-button/lock-button'
|
import { LockButton } from '../../../common/lock-button/lock-button'
|
||||||
import '../../utils/button-inside.scss'
|
import '../../utils/button-inside.scss'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('MarkmapFrame')
|
||||||
|
|
||||||
export interface MarkmapFrameProps {
|
export interface MarkmapFrameProps {
|
||||||
code: string
|
code: string
|
||||||
|
@ -54,11 +57,11 @@ export const MarkmapFrame: React.FC<MarkmapFrameProps> = ({ code }) => {
|
||||||
actualContainer.appendChild(svg)
|
actualContainer.appendChild(svg)
|
||||||
markmapLoader(svg, code)
|
markmapLoader(svg, code)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
log.error(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
console.error('error while loading markmap')
|
log.error('Error while loading markmap', error)
|
||||||
})
|
})
|
||||||
}, [code])
|
}, [code])
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,9 @@ import { Alert } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import './mermaid.scss'
|
import './mermaid.scss'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('MermaidChart')
|
||||||
export interface MermaidChartProps {
|
export interface MermaidChartProps {
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
@ -32,8 +34,8 @@ export const MermaidChart: React.FC<MermaidChartProps> = ({ code }) => {
|
||||||
mermaid.default.initialize({ startOnLoad: false })
|
mermaid.default.initialize({ startOnLoad: false })
|
||||||
mermaidInitialized = true
|
mermaidInitialized = true
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
console.error('error while loading mermaid')
|
log.error('Error while loading mermaid', error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -41,7 +43,7 @@ export const MermaidChart: React.FC<MermaidChartProps> = ({ code }) => {
|
||||||
const showError = useCallback(
|
const showError = useCallback(
|
||||||
(error: string) => {
|
(error: string) => {
|
||||||
setError(error)
|
setError(error)
|
||||||
console.error(error)
|
log.error(error)
|
||||||
if (!diagramContainer.current) {
|
if (!diagramContainer.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ import { IconName } from '../../../common/fork-awesome/types'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import './one-click-embedding.scss'
|
import './one-click-embedding.scss'
|
||||||
import { ProxyImageFrame } from '../image/proxy-image-frame'
|
import { ProxyImageFrame } from '../image/proxy-image-frame'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('OneClickEmbedding')
|
||||||
|
|
||||||
interface OneClickFrameProps {
|
interface OneClickFrameProps {
|
||||||
onImageFetch?: () => Promise<string>
|
onImageFetch?: () => Promise<string>
|
||||||
|
@ -52,7 +55,7 @@ export const OneClickEmbedding: React.FC<OneClickFrameProps> = ({
|
||||||
setPreviewImageUrl(imageLink)
|
setPreviewImageUrl(imageLink)
|
||||||
})
|
})
|
||||||
.catch((message) => {
|
.catch((message) => {
|
||||||
console.error(message)
|
log.error(message)
|
||||||
})
|
})
|
||||||
}, [onImageFetch])
|
}, [onImageFetch])
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ import { Alert } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { VisualizationSpec } from 'vega-embed'
|
import { VisualizationSpec } from 'vega-embed'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
|
import { Logger } from '../../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('VegaChart')
|
||||||
|
|
||||||
export interface VegaChartProps {
|
export interface VegaChartProps {
|
||||||
code: string
|
code: string
|
||||||
|
@ -23,7 +26,7 @@ export const VegaChart: React.FC<VegaChartProps> = ({ code }) => {
|
||||||
if (!diagramContainer.current) {
|
if (!diagramContainer.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.error(error)
|
log.error(error)
|
||||||
setError(error)
|
setError(error)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
@ -58,8 +61,8 @@ export const VegaChart: React.FC<VegaChartProps> = ({ code }) => {
|
||||||
showError(t('renderer.vega-lite.errorJson'))
|
showError(t('renderer.vega-lite.errorJson'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
console.error('error while loading vega-light')
|
log.error('Error while loading vega-light', error)
|
||||||
})
|
})
|
||||||
}, [code, showError, t])
|
}, [code, showError, t])
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,10 @@ import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { IconName } from '../common/fork-awesome/types'
|
import { IconName } from '../common/fork-awesome/types'
|
||||||
import { dismissUiNotification } from '../../redux/ui-notifications/methods'
|
import { dismissUiNotification } from '../../redux/ui-notifications/methods'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
const STEPS_PER_SECOND = 10
|
const STEPS_PER_SECOND = 10
|
||||||
|
const log = new Logger('UiNotificationToast')
|
||||||
|
|
||||||
export interface UiNotificationProps extends UiNotification {
|
export interface UiNotificationProps extends UiNotification {
|
||||||
notificationId: number
|
notificationId: number
|
||||||
|
@ -42,7 +44,7 @@ export const UiNotificationToast: React.FC<UiNotificationProps> = ({
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const dismissThisNotification = useCallback(() => {
|
const dismissThisNotification = useCallback(() => {
|
||||||
console.debug(`[Notifications] Dismissed notification ${notificationId}`)
|
log.debug(`Dismissed notification ${notificationId}`)
|
||||||
dismissUiNotification(notificationId)
|
dismissUiNotification(notificationId)
|
||||||
}, [notificationId])
|
}, [notificationId])
|
||||||
|
|
||||||
|
@ -50,7 +52,7 @@ export const UiNotificationToast: React.FC<UiNotificationProps> = ({
|
||||||
if (dismissed || !!interval.current) {
|
if (dismissed || !!interval.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.debug(`[Notifications] Show notification ${notificationId}`)
|
log.debug(`Show notification ${notificationId}`)
|
||||||
setEta(durationInSecond * STEPS_PER_SECOND)
|
setEta(durationInSecond * STEPS_PER_SECOND)
|
||||||
interval.current = setInterval(
|
interval.current = setInterval(
|
||||||
() =>
|
() =>
|
||||||
|
|
|
@ -15,6 +15,9 @@ import { IconButton } from '../../common/icon-button/icon-button'
|
||||||
import { CommonModal } from '../../common/modals/common-modal'
|
import { CommonModal } from '../../common/modals/common-modal'
|
||||||
import { DeletionModal } from '../../common/modals/deletion-modal'
|
import { DeletionModal } from '../../common/modals/deletion-modal'
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('ProfileAccessTokens')
|
||||||
|
|
||||||
export const ProfileAccessTokens: React.FC = () => {
|
export const ProfileAccessTokens: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -37,7 +40,7 @@ export const ProfileAccessTokens: React.FC = () => {
|
||||||
setNewTokenLabel('')
|
setNewTokenLabel('')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
log.error(error)
|
||||||
setError(true)
|
setError(true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -50,7 +53,7 @@ export const ProfileAccessTokens: React.FC = () => {
|
||||||
setSelectedForDeletion(0)
|
setSelectedForDeletion(0)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
log.error(error)
|
||||||
setError(true)
|
setError(true)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
@ -74,7 +77,7 @@ export const ProfileAccessTokens: React.FC = () => {
|
||||||
setAccessTokens(tokens)
|
setAccessTokens(tokens)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err)
|
log.error(err)
|
||||||
setError(true)
|
setError(true)
|
||||||
})
|
})
|
||||||
}, [showAddedModal])
|
}, [showAddedModal])
|
||||||
|
|
|
@ -13,6 +13,9 @@ import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
import { TranslatedExternalLink } from '../common/links/translated-external-link'
|
import { TranslatedExternalLink } from '../common/links/translated-external-link'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { fetchAndSetUser } from '../login-page/auth/utils'
|
import { fetchAndSetUser } from '../login-page/auth/utils'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('RegisterPage')
|
||||||
|
|
||||||
export enum RegisterError {
|
export enum RegisterError {
|
||||||
NONE = 'none',
|
NONE = 'none',
|
||||||
|
@ -37,7 +40,7 @@ export const RegisterPage: React.FC = () => {
|
||||||
doInternalRegister(username, password)
|
doInternalRegister(username, password)
|
||||||
.then(() => fetchAndSetUser())
|
.then(() => fetchAndSetUser())
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
console.error(err)
|
log.error(err)
|
||||||
setError(err.message === RegisterError.USERNAME_EXISTING ? err.message : RegisterError.OTHER)
|
setError(err.message === RegisterError.USERNAME_EXISTING ? err.message : RegisterError.OTHER)
|
||||||
})
|
})
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { WindowPostMessageCommunicator } from './window-post-message-communicator'
|
import { WindowPostMessageCommunicator } from './window-post-message-communicator'
|
||||||
import { CommunicationMessages, EditorToRendererMessageType, RendererToEditorMessageType } from './rendering-message'
|
import { CommunicationMessages, EditorToRendererMessageType, RendererToEditorMessageType } from './rendering-message'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The communicator that is used to send messages from the editor to the renderer.
|
* The communicator that is used to send messages from the editor to the renderer.
|
||||||
|
@ -15,7 +16,7 @@ export class EditorToRendererCommunicator extends WindowPostMessageCommunicator<
|
||||||
EditorToRendererMessageType,
|
EditorToRendererMessageType,
|
||||||
CommunicationMessages
|
CommunicationMessages
|
||||||
> {
|
> {
|
||||||
protected generateLogIdentifier(): string {
|
protected createLogger(): Logger {
|
||||||
return 'E=>R'
|
return new Logger('EditorToRendererCommunicator')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { WindowPostMessageCommunicator } from './window-post-message-communicator'
|
import { WindowPostMessageCommunicator } from './window-post-message-communicator'
|
||||||
import { CommunicationMessages, EditorToRendererMessageType, RendererToEditorMessageType } from './rendering-message'
|
import { CommunicationMessages, EditorToRendererMessageType, RendererToEditorMessageType } from './rendering-message'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The communicator that is used to send messages from the renderer to the editor.
|
* The communicator that is used to send messages from the renderer to the editor.
|
||||||
|
@ -15,7 +16,7 @@ export class RendererToEditorCommunicator extends WindowPostMessageCommunicator<
|
||||||
RendererToEditorMessageType,
|
RendererToEditorMessageType,
|
||||||
CommunicationMessages
|
CommunicationMessages
|
||||||
> {
|
> {
|
||||||
protected generateLogIdentifier(): string {
|
protected createLogger(): Logger {
|
||||||
return 'E<=R'
|
return new Logger('RendererToEditorCommunicator')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error that will be thrown if a message couldn't be sent.
|
* Error that will be thrown if a message couldn't be sent.
|
||||||
*/
|
*/
|
||||||
|
@ -33,12 +35,16 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
private targetOrigin?: string
|
private targetOrigin?: string
|
||||||
private communicationEnabled: boolean
|
private communicationEnabled: boolean
|
||||||
private handlers: HandlerMap<MESSAGES, RECEIVE_TYPE> = {}
|
private handlers: HandlerMap<MESSAGES, RECEIVE_TYPE> = {}
|
||||||
|
private log
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
window.addEventListener('message', this.handleEvent.bind(this))
|
window.addEventListener('message', this.handleEvent.bind(this))
|
||||||
this.communicationEnabled = false
|
this.communicationEnabled = false
|
||||||
|
this.log = this.createLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract createLogger(): Logger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the message event listener from the {@link window}
|
* Removes the message event listener from the {@link window}
|
||||||
*/
|
*/
|
||||||
|
@ -91,7 +97,7 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
`Communication isn't enabled. Maybe the other side is not ready?\nMessage was: ${JSON.stringify(message)}`
|
`Communication isn't enabled. Maybe the other side is not ready?\nMessage was: ${JSON.stringify(message)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
console.debug('[WPMC ' + this.generateLogIdentifier() + '] Sent event', message)
|
this.log.debug('Sent event', message)
|
||||||
this.messageTarget.postMessage(message, this.targetOrigin)
|
this.messageTarget.postMessage(message, this.targetOrigin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,12 +112,6 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
this.handlers[messageType] = handler as Handler<MESSAGES, RECEIVE_TYPE>
|
this.handlers[messageType] = handler as Handler<MESSAGES, RECEIVE_TYPE>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a unique identifier that helps to separate log messages in the console from different communicators.
|
|
||||||
* @return the identifier
|
|
||||||
*/
|
|
||||||
protected abstract generateLogIdentifier(): string
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives the message events and calls the handler that is mapped to the correct type.
|
* Receives the message events and calls the handler that is mapped to the correct type.
|
||||||
*
|
*
|
||||||
|
@ -125,7 +125,7 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
console.debug('[WPMC ' + this.generateLogIdentifier() + '] Received event ', data)
|
this.log.debug('Received event', data)
|
||||||
handler(data as Extract<MESSAGES, PostMessage<RECEIVE_TYPE>>)
|
handler(data as Extract<MESSAGES, PostMessage<RECEIVE_TYPE>>)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import * as serviceWorkerRegistration from './service-worker-registration'
|
||||||
import './style/dark.scss'
|
import './style/dark.scss'
|
||||||
import './style/index.scss'
|
import './style/index.scss'
|
||||||
import { isTestMode } from './utils/test-modes'
|
import { isTestMode } from './utils/test-modes'
|
||||||
|
import { Logger } from './utils/logger'
|
||||||
|
|
||||||
const EditorPage = React.lazy(
|
const EditorPage = React.lazy(
|
||||||
() => import(/* webpackPrefetch: true */ /* webpackChunkName: "editor" */ './components/editor-page/editor-page')
|
() => import(/* webpackPrefetch: true */ /* webpackChunkName: "editor" */ './components/editor-page/editor-page')
|
||||||
|
@ -37,6 +38,9 @@ const DocumentReadOnlyPage = React.lazy(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const baseUrl = new URL(document.head.baseURI).pathname
|
const baseUrl = new URL(document.head.baseURI).pathname
|
||||||
|
const log = new Logger('Index')
|
||||||
|
|
||||||
|
log.info('Starting HedgeDoc!')
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
@ -96,7 +100,7 @@ ReactDOM.render(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isTestMode()) {
|
if (isTestMode()) {
|
||||||
console.log('This build runs in test mode. This means:\n - no sandboxed iframe')
|
log.warn('This build runs in test mode. This means:\n - no sandboxed iframe')
|
||||||
}
|
}
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
import { store } from '..'
|
import { store } from '..'
|
||||||
import { DarkModeConfig, DarkModeConfigActionType, SetDarkModeConfigAction } from './types'
|
import { DarkModeConfig, DarkModeConfigActionType, SetDarkModeConfigAction } from './types'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('Redux > DarkMode')
|
||||||
|
|
||||||
export const setDarkMode = (darkMode: boolean): void => {
|
export const setDarkMode = (darkMode: boolean): void => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
|
@ -17,8 +20,8 @@ export const setDarkMode = (darkMode: boolean): void => {
|
||||||
export const saveToLocalStorage = (darkModeConfig: DarkModeConfig): void => {
|
export const saveToLocalStorage = (darkModeConfig: DarkModeConfig): void => {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem('nightMode', String(darkModeConfig.darkMode))
|
window.localStorage.setItem('nightMode', String(darkModeConfig.darkMode))
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error('Saving dark-mode setting to local storage failed: ', e)
|
log.error('Saving to local storage failed', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +34,8 @@ export const loadFromLocalStorage = (): DarkModeConfig | undefined => {
|
||||||
return {
|
return {
|
||||||
darkMode: storedValue === 'true'
|
darkMode: storedValue === 'true'
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error('Loading dark-mode setting from local storage failed: ', e)
|
log.error('Loading from local storage failed', error)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,8 +46,8 @@ export const determineDarkModeBrowserSetting = (): DarkModeConfig | undefined =>
|
||||||
return {
|
return {
|
||||||
darkMode: mediaQueryResult
|
darkMode: mediaQueryResult
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error('Can not determine dark-mode setting from browser: ', e)
|
log.error('Can not determine setting from browser', error)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@ import {
|
||||||
SetEditorSyncScrollAction,
|
SetEditorSyncScrollAction,
|
||||||
SetEditorViewModeAction
|
SetEditorViewModeAction
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('Redux > Editor')
|
||||||
|
|
||||||
export const loadFromLocalStorage = (): EditorConfig | undefined => {
|
export const loadFromLocalStorage = (): EditorConfig | undefined => {
|
||||||
try {
|
try {
|
||||||
|
@ -33,8 +36,8 @@ export const saveToLocalStorage = (editorConfig: EditorConfig): void => {
|
||||||
try {
|
try {
|
||||||
const json = JSON.stringify(editorConfig)
|
const json = JSON.stringify(editorConfig)
|
||||||
localStorage.setItem('editorConfig', json)
|
localStorage.setItem('editorConfig', json)
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error('Can not persist editor config in local storage: ', e)
|
log.error('Error while saving editor config in local storage', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,9 @@ import {
|
||||||
historyEntryToHistoryEntryPutDto,
|
historyEntryToHistoryEntryPutDto,
|
||||||
historyEntryToHistoryEntryUpdateDto
|
historyEntryToHistoryEntryUpdateDto
|
||||||
} from '../../api/history/dto-methods'
|
} from '../../api/history/dto-methods'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('Redux > History')
|
||||||
|
|
||||||
export const setHistoryEntries = (entries: HistoryEntry[]): void => {
|
export const setHistoryEntries = (entries: HistoryEntry[]): void => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
|
@ -163,7 +166,7 @@ const loadLocalHistory = (): HistoryEntry[] => {
|
||||||
window.localStorage.removeItem('notehistory')
|
window.localStorage.removeItem('notehistory')
|
||||||
return convertV1History(localV1History)
|
return convertV1History(localV1History)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error converting old history entries: ${String(error)}`)
|
log.error('Error while converting old history entries', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +183,7 @@ const loadLocalHistory = (): HistoryEntry[] => {
|
||||||
})
|
})
|
||||||
return localHistory
|
return localHistory
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error parsing local stored history entries: ${String(error)}`)
|
log.error('Error while parsing locally stored history entries', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +193,7 @@ const loadRemoteHistory = async (): Promise<HistoryEntry[]> => {
|
||||||
const remoteHistory = await getHistory()
|
const remoteHistory = await getHistory()
|
||||||
return remoteHistory.map(historyEntryDtoToHistoryEntry)
|
return remoteHistory.map(historyEntryDtoToHistoryEntry)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching history entries from server: ${String(error)}`)
|
log.error('Error while fetching history entries from server', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ import i18n, { TOptions } from 'i18next'
|
||||||
import { store } from '../index'
|
import { store } from '../index'
|
||||||
import { DismissUiNotificationAction, DispatchOptions, UiNotificationActionType } from './types'
|
import { DismissUiNotificationAction, DispatchOptions, UiNotificationActionType } from './types'
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('Redux > Notifications')
|
||||||
|
|
||||||
export const DEFAULT_DURATION_IN_SECONDS = 10
|
export const DEFAULT_DURATION_IN_SECONDS = 10
|
||||||
|
|
||||||
|
@ -70,7 +73,7 @@ export const dismissUiNotification = (notificationId: number): void => {
|
||||||
export const showErrorNotification =
|
export const showErrorNotification =
|
||||||
(messageI18nKey: string, messageI18nOptions?: TOptions | string) =>
|
(messageI18nKey: string, messageI18nOptions?: TOptions | string) =>
|
||||||
(error: Error): void => {
|
(error: Error): void => {
|
||||||
console.error(i18n.t(messageI18nKey, messageI18nOptions), error)
|
log.error(i18n.t(messageI18nKey, messageI18nOptions), error)
|
||||||
void dispatchUiNotification('common.errorOccurred', messageI18nKey, {
|
void dispatchUiNotification('common.errorOccurred', messageI18nKey, {
|
||||||
contentI18nOptions: messageI18nOptions,
|
contentI18nOptions: messageI18nOptions,
|
||||||
icon: 'exclamation-triangle'
|
icon: 'exclamation-triangle'
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
|
|
||||||
// To learn more about the benefits of this model and instructions on how to
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
// opt-in, read https://cra.link/PWA
|
// opt-in, read https://cra.link/PWA
|
||||||
|
import { Logger } from './utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('ServiceWorker > Registration')
|
||||||
|
|
||||||
const localhostRegex = /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
const localhostRegex = /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
|
||||||
|
@ -53,12 +56,12 @@ export function register(config?: Config): void {
|
||||||
// service worker/PWA documentation.
|
// service worker/PWA documentation.
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker.ready
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(
|
log.info(
|
||||||
'This web app is being served cache-first by a service ' +
|
'This web app is being served cache-first by a service ' +
|
||||||
'worker. To learn more, visit https://cra.link/PWA'
|
'worker. To learn more, visit https://cra.link/PWA'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch((error: Error) => console.error(error))
|
.catch((error: Error) => log.error(error))
|
||||||
} else {
|
} else {
|
||||||
// Is not localhost. Just register service worker
|
// Is not localhost. Just register service worker
|
||||||
registerValidSW(swUrl, config)
|
registerValidSW(swUrl, config)
|
||||||
|
@ -82,7 +85,7 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||||
// At this point, the updated precached content has been fetched,
|
// At this point, the updated precached content has been fetched,
|
||||||
// but the previous service worker will still serve the older
|
// but the previous service worker will still serve the older
|
||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
log.info(
|
||||||
'New content is available and will be used when all ' +
|
'New content is available and will be used when all ' +
|
||||||
'tabs for this page are closed. See https://cra.link/PWA.'
|
'tabs for this page are closed. See https://cra.link/PWA.'
|
||||||
)
|
)
|
||||||
|
@ -95,7 +98,7 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||||
// At this point, everything has been precached.
|
// At this point, everything has been precached.
|
||||||
// It's the perfect time to display a
|
// It's the perfect time to display a
|
||||||
// "Content is cached for offline use." message.
|
// "Content is cached for offline use." message.
|
||||||
console.log('Content is cached for offline use.')
|
log.info('Content is cached for offline use.')
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onSuccess) {
|
if (config && config.onSuccess) {
|
||||||
|
@ -107,7 +110,7 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error during service worker registration:', error)
|
log.error('Error during service worker registration', error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,16 +131,16 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
})
|
})
|
||||||
.catch((error: Error) => console.error(error))
|
.catch((error: Error) => log.error(error))
|
||||||
})
|
})
|
||||||
.catch((error: Error) => console.error(error))
|
.catch((error: Error) => log.error(error))
|
||||||
} else {
|
} else {
|
||||||
// Service worker found. Proceed as normal.
|
// Service worker found. Proceed as normal.
|
||||||
registerValidSW(swUrl, config)
|
registerValidSW(swUrl, config)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log('No internet connection found. App is running in offline mode.')
|
log.info('No internet connection found. App is running in offline mode.')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,10 +148,10 @@ export function unregister(): void {
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker.ready
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
registration.unregister().catch((error: Error) => console.error(error))
|
registration.unregister().catch((error: Error) => log.error(error))
|
||||||
})
|
})
|
||||||
.catch((error: Error) => {
|
.catch((error: Error) => {
|
||||||
console.error(error.message)
|
log.error(error.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ import { ExpirationPlugin } from 'workbox-expiration'
|
||||||
import { createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching'
|
import { createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching'
|
||||||
import { registerRoute } from 'workbox-routing'
|
import { registerRoute } from 'workbox-routing'
|
||||||
import { StaleWhileRevalidate } from 'workbox-strategies'
|
import { StaleWhileRevalidate } from 'workbox-strategies'
|
||||||
|
import { Logger } from './utils/logger'
|
||||||
|
|
||||||
|
const log = new Logger('ServiceWorker')
|
||||||
|
|
||||||
declare const self: ServiceWorkerGlobalScope
|
declare const self: ServiceWorkerGlobalScope
|
||||||
|
|
||||||
|
@ -80,7 +83,7 @@ registerRoute(
|
||||||
self.addEventListener('message', (event) => {
|
self.addEventListener('message', (event) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
self.skipWaiting().catch((e) => console.error(e))
|
self.skipWaiting().catch((e) => log.error(e))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
71
src/utils/logger.test.ts
Normal file
71
src/utils/logger.test.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Logger, LogLevel } from './logger'
|
||||||
|
import { Settings } from 'luxon'
|
||||||
|
|
||||||
|
describe('Logger', () => {
|
||||||
|
let consoleMock: jest.SpyInstance
|
||||||
|
let originalNow: () => number
|
||||||
|
let dateShift = 0
|
||||||
|
|
||||||
|
function mockConsole(methodToMock: LogLevel, onResult: (result: string) => void) {
|
||||||
|
consoleMock = jest.spyOn(console, methodToMock).mockImplementation((...data: string[]) => {
|
||||||
|
const result = data.reduce((state, current) => state + ' ' + current)
|
||||||
|
onResult(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalNow = Settings.now
|
||||||
|
Settings.now = () => new Date(2021, 9, 25, dateShift, 1 + dateShift, 2 + dateShift, 3 + dateShift).valueOf()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Settings.now = originalNow
|
||||||
|
consoleMock.mockReset()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs a debug message into the console', (done) => {
|
||||||
|
dateShift = 0
|
||||||
|
mockConsole(LogLevel.DEBUG, (result) => {
|
||||||
|
expect(consoleMock).toBeCalled()
|
||||||
|
expect(result).toEqual('%c[2021-10-25 00:01:02] %c(prefix) color: yellow color: orange beans')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
new Logger('prefix').debug('beans')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs a info message into the console', (done) => {
|
||||||
|
dateShift = 1
|
||||||
|
mockConsole(LogLevel.INFO, (result) => {
|
||||||
|
expect(consoleMock).toBeCalled()
|
||||||
|
expect(result).toEqual('%c[2021-10-25 01:02:03] %c(prefix) color: yellow color: orange toast')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
new Logger('prefix').info('toast')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs a warn message into the console', (done) => {
|
||||||
|
dateShift = 2
|
||||||
|
mockConsole(LogLevel.WARN, (result) => {
|
||||||
|
expect(consoleMock).toBeCalled()
|
||||||
|
expect(result).toEqual('%c[2021-10-25 02:03:04] %c(prefix) color: yellow color: orange eggs')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
new Logger('prefix').warn('eggs')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs a error message into the console', (done) => {
|
||||||
|
dateShift = 3
|
||||||
|
mockConsole(LogLevel.ERROR, (result) => {
|
||||||
|
expect(consoleMock).toBeCalled()
|
||||||
|
expect(result).toEqual('%c[2021-10-25 03:04:05] %c(prefix) color: yellow color: orange bacon')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
new Logger('prefix').error('bacon')
|
||||||
|
})
|
||||||
|
})
|
83
src/utils/logger.ts
Normal file
83
src/utils/logger.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DateTime } from 'luxon'
|
||||||
|
|
||||||
|
export enum LogLevel {
|
||||||
|
DEBUG = 'debug',
|
||||||
|
INFO = 'info',
|
||||||
|
WARN = 'warn',
|
||||||
|
ERROR = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputFunction = (...data: unknown[]) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple logger that prefixes messages with a timestamp and a name
|
||||||
|
*/
|
||||||
|
export class Logger {
|
||||||
|
private readonly scope: string
|
||||||
|
|
||||||
|
constructor(scope: string) {
|
||||||
|
this.scope = scope
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a debug message
|
||||||
|
* @param data data to log
|
||||||
|
*/
|
||||||
|
debug(...data: unknown[]): void {
|
||||||
|
this.log(LogLevel.DEBUG, ...data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a normal informative message
|
||||||
|
* @param data data to log
|
||||||
|
*/
|
||||||
|
info(...data: unknown[]): void {
|
||||||
|
this.log(LogLevel.INFO, ...data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a warning
|
||||||
|
* @param data data to log
|
||||||
|
*/
|
||||||
|
warn(...data: unknown[]): void {
|
||||||
|
this.log(LogLevel.WARN, ...data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an error
|
||||||
|
* @param data data to log
|
||||||
|
*/
|
||||||
|
error(...data: unknown[]): void {
|
||||||
|
this.log(LogLevel.ERROR, ...data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private log(loglevel: LogLevel, ...data: unknown[]) {
|
||||||
|
const preparedData = [...this.prefix(), ...data]
|
||||||
|
const logOutput = Logger.getLogOutput(loglevel)
|
||||||
|
logOutput(...preparedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getLogOutput(logLevel: LogLevel): OutputFunction {
|
||||||
|
switch (logLevel) {
|
||||||
|
case LogLevel.INFO:
|
||||||
|
return console.info
|
||||||
|
case LogLevel.DEBUG:
|
||||||
|
return console.debug
|
||||||
|
case LogLevel.ERROR:
|
||||||
|
return console.error
|
||||||
|
case LogLevel.WARN:
|
||||||
|
return console.warn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private prefix(): string[] {
|
||||||
|
const timestamp = DateTime.now().toFormat('yyyy-MM-dd HH:mm:ss')
|
||||||
|
return [`%c[${timestamp}] %c(${this.scope})`, 'color: yellow', 'color: orange']
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue