Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/log paginate #262

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/api/log-metrics/[[...type]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NextResponse } from 'next/server'
import getReqAuthToken from '../../../../utilities/getReqAuthToken';
import { fetchLogMetrics } from '../../logs';

export async function GET(req: Request, context: any) {
try {
const { type } = context.params;

const token = getReqAuthToken(req)
const data = await fetchLogMetrics(token, type)
return NextResponse.json(data)
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch log metrics' }, { status: 500 })
}
}
6 changes: 4 additions & 2 deletions app/api/logs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { LogType } from '../../src/types';
import fetchFromApi from '../../utilities/fetchFromApi';

const backendUrl = process.env.BACKEND_URL
export const fetchLogMetrics = async (token: string) => fetchFromApi(`${backendUrl}/logs/metrics`, token)
export const dismissLogAlert = async (token: string, index: string) => fetchFromApi(`${backendUrl}/logs/dismiss/${index}`, token)
export const fetchLogMetrics = async (token: string, type?: LogType) => fetchFromApi(`${backendUrl}/logs/metrics${type ? `/${type}` : ''}`, token)
export const dismissLogAlert = async (token: string, index: string) => fetchFromApi(`${backendUrl}/logs/dismiss/${index}`, token)
export const fetchPriorityLogs = async (token: string, page?: string) => fetchFromApi(`${backendUrl}/logs/priority${page ? `/${page}` : ''}`, token)
14 changes: 14 additions & 0 deletions app/api/priority-logs/[[...page]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { NextResponse } from 'next/server'
import getReqAuthToken from '../../../../utilities/getReqAuthToken';
import { fetchPriorityLogs } from '../../logs';

export async function GET(req: Request, context: any) {
try {
const { page } = context.params;
const token = getReqAuthToken(req)
const data = await fetchPriorityLogs(token, page)
return NextResponse.json(data)
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch priority logs' }, { status: 500 })
}
}
13 changes: 0 additions & 13 deletions app/api/priority-logs/route.ts

This file was deleted.

22 changes: 18 additions & 4 deletions app/dashboard/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import React, { FC, useEffect } from 'react';
import React, { FC, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'
import { useSetRecoilState } from 'recoil'
import pckJson from '../../package.json'
Expand All @@ -17,7 +17,7 @@ import useLocalStorage from '../../src/hooks/useLocalStorage'
import useNetworkMonitor from '../../src/hooks/useNetworkMonitor'
import useSWRPolling from '../../src/hooks/useSWRPolling'
import { exchangeRates, proposerDuties } from '../../src/recoil/atoms';
import { LogMetric, ProposerDuty, StatusColor } from '../../src/types';
import { LogMetric, PriorityLogResults, ProposerDuty, StatusColor } from '../../src/types';
import { BeaconNodeSpecResults, SyncData } from '../../src/types/beacon'
import { Diagnostics, PeerDataResults } from '../../src/types/diagnostic'
import { ValidatorCache, ValidatorInclusionData, ValidatorInfo } from '../../src/types/validator'
Expand All @@ -36,6 +36,7 @@ export interface MainProps {
initInclusionRate: ValidatorInclusionData
initProposerDuties: ProposerDuty[]
initLogMetrics: LogMetric
initPriorityLogs: PriorityLogResults
}

const Main: FC<MainProps> = (props) => {
Expand All @@ -52,6 +53,7 @@ const Main: FC<MainProps> = (props) => {
genesisTime,
initProposerDuties,
initLogMetrics,
initPriorityLogs
} = props

const { t } = useTranslation()
Expand All @@ -62,6 +64,7 @@ const Main: FC<MainProps> = (props) => {
const [username] = useLocalStorage<string>('username', 'Keeper')
const setExchangeRate = useSetRecoilState(exchangeRates)
const setDuties = useSetRecoilState(proposerDuties)
const [priorityAlertPage, setPage] = useState(1)

const { isValidatorError, isBeaconError } = useNetworkMonitor()

Expand Down Expand Up @@ -111,18 +114,24 @@ const Main: FC<MainProps> = (props) => {
networkError,
})

const { data: logMetrics } = useSWRPolling<LogMetric>('/api/priority-logs', {
const { data: logMetrics } = useSWRPolling<LogMetric>('/api/log-metrics', {
refreshInterval: slotInterval / 2,
fallbackData: initLogMetrics,
networkError,
})

const { data: priorityLogs, isLoading } = useSWRPolling<PriorityLogResults>(`/api/priority-logs/${priorityAlertPage}`, {
refreshInterval: slotInterval / 2,
fallbackData: initPriorityLogs,
networkError,
})

const { beaconSync, executionSync } = syncData
const { isSyncing } = beaconSync
const { isReady } = executionSync
const { connected } = peerData
const { natOpen } = nodeHealth
const warningCount = logMetrics.warningLogs?.length || 0
const warningCount = logMetrics.warningLogs || 0

useEffect(() => {
setDuties(prev => formatUniqueObjectArray([...prev, ...valDuties]))
Expand Down Expand Up @@ -215,6 +224,8 @@ const Main: FC<MainProps> = (props) => {
removeAlert(ALERT_ID.WARNING_LOG)
}, [warningCount, storeAlert, removeAlert])

const loadMoreLogs = async () => setPage(prev => prev + 1)

return (
<DashboardWrapper
syncData={syncData}
Expand Down Expand Up @@ -250,6 +261,9 @@ const Main: FC<MainProps> = (props) => {
/>
<ValidatorTable validators={validatorStates} className='mt-8 lg:mt-2' />
<DiagnosticTable
isLoadingPriority={isLoading}
logData={priorityLogs}
onLoadMore={loadMoreLogs}
metrics={logMetrics}
bnSpec={beaconSpec}
syncData={syncData}
Expand Down
14 changes: 3 additions & 11 deletions app/dashboard/logs/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { FC, useEffect, useState } from 'react';
import DashboardWrapper from '../../../src/components/DashboardWrapper/DashboardWrapper'
import LogControls from '../../../src/components/LogControls/LogControls'
import LogDisplay from '../../../src/components/LogDisplay/LogDisplay'
Expand Down Expand Up @@ -42,20 +42,12 @@ const Main: FC<MainProps> = ({ initSyncData, beaconSpec, initNodeHealth, initLog
networkError,
})

const { data: logMetrics } = useSWRPolling<LogMetric>('/api/priority-logs', {
const { data: logMetrics } = useSWRPolling<LogMetric>(`/api/log-metrics/${logType}`, {
refreshInterval: slotInterval / 2,
fallbackData: initLogMetrics,
networkError,
})

const filteredLogs = useMemo(() => {
return {
warningLogs: logMetrics.warningLogs.filter(({type}) => type === logType),
errorLogs: logMetrics.errorLogs.filter(({type}) => type === logType),
criticalLogs: logMetrics.criticalLogs.filter(({type}) => type === logType)
}
}, [logMetrics, logType])

const toggleLogType = (selection: OptionType) => {
if (selection === logType) return

Expand All @@ -79,7 +71,7 @@ const Main: FC<MainProps> = ({ initSyncData, beaconSpec, initNodeHealth, initLog
>
<div className='w-full h-full pt-8 p-2 md:p-6 flex flex-col'>
<LogControls logType={logType} onSetLoading={setLoading} onTypeSelect={toggleLogType} />
<LogDisplay priorityLogs={filteredLogs} isLoading={isLoading} type={logType} />
<LogDisplay metrics={logMetrics} isLoading={isLoading} type={logType} />
</div>
</DashboardWrapper>
)
Expand Down
9 changes: 5 additions & 4 deletions app/dashboard/logs/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import '../../../src/global.css'
import '../../../src/global.css';
import { redirect } from 'next/navigation';
import { LogType } from '../../../src/types';
import getSessionCookie from '../../../utilities/getSessionCookie';
import { fetchBeaconSpec, fetchNodeHealth, fetchSyncData } from '../../api/beacon'
import { fetchBeaconSpec, fetchNodeHealth, fetchSyncData } from '../../api/beacon';
import { fetchLogMetrics } from '../../api/logs';
import Wrapper from './Wrapper'
import Wrapper from './Wrapper';

export default async function Page() {
try {
const token = getSessionCookie()

const logMetrics = await fetchLogMetrics(token)
const logMetrics = await fetchLogMetrics(token, LogType.VALIDATOR)
const beaconSpec = await fetchBeaconSpec(token)
const syncData = await fetchSyncData(token)
const nodeHealth = await fetchNodeHealth(token)
Expand Down
15 changes: 9 additions & 6 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import '../../src/global.css'
import '../../src/global.css';
import { redirect } from 'next/navigation';
import getSessionCookie from '../../utilities/getSessionCookie';
import {
fetchBeaconSpec,
fetchInclusionRate,
fetchNodeHealth,
fetchPeerData, fetchProposerDuties,
fetchPeerData,
fetchProposerDuties,
fetchSyncData
} from '../api/beacon';
import { fetchBeaconNodeVersion, fetchGenesisData, fetchValidatorVersion } from '../api/config'
import { fetchLogMetrics } from '../api/logs';
import { fetchValCaches, fetchValStates } from '../api/validator'
import Wrapper from './Wrapper'
import { fetchBeaconNodeVersion, fetchGenesisData, fetchValidatorVersion } from '../api/config';
import { fetchLogMetrics, fetchPriorityLogs } from '../api/logs';
import { fetchValCaches, fetchValStates } from '../api/validator';
import Wrapper from './Wrapper';

export default async function Page() {
try {
Expand All @@ -29,10 +30,12 @@ export default async function Page() {
const lighthouseVersion = await fetchValidatorVersion(token)
const proposerDuties = await fetchProposerDuties(token)
const logMetrics = await fetchLogMetrics(token)
const priorityLogs = await fetchPriorityLogs(token)

return (
<Wrapper
initProposerDuties={proposerDuties}
initPriorityLogs={priorityLogs}
initValCaches={caches}
initValStates={states}
initNodeHealth={nodeHealth}
Expand Down
1 change: 1 addition & 0 deletions app/error/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useTranslation } from 'react-i18next';
import Button, { ButtonFace } from '../../src/components/Button/Button';
import Typography from '../../src/components/Typography/Typography';
import '../../src/i18n'

const Main = () => {
const {t} = useTranslation()
Expand Down
18 changes: 17 additions & 1 deletion backend/src/logs/logs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Controller, Get, Res, Req, Param, UseGuards } from '@nestjs/common';
import { Request, Response } from 'express';
import { LogsService } from './logs.service';
import { SessionGuard } from '../session.guard';
import { LogType } from '../../../src/types';

@Controller('logs')
@UseGuards(SessionGuard)
Expand All @@ -21,9 +22,24 @@ export class LogsController {
this.logsService.getSseStream(req, res, `${beaconUrl}/lighthouse/logs`);
}

@Get('priority')
getPriorityLogs() {
return this.logsService.readPriorityLogs()
}

@Get('priority/:page')
getPriorityLogsPage(@Param('page') page: string) {
return this.logsService.readPriorityLogs(page)
}

@Get('metrics')
getLogMetrics() {
return this.logsService.readLogMetrics()
return this.logsService.fetchLogCounts()
}

@Get('metrics/:type')
getLogTypeMetrics(@Param('type') type: LogType) {
return this.logsService.fetchLogCounts(type)
}

@Get('dismiss/:index')
Expand Down
47 changes: 42 additions & 5 deletions backend/src/logs/logs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,28 @@ export class LogsService {
}
}

async readLogMetrics(type?: LogType) {
async fetchLogCounts(type?: LogType) {
if(type && !this.logTypes.includes(type)) {
throw new Error('Invalid log type');
}

let warnOptions = { where: { level: LogLevels.WARN } } as any
let errorOptions = { where: { level: LogLevels.ERRO } } as any
let critOptions = { where: { level: LogLevels.CRIT } } as any
let critOptions = {
where: { level: LogLevels.CRIT },
createdAt: {
[Op.gte]: new Date(Date.now() - 60 * 60 * 1000)
} } as any

if(type) {
warnOptions.where.type = { [Op.eq]: type };
errorOptions.where.type = { [Op.eq]: type };
critOptions.where.type = { [Op.eq]: type };
}

const warningLogs = (await this.logRepository.findAll(warnOptions)).map(data => data.dataValues)
const errorLogs = (await this.logRepository.findAll(errorOptions)).map(data => data.dataValues)
const criticalLogs = (await this.logRepository.findAll(critOptions)).map(data => data.dataValues)
const { count: warningLogs } = await this.logRepository.findAndCountAll(warnOptions)
const { count: errorLogs } = await this.logRepository.findAndCountAll(errorOptions)
const { count: criticalLogs } = await this.logRepository.findAndCountAll(critOptions)

return {
warningLogs,
Expand All @@ -105,6 +109,39 @@ export class LogsService {
}
}

async readPriorityLogs(page?: string) {
const formattedPage = page || 1
const max = 15
const limit = Number(formattedPage) * max

let options = {
where: {
[Op.and]: [
{
[Op.or]: [
{ level: LogLevels.CRIT },
{ level: LogLevels.ERRO }
]
},
{
isHidden: false
}
]
},
limit: limit + 1,
order: [['createdAt', 'DESC']]
} as any

const results = (await this.logRepository.findAll(options)).map(data => data.dataValues)

const hasNextPage = results.length > limit

return {
logs: results.slice(0, limit),
hasNextPage
}
}

async dismissLog(id: string) {
return await this.logRepository.update({isHidden: true}, {where: {id}})
}
Expand Down
Loading