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

feat(settings): logs viewer #997

Merged
merged 17 commits into from
Mar 17, 2021
Merged
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
48 changes: 48 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2252,6 +2252,54 @@ paths:
responses:
'204':
description: 'Flushed cache'
/settings/logs:
get:
summary: Returns logs
description: Returns list of all log items and details
tags:
- settings
parameters:
- in: query
name: take
schema:
type: number
nullable: true
example: 25
- in: query
name: skip
schema:
type: number
nullable: true
example: 0
- in: query
name: filter
schema:
type: string
nullable: true
enum: [debug, info, warn, error]
default: debug
responses:
'200':
description: Server log returned
content:
application/json:
schema:
type: array
items:
type: object
properties:
label:
type: string
example: server
level:
type: string
example: info
message:
type: string
example: Server ready on port 5055
timestamp:
type: string
example: 2020-12-15T16:20:00.069Z
/settings/notifications:
get:
summary: Return notification settings
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"email-templates": "^8.0.3",
"express": "^4.17.1",
"express-openapi-validator": "^4.12.5",
"express-rate-limit": "^5.2.6",
"express-session": "^1.17.1",
"formik": "^2.2.6",
"gravatar-url": "^3.1.0",
Expand Down Expand Up @@ -89,6 +90,7 @@
"@types/csurf": "^1.11.0",
"@types/email-templates": "^8.0.2",
"@types/express": "^4.17.11",
"@types/express-rate-limit": "^5.1.1",
"@types/express-session": "^1.17.3",
"@types/lodash": "^4.14.168",
"@types/node": "^14.14.35",
Expand Down
13 changes: 13 additions & 0 deletions server/interfaces/api/settingsInterfaces.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
import type { PaginatedResponse } from './common';

export type LogMessage = {
timestamp: string;
level: string;
label: string;
message: string;
};

export interface LogsResultsResponse extends PaginatedResponse {
results: LogMessage[];
}

export interface SettingsAboutResponse {
version: string;
totalRequests: number;
Expand Down
102 changes: 93 additions & 9 deletions server/routes/settings/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import { Router } from 'express';
import { getSettings, Library, MainSettings } from '../../lib/settings';
import rateLimit from 'express-rate-limit';
import fs from 'fs';
import { merge, omit } from 'lodash';
import path from 'path';
import { getRepository } from 'typeorm';
import { User } from '../../entity/User';
import PlexAPI from '../../api/plexapi';
import PlexTvAPI from '../../api/plextv';
import Media from '../../entity/Media';
import { MediaRequest } from '../../entity/MediaRequest';
import { User } from '../../entity/User';
import {
LogMessage,
LogsResultsResponse,
SettingsAboutResponse,
} from '../../interfaces/api/settingsInterfaces';
import { scheduledJobs } from '../../job/schedule';
import cacheManager, { AvailableCacheIds } from '../../lib/cache';
import { Permission } from '../../lib/permissions';
import { plexFullScanner } from '../../lib/scanners/plex';
import { getSettings, Library, MainSettings } from '../../lib/settings';
import logger from '../../logger';
import { isAuthenticated } from '../../middleware/auth';
import { merge, omit } from 'lodash';
import Media from '../../entity/Media';
import { MediaRequest } from '../../entity/MediaRequest';
import { getAppVersion } from '../../utils/appVersion';
import { SettingsAboutResponse } from '../../interfaces/api/settingsInterfaces';
import notificationRoutes from './notifications';
import sonarrRoutes from './sonarr';
import radarrRoutes from './radarr';
import cacheManager, { AvailableCacheIds } from '../../lib/cache';
import { plexFullScanner } from '../../lib/scanners/plex';
import sonarrRoutes from './sonarr';

const settingsRoutes = Router();

Expand Down Expand Up @@ -223,6 +231,82 @@ settingsRoutes.post('/plex/sync', (req, res) => {
return res.status(200).json(plexFullScanner.status());
});

settingsRoutes.get(
'/logs',
rateLimit({ windowMs: 60 * 1000, max: 50 }),
(req, res, next) => {
const pageSize = req.query.take ? Number(req.query.take) : 25;
const skip = req.query.skip ? Number(req.query.skip) : 0;

let filter: string[] = [];
switch (req.query.filter) {
case 'debug':
filter.push('debug');
// falls through
case 'info':
filter.push('info');
// falls through
case 'warn':
filter.push('warn');
// falls through
case 'error':
filter.push('error');
break;
default:
filter = ['debug', 'info', 'warn', 'error'];
}

const logFile = process.env.CONFIG_DIRECTORY
? `${process.env.CONFIG_DIRECTORY}/logs/overseerr.log`
: path.join(__dirname, '../../../config/logs/overseerr.log');
const logs: LogMessage[] = [];

try {
fs.readFileSync(logFile)
.toString()
.split('\n')
.forEach((line) => {
if (!line.length) return;

const timestamp = line.match(new RegExp(/^.{24}/)) || [];
const level = line.match(new RegExp(/\s\[\w+\]/)) || [];
const label = line.match(new RegExp(/[^\s]\[\w+\s*\w*\]/)) || [];
const message = line.match(new RegExp(/:\s.*/)) || [];

if (level.length && filter.includes(level[0].slice(2, -1))) {
logs.push({
timestamp: timestamp[0],
level: level.length ? level[0].slice(2, -1) : '',
label: label.length ? label[0].slice(2, -1) : '',
message: message.length ? message[0].slice(2) : '',
});
}
});

const displayedLogs = logs.reverse().slice(skip, skip + pageSize);

return res.status(200).json({
pageInfo: {
pages: Math.ceil(logs.length / pageSize),
pageSize,
results: logs.length,
page: Math.ceil(skip / pageSize) + 1,
},
results: displayedLogs,
} as LogsResultsResponse);
} catch (error) {
logger.error('Something went wrong while fetching the logs', {
label: 'Logs',
errorMessage: error.message,
});
return next({
status: 500,
message: 'Something went wrong while fetching the logs',
});
}
}
);

settingsRoutes.get('/jobs', (_req, res) => {
return res.status(200).json(
scheduledJobs.map((job) => ({
Expand Down
2 changes: 1 addition & 1 deletion src/components/RequestList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const RequestList: React.FC = () => {
}
}, []);

// Set fitler values to local storage any time they are changed
// Set filter values to local storage any time they are changed
useEffect(() => {
window.localStorage.setItem(
'rl-filter-settings',
Expand Down
Loading