]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/controllers/api/server/logs.ts
Serve audit logs to client
[github/Chocobozzz/PeerTube.git] / server / controllers / api / server / logs.ts
1 import * as express from 'express'
2 import { UserRight } from '../../../../shared/models/users'
3 import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4 import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs'
5 import { readdir, readFile } from 'fs-extra'
6 import { AUDIT_LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS, LOG_FILENAME } from '../../../initializers/constants'
7 import { join } from 'path'
8 import { getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
9 import { LogLevel } from '../../../../shared/models/server/log-level.type'
10 import { CONFIG } from '../../../initializers/config'
11 import { logger } from '@server/helpers/logger'
12
13 const logsRouter = express.Router()
14
15 logsRouter.get('/logs',
16 authenticate,
17 ensureUserHasRight(UserRight.MANAGE_LOGS),
18 getLogsValidator,
19 asyncMiddleware(getLogs)
20 )
21
22 logsRouter.get('/audit-logs',
23 authenticate,
24 ensureUserHasRight(UserRight.MANAGE_LOGS),
25 getAuditLogsValidator,
26 asyncMiddleware(getAuditLogs)
27 )
28
29 // ---------------------------------------------------------------------------
30
31 export {
32 logsRouter
33 }
34
35 // ---------------------------------------------------------------------------
36
37 const auditLogNameFilter = generateLogNameFilter(AUDIT_LOG_FILENAME)
38 async function getAuditLogs (req: express.Request, res: express.Response) {
39 const output = await generateOutput({
40 startDateQuery: req.query.startDate,
41 endDateQuery: req.query.endDate,
42 level: 'audit',
43 nameFilter: auditLogNameFilter
44 })
45
46 return res.json(output).end()
47 }
48
49 const logNameFilter = generateLogNameFilter(LOG_FILENAME)
50 async function getLogs (req: express.Request, res: express.Response) {
51 const output = await generateOutput({
52 startDateQuery: req.query.startDate,
53 endDateQuery: req.query.endDate,
54 level: req.query.level || 'info',
55 nameFilter: logNameFilter
56 })
57
58 return res.json(output).end()
59 }
60
61 async function generateOutput (options: {
62 startDateQuery: string,
63 endDateQuery?: string,
64 level: LogLevel,
65 nameFilter: RegExp
66 }) {
67 const { startDateQuery, level, nameFilter } = options
68
69 const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
70 const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
71 let currentSize = 0
72
73 const startDate = new Date(startDateQuery)
74 const endDate = options.endDateQuery ? new Date(options.endDateQuery) : new Date()
75
76 let output: string[] = []
77
78 for (const meta of sortedLogFiles) {
79 if (nameFilter.exec(meta.file) === null) continue
80
81 const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
82
83 const result = await getOutputFromFile(path, startDate, endDate, level, currentSize)
84 if (!result.output) break
85
86 output = result.output.concat(output)
87 currentSize = result.currentSize
88
89 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
90 }
91
92 return output
93 }
94
95 async function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) {
96 const startTime = startDate.getTime()
97 const endTime = endDate.getTime()
98 let logTime: number
99
100 const logsLevel: { [ id in LogLevel ]: number } = {
101 audit: -1,
102 debug: 0,
103 info: 1,
104 warn: 2,
105 error: 3
106 }
107
108 const content = await readFile(path)
109 const lines = content.toString().split('\n')
110 const output: any[] = []
111
112 for (let i = lines.length - 1; i >= 0; i--) {
113 const line = lines[ i ]
114 let log: any
115
116 try {
117 log = JSON.parse(line)
118 } catch {
119 // Maybe there a multiple \n at the end of the file
120 continue
121 }
122
123 logTime = new Date(log.timestamp).getTime()
124 if (logTime >= startTime && logTime <= endTime && logsLevel[ log.level ] >= logsLevel[ level ]) {
125 output.push(log)
126
127 currentSize += line.length
128
129 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
130 } else if (logTime < startTime) {
131 break
132 }
133 }
134
135 return { currentSize, output: output.reverse(), logTime }
136 }
137
138 function generateLogNameFilter (baseName: string) {
139 return new RegExp('^' + baseName.replace(/\.log$/, '') + '\d*.log$')
140 }