1 import express from 'express'
2 import { readdir, readFile } from 'fs-extra'
3 import { join } from 'path'
4 import { isArray } from '@server/helpers/custom-validators/misc'
5 import { logger, mtimeSortFilesDesc } from '@server/helpers/logger'
6 import { LogLevel } from '../../../../shared/models/server/log-level.type'
7 import { UserRight } from '../../../../shared/models/users'
8 import { CONFIG } from '../../../initializers/config'
9 import { AUDIT_LOG_FILENAME, LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants'
10 import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
11 import { getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
13 const logsRouter = express.Router()
15 logsRouter.get('/logs',
17 ensureUserHasRight(UserRight.MANAGE_LOGS),
19 asyncMiddleware(getLogs)
22 logsRouter.get('/audit-logs',
24 ensureUserHasRight(UserRight.MANAGE_LOGS),
25 getAuditLogsValidator,
26 asyncMiddleware(getAuditLogs)
29 // ---------------------------------------------------------------------------
35 // ---------------------------------------------------------------------------
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,
43 nameFilter: auditLogNameFilter
46 return res.json(output).end()
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 tagsOneOf: req.query.tagsOneOf,
56 nameFilter: logNameFilter
59 return res.json(output)
62 async function generateOutput (options: {
63 startDateQuery: string
70 const { startDateQuery, level, nameFilter } = options
72 const tagsOneOf = Array.isArray(options.tagsOneOf) && options.tagsOneOf.length !== 0
73 ? new Set(options.tagsOneOf)
76 const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
77 const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
80 const startDate = new Date(startDateQuery)
81 const endDate = options.endDateQuery ? new Date(options.endDateQuery) : new Date()
83 let output: string[] = []
85 for (const meta of sortedLogFiles) {
86 if (nameFilter.exec(meta.file) === null) continue
88 const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
89 logger.debug('Opening %s to fetch logs.', path)
91 const result = await getOutputFromFile({ path, startDate, endDate, level, currentSize, tagsOneOf })
92 if (!result.output) break
94 output = result.output.concat(output)
95 currentSize = result.currentSize
97 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
103 async function getOutputFromFile (options: {
109 tagsOneOf: Set<string>
111 const { path, startDate, endDate, level, tagsOneOf } = options
113 const startTime = startDate.getTime()
114 const endTime = endDate.getTime()
115 let currentSize = options.currentSize
119 const logsLevel: { [ id in LogLevel ]: number } = {
127 const content = await readFile(path)
128 const lines = content.toString().split('\n')
129 const output: any[] = []
131 for (let i = lines.length - 1; i >= 0; i--) {
132 const line = lines[i]
136 log = JSON.parse(line)
138 // Maybe there a multiple \n at the end of the file
142 logTime = new Date(log.timestamp).getTime()
144 logTime >= startTime &&
145 logTime <= endTime &&
146 logsLevel[log.level] >= logsLevel[level] &&
147 (!tagsOneOf || lineHasTag(log, tagsOneOf))
151 currentSize += line.length
153 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
154 } else if (logTime < startTime) {
159 return { currentSize, output: output.reverse(), logTime }
162 function lineHasTag (line: { tags?: string }, tagsOneOf: Set<string>) {
163 if (!isArray(line.tags)) return false
165 for (const lineTag of line.tags) {
166 if (tagsOneOf.has(lineTag)) return true
172 function generateLogNameFilter (baseName: string) {
173 return new RegExp('^' + baseName.replace(/\.log$/, '') + '\\d*.log$')