PeerTube/server/core/controllers/api/server/logs.ts

202 lines
5.6 KiB
TypeScript
Raw Normal View History

2021-08-27 14:32:44 +02:00
import express from 'express'
import { readdir, readFile } from 'fs/promises'
2019-04-10 15:26:33 +02:00
import { join } from 'path'
import { pick } from '@peertube/peertube-core-utils'
import { ClientLogCreate, HttpStatusCode, ServerLogLevel, UserRight } from '@peertube/peertube-models'
import { isArray } from '@server/helpers/custom-validators/misc.js'
import { logger, mtimeSortFilesDesc } from '@server/helpers/logger.js'
import { CONFIG } from '../../../initializers/config.js'
import { AUDIT_LOG_FILENAME, LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants.js'
import { asyncMiddleware, authenticate, buildRateLimiter, ensureUserHasRight, optionalAuthenticate } from '../../../middlewares/index.js'
import { createClientLogValidator, getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs.js'
const createClientLogRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.WINDOW_MS,
max: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.MAX
})
2019-04-10 15:26:33 +02:00
const logsRouter = express.Router()
logsRouter.post('/logs/client',
createClientLogRateLimiter,
optionalAuthenticate,
createClientLogValidator,
createClientLog
)
2019-04-10 15:26:33 +02:00
logsRouter.get('/logs',
authenticate,
ensureUserHasRight(UserRight.MANAGE_LOGS),
getLogsValidator,
asyncMiddleware(getLogs)
)
2019-12-11 14:14:01 +01:00
logsRouter.get('/audit-logs',
authenticate,
ensureUserHasRight(UserRight.MANAGE_LOGS),
getAuditLogsValidator,
asyncMiddleware(getAuditLogs)
)
2019-04-10 15:26:33 +02:00
// ---------------------------------------------------------------------------
export {
logsRouter
}
// ---------------------------------------------------------------------------
function createClientLog (req: express.Request, res: express.Response) {
const logInfo = req.body as ClientLogCreate
const meta = {
tags: [ 'client' ],
username: res.locals.oauth?.token?.User?.username,
...pick(logInfo, [ 'userAgent', 'stackTrace', 'meta', 'url' ])
}
logger.log(logInfo.level, `Client log: ${logInfo.message}`, meta)
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
2019-12-11 14:14:01 +01:00
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)
2019-04-10 15:26:33 +02:00
async function getLogs (req: express.Request, res: express.Response) {
2019-12-11 14:14:01 +01:00
const output = await generateOutput({
startDateQuery: req.query.startDate,
endDateQuery: req.query.endDate,
level: req.query.level || 'info',
2021-10-20 14:23:32 +02:00
tagsOneOf: req.query.tagsOneOf,
2019-12-11 14:14:01 +01:00
nameFilter: logNameFilter
})
2021-10-20 14:23:32 +02:00
return res.json(output)
2019-12-11 14:14:01 +01:00
}
async function generateOutput (options: {
2020-01-31 16:56:52 +01:00
startDateQuery: string
endDateQuery?: string
2021-10-20 14:23:32 +02:00
level: ServerLogLevel
2019-12-11 14:14:01 +01:00
nameFilter: RegExp
2021-10-20 14:23:32 +02:00
tagsOneOf?: string[]
2019-12-11 14:14:01 +01:00
}) {
const { startDateQuery, level, nameFilter } = options
2021-10-20 14:23:32 +02:00
const tagsOneOf = Array.isArray(options.tagsOneOf) && options.tagsOneOf.length !== 0
? new Set(options.tagsOneOf)
: undefined
2019-04-10 15:26:33 +02:00
const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
let currentSize = 0
2019-12-11 14:14:01 +01:00
const startDate = new Date(startDateQuery)
const endDate = options.endDateQuery ? new Date(options.endDateQuery) : new Date()
2019-04-10 15:26:33 +02:00
2019-04-11 10:05:43 +02:00
let output: string[] = []
2019-04-10 15:26:33 +02:00
for (const meta of sortedLogFiles) {
2019-12-11 14:14:01 +01:00
if (nameFilter.exec(meta.file) === null) continue
2019-04-10 15:26:33 +02:00
const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
2019-12-12 09:15:38 +01:00
logger.debug('Opening %s to fetch logs.', path)
2019-04-10 15:26:33 +02:00
2021-10-20 14:23:32 +02:00
const result = await getOutputFromFile({ path, startDate, endDate, level, currentSize, tagsOneOf })
2019-04-10 15:26:33 +02:00
if (!result.output) break
2019-04-11 10:05:43 +02:00
output = result.output.concat(output)
2019-04-10 15:26:33 +02:00
currentSize = result.currentSize
2019-04-11 10:05:43 +02:00
if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
2019-04-10 15:26:33 +02:00
}
2019-12-11 14:14:01 +01:00
return output
2019-04-10 15:26:33 +02:00
}
2021-10-20 14:23:32 +02:00
async function getOutputFromFile (options: {
path: string
startDate: Date
endDate: Date
level: ServerLogLevel
2021-10-20 14:23:32 +02:00
currentSize: number
tagsOneOf: Set<string>
}) {
const { path, startDate, endDate, level, tagsOneOf } = options
2019-04-10 15:26:33 +02:00
const startTime = startDate.getTime()
const endTime = endDate.getTime()
2021-10-20 14:23:32 +02:00
let currentSize = options.currentSize
2019-04-11 10:05:43 +02:00
let logTime: number
2019-04-10 15:26:33 +02:00
const logsLevel: { [ id in ServerLogLevel ]: number } = {
2019-12-11 14:14:01 +01:00
audit: -1,
2019-04-10 15:26:33 +02:00
debug: 0,
info: 1,
warn: 2,
error: 3
}
2019-04-11 10:05:43 +02:00
const content = await readFile(path)
const lines = content.toString().split('\n')
const output: any[] = []
2019-04-10 15:26:33 +02:00
2019-04-11 10:05:43 +02:00
for (let i = lines.length - 1; i >= 0; i--) {
2020-01-31 16:56:52 +01:00
const line = lines[i]
2019-04-11 10:05:43 +02:00
let log: any
2019-04-10 15:26:33 +02:00
2019-04-11 10:05:43 +02:00
try {
log = JSON.parse(line)
} catch {
// Maybe there a multiple \n at the end of the file
continue
}
2019-04-10 15:26:33 +02:00
2019-04-11 10:05:43 +02:00
logTime = new Date(log.timestamp).getTime()
2021-10-20 14:23:32 +02:00
if (
logTime >= startTime &&
logTime <= endTime &&
logsLevel[log.level] >= logsLevel[level] &&
(!tagsOneOf || lineHasTag(log, tagsOneOf))
) {
2019-04-11 10:05:43 +02:00
output.push(log)
2019-04-10 15:26:33 +02:00
2019-04-11 10:05:43 +02:00
currentSize += line.length
2019-04-10 15:26:33 +02:00
2019-04-11 10:05:43 +02:00
if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
} else if (logTime < startTime) {
break
}
}
2019-04-10 15:26:33 +02:00
2019-04-11 10:05:43 +02:00
return { currentSize, output: output.reverse(), logTime }
2019-04-10 15:26:33 +02:00
}
2019-12-11 14:14:01 +01:00
2021-10-20 14:23:32 +02:00
function lineHasTag (line: { tags?: string }, tagsOneOf: Set<string>) {
if (!isArray(line.tags)) return false
for (const lineTag of line.tags) {
if (tagsOneOf.has(lineTag)) return true
}
return false
}
2019-12-11 14:14:01 +01:00
function generateLogNameFilter (baseName: string) {
2019-12-12 09:15:38 +01:00
return new RegExp('^' + baseName.replace(/\.log$/, '') + '\\d*.log$')
2019-12-11 14:14:01 +01:00
}