diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/server/logs.ts | 42 | ||||
-rw-r--r-- | server/middlewares/validators/logs.ts | 7 | ||||
-rw-r--r-- | server/tests/api/server/logs.ts | 23 |
3 files changed, 66 insertions, 6 deletions
diff --git a/server/controllers/api/server/logs.ts b/server/controllers/api/server/logs.ts index dfd5491aa..8aa4b7190 100644 --- a/server/controllers/api/server/logs.ts +++ b/server/controllers/api/server/logs.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { readdir, readFile } from 'fs-extra' | 2 | import { readdir, readFile } from 'fs-extra' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { isArray } from '@server/helpers/custom-validators/misc' | ||
4 | import { logger, mtimeSortFilesDesc } from '@server/helpers/logger' | 5 | import { logger, mtimeSortFilesDesc } from '@server/helpers/logger' |
5 | import { LogLevel } from '../../../../shared/models/server/log-level.type' | 6 | import { LogLevel } from '../../../../shared/models/server/log-level.type' |
6 | import { UserRight } from '../../../../shared/models/users' | 7 | import { UserRight } from '../../../../shared/models/users' |
@@ -51,20 +52,27 @@ async function getLogs (req: express.Request, res: express.Response) { | |||
51 | startDateQuery: req.query.startDate, | 52 | startDateQuery: req.query.startDate, |
52 | endDateQuery: req.query.endDate, | 53 | endDateQuery: req.query.endDate, |
53 | level: req.query.level || 'info', | 54 | level: req.query.level || 'info', |
55 | tagsOneOf: req.query.tagsOneOf, | ||
54 | nameFilter: logNameFilter | 56 | nameFilter: logNameFilter |
55 | }) | 57 | }) |
56 | 58 | ||
57 | return res.json(output).end() | 59 | return res.json(output) |
58 | } | 60 | } |
59 | 61 | ||
60 | async function generateOutput (options: { | 62 | async function generateOutput (options: { |
61 | startDateQuery: string | 63 | startDateQuery: string |
62 | endDateQuery?: string | 64 | endDateQuery?: string |
65 | |||
63 | level: LogLevel | 66 | level: LogLevel |
64 | nameFilter: RegExp | 67 | nameFilter: RegExp |
68 | tagsOneOf?: string[] | ||
65 | }) { | 69 | }) { |
66 | const { startDateQuery, level, nameFilter } = options | 70 | const { startDateQuery, level, nameFilter } = options |
67 | 71 | ||
72 | const tagsOneOf = Array.isArray(options.tagsOneOf) && options.tagsOneOf.length !== 0 | ||
73 | ? new Set(options.tagsOneOf) | ||
74 | : undefined | ||
75 | |||
68 | const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR) | 76 | const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR) |
69 | const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR) | 77 | const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR) |
70 | let currentSize = 0 | 78 | let currentSize = 0 |
@@ -80,7 +88,7 @@ async function generateOutput (options: { | |||
80 | const path = join(CONFIG.STORAGE.LOG_DIR, meta.file) | 88 | const path = join(CONFIG.STORAGE.LOG_DIR, meta.file) |
81 | logger.debug('Opening %s to fetch logs.', path) | 89 | logger.debug('Opening %s to fetch logs.', path) |
82 | 90 | ||
83 | const result = await getOutputFromFile(path, startDate, endDate, level, currentSize) | 91 | const result = await getOutputFromFile({ path, startDate, endDate, level, currentSize, tagsOneOf }) |
84 | if (!result.output) break | 92 | if (!result.output) break |
85 | 93 | ||
86 | output = result.output.concat(output) | 94 | output = result.output.concat(output) |
@@ -92,9 +100,20 @@ async function generateOutput (options: { | |||
92 | return output | 100 | return output |
93 | } | 101 | } |
94 | 102 | ||
95 | async function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) { | 103 | async function getOutputFromFile (options: { |
104 | path: string | ||
105 | startDate: Date | ||
106 | endDate: Date | ||
107 | level: LogLevel | ||
108 | currentSize: number | ||
109 | tagsOneOf: Set<string> | ||
110 | }) { | ||
111 | const { path, startDate, endDate, level, tagsOneOf } = options | ||
112 | |||
96 | const startTime = startDate.getTime() | 113 | const startTime = startDate.getTime() |
97 | const endTime = endDate.getTime() | 114 | const endTime = endDate.getTime() |
115 | let currentSize = options.currentSize | ||
116 | |||
98 | let logTime: number | 117 | let logTime: number |
99 | 118 | ||
100 | const logsLevel: { [ id in LogLevel ]: number } = { | 119 | const logsLevel: { [ id in LogLevel ]: number } = { |
@@ -121,7 +140,12 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date, | |||
121 | } | 140 | } |
122 | 141 | ||
123 | logTime = new Date(log.timestamp).getTime() | 142 | logTime = new Date(log.timestamp).getTime() |
124 | if (logTime >= startTime && logTime <= endTime && logsLevel[log.level] >= logsLevel[level]) { | 143 | if ( |
144 | logTime >= startTime && | ||
145 | logTime <= endTime && | ||
146 | logsLevel[log.level] >= logsLevel[level] && | ||
147 | (!tagsOneOf || lineHasTag(log, tagsOneOf)) | ||
148 | ) { | ||
125 | output.push(log) | 149 | output.push(log) |
126 | 150 | ||
127 | currentSize += line.length | 151 | currentSize += line.length |
@@ -135,6 +159,16 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date, | |||
135 | return { currentSize, output: output.reverse(), logTime } | 159 | return { currentSize, output: output.reverse(), logTime } |
136 | } | 160 | } |
137 | 161 | ||
162 | function lineHasTag (line: { tags?: string }, tagsOneOf: Set<string>) { | ||
163 | if (!isArray(line.tags)) return false | ||
164 | |||
165 | for (const lineTag of line.tags) { | ||
166 | if (tagsOneOf.has(lineTag)) return true | ||
167 | } | ||
168 | |||
169 | return false | ||
170 | } | ||
171 | |||
138 | function generateLogNameFilter (baseName: string) { | 172 | function generateLogNameFilter (baseName: string) { |
139 | return new RegExp('^' + baseName.replace(/\.log$/, '') + '\\d*.log$') | 173 | return new RegExp('^' + baseName.replace(/\.log$/, '') + '\\d*.log$') |
140 | } | 174 | } |
diff --git a/server/middlewares/validators/logs.ts b/server/middlewares/validators/logs.ts index 03c1c4df1..901d8ca64 100644 --- a/server/middlewares/validators/logs.ts +++ b/server/middlewares/validators/logs.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
3 | import { isStringArray } from '@server/helpers/custom-validators/search' | ||
3 | import { isValidLogLevel } from '../../helpers/custom-validators/logs' | 4 | import { isValidLogLevel } from '../../helpers/custom-validators/logs' |
4 | import { isDateValid } from '../../helpers/custom-validators/misc' | 5 | import { isDateValid, toArray } from '../../helpers/custom-validators/misc' |
5 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
6 | import { areValidationErrors } from './shared' | 7 | import { areValidationErrors } from './shared' |
7 | 8 | ||
@@ -11,6 +12,10 @@ const getLogsValidator = [ | |||
11 | query('level') | 12 | query('level') |
12 | .optional() | 13 | .optional() |
13 | .custom(isValidLogLevel).withMessage('Should have a valid level'), | 14 | .custom(isValidLogLevel).withMessage('Should have a valid level'), |
15 | query('tagsOneOf') | ||
16 | .optional() | ||
17 | .customSanitizer(toArray) | ||
18 | .custom(isStringArray).withMessage('Should have a valid one of tags array'), | ||
14 | query('endDate') | 19 | query('endDate') |
15 | .optional() | 20 | .optional() |
16 | .custom(isDateValid).withMessage('Should have an end date that conforms to ISO 8601'), | 21 | .custom(isDateValid).withMessage('Should have an end date that conforms to ISO 8601'), |
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts index bcd94dda3..4fa13886e 100644 --- a/server/tests/api/server/logs.ts +++ b/server/tests/api/server/logs.ts | |||
@@ -71,7 +71,7 @@ describe('Test logs', function () { | |||
71 | expect(logsString.includes('video 5')).to.be.false | 71 | expect(logsString.includes('video 5')).to.be.false |
72 | }) | 72 | }) |
73 | 73 | ||
74 | it('Should get filter by level', async function () { | 74 | it('Should filter by level', async function () { |
75 | this.timeout(20000) | 75 | this.timeout(20000) |
76 | 76 | ||
77 | const now = new Date() | 77 | const now = new Date() |
@@ -94,6 +94,27 @@ describe('Test logs', function () { | |||
94 | } | 94 | } |
95 | }) | 95 | }) |
96 | 96 | ||
97 | it('Should filter by tag', async function () { | ||
98 | const now = new Date() | ||
99 | |||
100 | const { uuid } = await server.videos.upload({ attributes: { name: 'video 6' } }) | ||
101 | await waitJobs([ server ]) | ||
102 | |||
103 | { | ||
104 | const body = await logsCommand.getLogs({ startDate: now, level: 'debug', tagsOneOf: [ 'toto' ] }) | ||
105 | expect(body).to.have.lengthOf(0) | ||
106 | } | ||
107 | |||
108 | { | ||
109 | const body = await logsCommand.getLogs({ startDate: now, level: 'debug', tagsOneOf: [ uuid ] }) | ||
110 | expect(body).to.not.have.lengthOf(0) | ||
111 | |||
112 | for (const line of body) { | ||
113 | expect(line.tags).to.contain(uuid) | ||
114 | } | ||
115 | } | ||
116 | }) | ||
117 | |||
97 | it('Should log ping requests', async function () { | 118 | it('Should log ping requests', async function () { |
98 | this.timeout(10000) | 119 | this.timeout(10000) |
99 | 120 | ||