]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/controllers/api/server/logs.ts
Merge branch 'release/3.2.0' into develop
[github/Chocobozzz/PeerTube.git] / server / controllers / api / server / logs.ts
index c551c67e3cdec3ef3818c4190b730341c5734b3c..4b543d686e45512bf2fdec2ceff03ccd1d594795 100644 (file)
@@ -1,14 +1,14 @@
 import * as express from 'express'
 import { UserRight } from '../../../../shared/models/users'
 import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
-import { mtimeSortFilesDesc } from '../../../../shared/utils/logs/logs'
-import { readdir } from 'fs-extra'
-import { CONFIG, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers'
-import { createInterface } from 'readline'
-import { createReadStream } from 'fs'
+import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs'
+import { readdir, readFile } from 'fs-extra'
+import { AUDIT_LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS, LOG_FILENAME } from '../../../initializers/constants'
 import { join } from 'path'
-import { getLogsValidator } from '../../../middlewares/validators/logs'
+import { getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
 import { LogLevel } from '../../../../shared/models/server/log-level.type'
+import { CONFIG } from '../../../initializers/config'
+import { logger } from '@server/helpers/logger'
 
 const logsRouter = express.Router()
 
@@ -19,6 +19,13 @@ logsRouter.get('/logs',
   asyncMiddleware(getLogs)
 )
 
+logsRouter.get('/audit-logs',
+  authenticate,
+  ensureUserHasRight(UserRight.MANAGE_LOGS),
+  getAuditLogsValidator,
+  asyncMiddleware(getAuditLogs)
+)
+
 // ---------------------------------------------------------------------------
 
 export {
@@ -27,64 +34,108 @@ export {
 
 // ---------------------------------------------------------------------------
 
+const auditLogNameFilter = generateLogNameFilter(AUDIT_LOG_FILENAME)
+async function getAuditLogs (req: express.Request, res: express.Response) {
+  const output = await generateOutput({
+    startDateQuery: req.query.startDate,
+    endDateQuery: req.query.endDate,
+    level: 'audit',
+    nameFilter: auditLogNameFilter
+  })
+
+  return res.json(output).end()
+}
+
+const logNameFilter = generateLogNameFilter(LOG_FILENAME)
 async function getLogs (req: express.Request, res: express.Response) {
+  const output = await generateOutput({
+    startDateQuery: req.query.startDate,
+    endDateQuery: req.query.endDate,
+    level: req.query.level || 'info',
+    nameFilter: logNameFilter
+  })
+
+  return res.json(output).end()
+}
+
+async function generateOutput (options: {
+  startDateQuery: string
+  endDateQuery?: string
+  level: LogLevel
+  nameFilter: RegExp
+}) {
+  const { startDateQuery, level, nameFilter } = options
+
   const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
   const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
   let currentSize = 0
 
-  const startDate = new Date(req.query.startDate)
-  const endDate = req.query.endDate ? new Date(req.query.endDate) : new Date()
-  const level: LogLevel = req.query.level || 'info'
+  const startDate = new Date(startDateQuery)
+  const endDate = options.endDateQuery ? new Date(options.endDateQuery) : new Date()
 
-  let output = ''
+  let output: string[] = []
 
   for (const meta of sortedLogFiles) {
+    if (nameFilter.exec(meta.file) === null) continue
+
     const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
+    logger.debug('Opening %s to fetch logs.', path)
 
     const result = await getOutputFromFile(path, startDate, endDate, level, currentSize)
     if (!result.output) break
 
-    output = output + result.output
+    output = result.output.concat(output)
     currentSize = result.currentSize
 
-    if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
+    if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
   }
 
-  return res.json(output).end()
+  return output
 }
 
-function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) {
+async function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) {
   const startTime = startDate.getTime()
   const endTime = endDate.getTime()
+  let logTime: number
 
   const logsLevel: { [ id in LogLevel ]: number } = {
+    audit: -1,
     debug: 0,
     info: 1,
     warn: 2,
     error: 3
   }
 
-  return new Promise<{ output: string, currentSize: number }>(res => {
-    const stream = createReadStream(path)
-    let output = ''
+  const content = await readFile(path)
+  const lines = content.toString().split('\n')
+  const output: any[] = []
 
-    stream.once('close', () => res({ output, currentSize }))
+  for (let i = lines.length - 1; i >= 0; i--) {
+    const line = lines[i]
+    let log: any
 
-    const rl = createInterface({
-      input: stream
-    })
+    try {
+      log = JSON.parse(line)
+    } catch {
+      // Maybe there a multiple \n at the end of the file
+      continue
+    }
 
-    rl.on('line', line => {
-      const log = JSON.parse(line)
+    logTime = new Date(log.timestamp).getTime()
+    if (logTime >= startTime && logTime <= endTime && logsLevel[log.level] >= logsLevel[level]) {
+      output.push(log)
 
-      const logTime = new Date(log.timestamp).getTime()
-      if (logTime >= startTime && logTime <= endTime && logsLevel[log.level] >= logsLevel[level]) {
-        output += line
+      currentSize += line.length
 
-        currentSize += line.length
+      if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
+    } else if (logTime < startTime) {
+      break
+    }
+  }
 
-        if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) stream.close()
-      }
-    })
-  })
+  return { currentSize, output: output.reverse(), logTime }
+}
+
+function generateLogNameFilter (baseName: string) {
+  return new RegExp('^' + baseName.replace(/\.log$/, '') + '\\d*.log$')
 }