chore!: remove stdout logging functionality (#37114)
Co-authored-by: Tasso Evangelista <tasso.evangelista@rocket.chat> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>pull/35380/merge
parent
bd5edfc299
commit
ec0f8b435d
@ -0,0 +1,8 @@ |
||||
--- |
||||
'@rocket.chat/rest-typings': major |
||||
'@rocket.chat/ddp-client': major |
||||
'@rocket.chat/logger': major |
||||
'@rocket.chat/meteor': major |
||||
--- |
||||
|
||||
Removes stdout logging functionality, related components and settings |
||||
@ -1,223 +0,0 @@ |
||||
import type { Serialized } from '@rocket.chat/core-typings'; |
||||
import { Box, Icon, Scrollable } from '@rocket.chat/fuselage'; |
||||
import { useToastMessageDispatch, useEndpoint, useStream } from '@rocket.chat/ui-contexts'; |
||||
import type { ReactElement } from 'react'; |
||||
import { useEffect, useRef, useState, useCallback } from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
|
||||
import { ansispan } from './ansispan'; |
||||
|
||||
type StdOutLogEntry = { |
||||
id: string; |
||||
string: string; |
||||
ts: Date; |
||||
}; |
||||
|
||||
const compareEntries = (a: StdOutLogEntry, b: StdOutLogEntry): number => a.ts.getTime() - b.ts.getTime(); |
||||
|
||||
const unserializeEntry = ({ ts, ...entry }: Serialized<StdOutLogEntry>): StdOutLogEntry => ({ |
||||
ts: new Date(ts), |
||||
...entry, |
||||
}); |
||||
|
||||
const ServerLogs = (): ReactElement => { |
||||
const [entries, setEntries] = useState<StdOutLogEntry[]>([]); |
||||
|
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
|
||||
const getStdoutQueue = useEndpoint('GET', '/v1/stdout.queue'); |
||||
const subscribeToStdout = useStream('stdout'); |
||||
|
||||
useEffect(() => { |
||||
const fetchLines = async (): Promise<void> => { |
||||
try { |
||||
const { queue } = await getStdoutQueue(undefined); |
||||
setEntries(queue.map(unserializeEntry).sort(compareEntries)); |
||||
} catch (error: unknown) { |
||||
dispatchToastMessage({ type: 'error', message: error }); |
||||
} |
||||
}; |
||||
|
||||
fetchLines(); |
||||
}, [dispatchToastMessage, getStdoutQueue]); |
||||
|
||||
useEffect( |
||||
() => |
||||
subscribeToStdout('stdout', (entry: StdOutLogEntry) => { |
||||
setEntries((entries) => [...entries, entry]); |
||||
}), |
||||
[subscribeToStdout], |
||||
); |
||||
|
||||
const { t } = useTranslation(); |
||||
|
||||
const wrapperRef = useRef<HTMLElement>(); |
||||
const atBottomRef = useRef<boolean>(false); |
||||
|
||||
const [newLogsVisible, setNewLogsVisible] = useState(false); |
||||
|
||||
const isAtBottom = useCallback<(scrollThreshold?: number) => boolean>((scrollThreshold = 0) => { |
||||
const wrapper = wrapperRef.current; |
||||
|
||||
if (!wrapper) { |
||||
return false; |
||||
} |
||||
|
||||
if (wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight) { |
||||
setNewLogsVisible(false); |
||||
return true; |
||||
} |
||||
return false; |
||||
}, []); |
||||
|
||||
const sendToBottom = useCallback(() => { |
||||
const wrapper = wrapperRef.current; |
||||
|
||||
if (!wrapper) { |
||||
return; |
||||
} |
||||
|
||||
wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight; |
||||
setNewLogsVisible(false); |
||||
}, []); |
||||
|
||||
const checkIfScrollIsAtBottom = useCallback(() => { |
||||
atBottomRef.current = isAtBottom(100); |
||||
}, [isAtBottom]); |
||||
|
||||
const sendToBottomIfNecessary = useCallback(() => { |
||||
if (atBottomRef.current === true && isAtBottom() !== true) { |
||||
sendToBottom(); |
||||
} else if (atBottomRef.current === false) { |
||||
setNewLogsVisible(true); |
||||
} |
||||
}, [isAtBottom, sendToBottom]); |
||||
|
||||
useEffect(() => { |
||||
const wrapper = wrapperRef.current; |
||||
|
||||
if (!wrapper) { |
||||
return; |
||||
} |
||||
|
||||
if (window.MutationObserver) { |
||||
const observer = new MutationObserver((mutations) => { |
||||
mutations.forEach(() => { |
||||
sendToBottomIfNecessary(); |
||||
}); |
||||
}); |
||||
observer.observe(wrapper, { childList: true }); |
||||
|
||||
return (): void => { |
||||
observer.disconnect(); |
||||
}; |
||||
} |
||||
|
||||
const handleSubtreeModified = (): void => { |
||||
sendToBottomIfNecessary(); |
||||
}; |
||||
wrapper.addEventListener('DOMSubtreeModified', handleSubtreeModified); |
||||
|
||||
return (): void => { |
||||
wrapper.removeEventListener('DOMSubtreeModified', handleSubtreeModified); |
||||
}; |
||||
}, [sendToBottomIfNecessary]); |
||||
|
||||
useEffect(() => { |
||||
const handleWindowResize = (): void => { |
||||
setTimeout(() => { |
||||
sendToBottomIfNecessary(); |
||||
}, 100); |
||||
}; |
||||
|
||||
window.addEventListener('resize', handleWindowResize); |
||||
|
||||
return (): void => { |
||||
window.removeEventListener('resize', handleWindowResize); |
||||
}; |
||||
}, [sendToBottomIfNecessary]); |
||||
|
||||
const handleWheel = useCallback(() => { |
||||
atBottomRef.current = false; |
||||
setTimeout(() => { |
||||
checkIfScrollIsAtBottom(); |
||||
}, 100); |
||||
}, [checkIfScrollIsAtBottom]); |
||||
|
||||
const handleTouchStart = (): void => { |
||||
atBottomRef.current = false; |
||||
}; |
||||
|
||||
const handleTouchEnd = useCallback(() => { |
||||
setTimeout(() => { |
||||
checkIfScrollIsAtBottom(); |
||||
}, 100); |
||||
}, [checkIfScrollIsAtBottom]); |
||||
|
||||
const handleScroll = useCallback(() => { |
||||
atBottomRef.current = false; |
||||
setTimeout(() => { |
||||
checkIfScrollIsAtBottom(); |
||||
}, 100); |
||||
}, [checkIfScrollIsAtBottom]); |
||||
|
||||
const handleClick = useCallback(() => { |
||||
atBottomRef.current = true; |
||||
sendToBottomIfNecessary(); |
||||
}, [sendToBottomIfNecessary]); |
||||
|
||||
return ( |
||||
<Box width='full' height='full' overflow='hidden' position='relative' display='flex' mbe={8}> |
||||
<Scrollable vertical> |
||||
<Box |
||||
ref={wrapperRef} |
||||
display='flex' |
||||
flexDirection='column' |
||||
padding={8} |
||||
flexGrow={1} |
||||
fontFamily='mono' |
||||
color='default' |
||||
bg='neutral' |
||||
style={{ wordBreak: 'break-all' }} |
||||
onWheel={handleWheel} |
||||
onTouchStart={handleTouchStart} |
||||
onTouchEnd={handleTouchEnd} |
||||
onScroll={handleScroll} |
||||
borderRadius='x4' |
||||
> |
||||
{entries.sort(compareEntries).map(({ string }, i) => ( |
||||
<span key={i} dangerouslySetInnerHTML={{ __html: ansispan(string) }} /> |
||||
))} |
||||
</Box> |
||||
</Scrollable> |
||||
<Box |
||||
role='button' |
||||
position='absolute' |
||||
display='flex' |
||||
justifyContent='center' |
||||
insetBlockEnd={8} |
||||
insetInlineStart='50%' |
||||
width='x132' |
||||
height='x32' |
||||
marginInline='neg-x64' |
||||
paddingBlock={8} |
||||
fontScale='c2' |
||||
borderRadius='full' |
||||
elevation='1' |
||||
color='default' |
||||
bg='light' |
||||
onClick={handleClick} |
||||
textAlign='center' |
||||
style={{ |
||||
cursor: 'pointer', |
||||
transition: 'transform 0.3s ease-out', |
||||
transform: newLogsVisible ? 'translateY(0)' : 'translateY(150%)', |
||||
}} |
||||
> |
||||
<Icon name='jump' size='x16' /> {t('New_logs')} |
||||
</Box> |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
export default ServerLogs; |
||||
@ -0,0 +1,11 @@ |
||||
import { Settings } from '@rocket.chat/models'; |
||||
|
||||
import { addMigration } from '../../lib/migrations'; |
||||
|
||||
addMigration({ |
||||
version: 324, |
||||
name: 'Remove Log_View_Limit setting', |
||||
async up() { |
||||
await Settings.deleteOne({ _id: 'Log_View_Limit' }); |
||||
}, |
||||
}); |
||||
@ -1,68 +0,0 @@ |
||||
import { performance } from 'perf_hooks'; |
||||
|
||||
import { format } from '@rocket.chat/log-format'; |
||||
import { getQueuedLogs, logEntries } from '@rocket.chat/logger'; |
||||
import EJSON from 'ejson'; |
||||
|
||||
import notifications from '../../app/notifications/server/lib/Notifications'; |
||||
|
||||
const processString = (string: string, date: Date): string => { |
||||
try { |
||||
const obj = EJSON.parse(string); |
||||
if (!obj || typeof obj !== 'object') { |
||||
throw new TypeError('Invalid JSON'); |
||||
} |
||||
|
||||
if ('toJSONValue' in obj) { |
||||
return format(obj.toJSONValue(), { color: true }); |
||||
} |
||||
|
||||
if (!Array.isArray(obj) && !(obj instanceof Date) && !(obj instanceof Uint8Array)) { |
||||
return format(obj, { color: true }); |
||||
} |
||||
return format( |
||||
{ |
||||
message: string, |
||||
time: date, |
||||
level: 'info', |
||||
}, |
||||
{ color: true }, |
||||
); |
||||
} catch (e) { |
||||
return string; |
||||
} |
||||
}; |
||||
|
||||
const rawTransformLog = (item: any): { id: string; string: string; ts: Date; time?: number } => { |
||||
return { |
||||
id: item.id, |
||||
string: processString(item.data, item.ts), |
||||
ts: item.ts, |
||||
}; |
||||
}; |
||||
|
||||
const timedTransformLog = (log: any): { id: string; string: string; ts: Date; time?: number } => { |
||||
const timeStart = performance.now(); |
||||
const item = rawTransformLog(log); |
||||
const timeEnd = performance.now(); |
||||
|
||||
item.time = timeEnd - timeStart; |
||||
|
||||
return item; |
||||
}; |
||||
|
||||
const transformLog = process.env.STDOUT_METRICS === 'true' ? timedTransformLog : rawTransformLog; |
||||
|
||||
logEntries.on('log', (item) => { |
||||
// TODO having this as 'emitWithoutBroadcast' will not sent this data to ddp-streamer, so this data
|
||||
// won't be available when using micro services.
|
||||
notifications.streamStdout.emitWithoutBroadcast('stdout', transformLog(item)); |
||||
}); |
||||
|
||||
export function getLogs(): { |
||||
id: string; |
||||
string: string; |
||||
ts: Date; |
||||
}[] { |
||||
return getQueuedLogs().map(transformLog); |
||||
} |
||||
@ -1,63 +0,0 @@ |
||||
import EventEmitter from 'events'; |
||||
|
||||
const { MOLECULER_LOG_LEVEL, STDOUT_VIEWER_DISABLED = 'false' } = process.env; |
||||
|
||||
type LogQueue = { |
||||
id: string; |
||||
data: string; |
||||
ts: Date; |
||||
}; |
||||
|
||||
const queue: LogQueue[] = []; |
||||
const maxInt = 2147483647; |
||||
let queueLimit = 1000; |
||||
let queueSize = 0; |
||||
|
||||
export function setQueueLimit(limit: number): void { |
||||
queueLimit = limit; |
||||
|
||||
if (queueSize > queueLimit) { |
||||
queue.splice(0, queueSize - queueLimit); |
||||
} |
||||
} |
||||
|
||||
export function getQueuedLogs(): LogQueue[] { |
||||
return queue; |
||||
} |
||||
|
||||
export const logEntries = new EventEmitter(); |
||||
|
||||
const { write } = process.stdout; |
||||
|
||||
function queueWrite(buffer: Uint8Array | string, cb?: (err?: Error) => void): boolean; |
||||
function queueWrite(str: Uint8Array | string, encoding?: string, cb?: (err?: Error) => void): boolean; |
||||
function queueWrite(...args: any): boolean { |
||||
write.apply(process.stdout, args); |
||||
|
||||
const [str] = args; |
||||
if (typeof str !== 'string') { |
||||
return true; |
||||
} |
||||
|
||||
const date = new Date(); |
||||
const item = { |
||||
id: `logid-${queueSize}`, |
||||
data: str, |
||||
ts: date, |
||||
}; |
||||
queue.push(item); |
||||
|
||||
queueSize = (queueSize + 1) & maxInt; |
||||
|
||||
if (queueSize > queueLimit) { |
||||
queue.shift(); |
||||
} |
||||
|
||||
logEntries.emit('log', item); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
if (String(MOLECULER_LOG_LEVEL).toLowerCase() !== 'debug' && STDOUT_VIEWER_DISABLED === 'false') { |
||||
process.stdout.write = queueWrite; |
||||
} |
||||
Loading…
Reference in new issue