]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/server/logs.ts
Bumped to version v5.2.1
[github/Chocobozzz/PeerTube.git] / server / controllers / api / server / logs.ts
CommitLineData
41fb13c3 1import express from 'express'
2c22613c 2import { readdir, readFile } from 'fs-extra'
fd8710b8 3import { join } from 'path'
64553e88 4import { isArray } from '@server/helpers/custom-validators/misc'
15a7eafb 5import { logger, mtimeSortFilesDesc } from '@server/helpers/logger'
42b40636
C
6import { pick } from '@shared/core-utils'
7import { ClientLogCreate, HttpStatusCode } from '@shared/models'
8import { ServerLogLevel } from '../../../../shared/models/server/server-log-level.type'
4c7e60bc 9import { UserRight } from '../../../../shared/models/users'
6dd9de95 10import { CONFIG } from '../../../initializers/config'
4c7e60bc 11import { AUDIT_LOG_FILENAME, LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants'
42b40636
C
12import { asyncMiddleware, authenticate, buildRateLimiter, ensureUserHasRight, optionalAuthenticate } from '../../../middlewares'
13import { createClientLogValidator, getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
14
15const createClientLogRateLimiter = buildRateLimiter({
16 windowMs: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.WINDOW_MS,
17 max: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.MAX
18})
fd8710b8
C
19
20const logsRouter = express.Router()
21
42b40636
C
22logsRouter.post('/logs/client',
23 createClientLogRateLimiter,
24 optionalAuthenticate,
25 createClientLogValidator,
26 createClientLog
27)
28
fd8710b8
C
29logsRouter.get('/logs',
30 authenticate,
31 ensureUserHasRight(UserRight.MANAGE_LOGS),
32 getLogsValidator,
33 asyncMiddleware(getLogs)
34)
35
566c125d
C
36logsRouter.get('/audit-logs',
37 authenticate,
38 ensureUserHasRight(UserRight.MANAGE_LOGS),
39 getAuditLogsValidator,
40 asyncMiddleware(getAuditLogs)
41)
42
fd8710b8
C
43// ---------------------------------------------------------------------------
44
45export {
46 logsRouter
47}
48
49// ---------------------------------------------------------------------------
50
42b40636
C
51function createClientLog (req: express.Request, res: express.Response) {
52 const logInfo = req.body as ClientLogCreate
53
54 const meta = {
55 tags: [ 'client' ],
56 username: res.locals.oauth?.token?.User?.username,
57
58 ...pick(logInfo, [ 'userAgent', 'stackTrace', 'meta', 'url' ])
59 }
60
61 logger.log(logInfo.level, `Client log: ${logInfo.message}`, meta)
62
63 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
64}
65
566c125d
C
66const auditLogNameFilter = generateLogNameFilter(AUDIT_LOG_FILENAME)
67async function getAuditLogs (req: express.Request, res: express.Response) {
68 const output = await generateOutput({
69 startDateQuery: req.query.startDate,
70 endDateQuery: req.query.endDate,
71 level: 'audit',
72 nameFilter: auditLogNameFilter
73 })
74
75 return res.json(output).end()
76}
77
78const logNameFilter = generateLogNameFilter(LOG_FILENAME)
fd8710b8 79async function getLogs (req: express.Request, res: express.Response) {
566c125d
C
80 const output = await generateOutput({
81 startDateQuery: req.query.startDate,
82 endDateQuery: req.query.endDate,
83 level: req.query.level || 'info',
64553e88 84 tagsOneOf: req.query.tagsOneOf,
566c125d
C
85 nameFilter: logNameFilter
86 })
87
64553e88 88 return res.json(output)
566c125d
C
89}
90
91async function generateOutput (options: {
a1587156
C
92 startDateQuery: string
93 endDateQuery?: string
64553e88 94
42b40636 95 level: ServerLogLevel
566c125d 96 nameFilter: RegExp
64553e88 97 tagsOneOf?: string[]
566c125d
C
98}) {
99 const { startDateQuery, level, nameFilter } = options
100
64553e88
C
101 const tagsOneOf = Array.isArray(options.tagsOneOf) && options.tagsOneOf.length !== 0
102 ? new Set(options.tagsOneOf)
103 : undefined
104
fd8710b8
C
105 const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
106 const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
107 let currentSize = 0
108
566c125d
C
109 const startDate = new Date(startDateQuery)
110 const endDate = options.endDateQuery ? new Date(options.endDateQuery) : new Date()
fd8710b8 111
2c22613c 112 let output: string[] = []
fd8710b8
C
113
114 for (const meta of sortedLogFiles) {
566c125d
C
115 if (nameFilter.exec(meta.file) === null) continue
116
fd8710b8 117 const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
3c0d0c66 118 logger.debug('Opening %s to fetch logs.', path)
fd8710b8 119
64553e88 120 const result = await getOutputFromFile({ path, startDate, endDate, level, currentSize, tagsOneOf })
fd8710b8
C
121 if (!result.output) break
122
2c22613c 123 output = result.output.concat(output)
fd8710b8
C
124 currentSize = result.currentSize
125
2c22613c 126 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
fd8710b8
C
127 }
128
566c125d 129 return output
fd8710b8
C
130}
131
64553e88
C
132async function getOutputFromFile (options: {
133 path: string
134 startDate: Date
135 endDate: Date
42b40636 136 level: ServerLogLevel
64553e88
C
137 currentSize: number
138 tagsOneOf: Set<string>
139}) {
140 const { path, startDate, endDate, level, tagsOneOf } = options
141
fd8710b8
C
142 const startTime = startDate.getTime()
143 const endTime = endDate.getTime()
64553e88
C
144 let currentSize = options.currentSize
145
2c22613c 146 let logTime: number
fd8710b8 147
42b40636 148 const logsLevel: { [ id in ServerLogLevel ]: number } = {
566c125d 149 audit: -1,
fd8710b8
C
150 debug: 0,
151 info: 1,
152 warn: 2,
153 error: 3
154 }
155
2c22613c
C
156 const content = await readFile(path)
157 const lines = content.toString().split('\n')
158 const output: any[] = []
fd8710b8 159
2c22613c 160 for (let i = lines.length - 1; i >= 0; i--) {
a1587156 161 const line = lines[i]
2c22613c 162 let log: any
fd8710b8 163
2c22613c
C
164 try {
165 log = JSON.parse(line)
166 } catch {
167 // Maybe there a multiple \n at the end of the file
168 continue
169 }
fd8710b8 170
2c22613c 171 logTime = new Date(log.timestamp).getTime()
64553e88
C
172 if (
173 logTime >= startTime &&
174 logTime <= endTime &&
175 logsLevel[log.level] >= logsLevel[level] &&
176 (!tagsOneOf || lineHasTag(log, tagsOneOf))
177 ) {
2c22613c 178 output.push(log)
fd8710b8 179
2c22613c 180 currentSize += line.length
fd8710b8 181
2c22613c
C
182 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
183 } else if (logTime < startTime) {
184 break
185 }
186 }
fd8710b8 187
2c22613c 188 return { currentSize, output: output.reverse(), logTime }
fd8710b8 189}
566c125d 190
64553e88
C
191function lineHasTag (line: { tags?: string }, tagsOneOf: Set<string>) {
192 if (!isArray(line.tags)) return false
193
194 for (const lineTag of line.tags) {
195 if (tagsOneOf.has(lineTag)) return true
196 }
197
198 return false
199}
200
566c125d 201function generateLogNameFilter (baseName: string) {
3c0d0c66 202 return new RegExp('^' + baseName.replace(/\.log$/, '') + '\\d*.log$')
566c125d 203}