aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/server/logs.ts57
-rw-r--r--server/helpers/audit-logger.ts3
-rw-r--r--server/helpers/logger.ts3
-rw-r--r--server/initializers/constants.ts4
-rw-r--r--server/middlewares/validators/logs.ts19
-rw-r--r--server/tests/api/server/logs.ts145
6 files changed, 173 insertions, 58 deletions
diff --git a/server/controllers/api/server/logs.ts b/server/controllers/api/server/logs.ts
index e9d1f2efd..a0ca21cd5 100644
--- a/server/controllers/api/server/logs.ts
+++ b/server/controllers/api/server/logs.ts
@@ -3,11 +3,12 @@ import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs' 4import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs'
5import { readdir, readFile } from 'fs-extra' 5import { readdir, readFile } from 'fs-extra'
6import { MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants' 6import { AUDIT_LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS, LOG_FILENAME } from '../../../initializers/constants'
7import { join } from 'path' 7import { join } from 'path'
8import { getLogsValidator } from '../../../middlewares/validators/logs' 8import { getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
9import { LogLevel } from '../../../../shared/models/server/log-level.type' 9import { LogLevel } from '../../../../shared/models/server/log-level.type'
10import { CONFIG } from '../../../initializers/config' 10import { CONFIG } from '../../../initializers/config'
11import { logger } from '@server/helpers/logger'
11 12
12const logsRouter = express.Router() 13const logsRouter = express.Router()
13 14
@@ -18,6 +19,13 @@ logsRouter.get('/logs',
18 asyncMiddleware(getLogs) 19 asyncMiddleware(getLogs)
19) 20)
20 21
22logsRouter.get('/audit-logs',
23 authenticate,
24 ensureUserHasRight(UserRight.MANAGE_LOGS),
25 getAuditLogsValidator,
26 asyncMiddleware(getAuditLogs)
27)
28
21// --------------------------------------------------------------------------- 29// ---------------------------------------------------------------------------
22 30
23export { 31export {
@@ -26,18 +34,50 @@ export {
26 34
27// --------------------------------------------------------------------------- 35// ---------------------------------------------------------------------------
28 36
37const auditLogNameFilter = generateLogNameFilter(AUDIT_LOG_FILENAME)
38async 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
49const logNameFilter = generateLogNameFilter(LOG_FILENAME)
29async function getLogs (req: express.Request, res: express.Response) { 50async 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
61async function generateOutput (options: {
62 startDateQuery: string,
63 endDateQuery?: string,
64 level: LogLevel,
65 nameFilter: RegExp
66}) {
67 const { startDateQuery, level, nameFilter } = options
68
30 const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR) 69 const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
31 const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR) 70 const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
32 let currentSize = 0 71 let currentSize = 0
33 72
34 const startDate = new Date(req.query.startDate) 73 const startDate = new Date(startDateQuery)
35 const endDate = req.query.endDate ? new Date(req.query.endDate) : new Date() 74 const endDate = options.endDateQuery ? new Date(options.endDateQuery) : new Date()
36 const level: LogLevel = req.query.level || 'info'
37 75
38 let output: string[] = [] 76 let output: string[] = []
39 77
40 for (const meta of sortedLogFiles) { 78 for (const meta of sortedLogFiles) {
79 if (nameFilter.exec(meta.file) === null) continue
80
41 const path = join(CONFIG.STORAGE.LOG_DIR, meta.file) 81 const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
42 82
43 const result = await getOutputFromFile(path, startDate, endDate, level, currentSize) 83 const result = await getOutputFromFile(path, startDate, endDate, level, currentSize)
@@ -49,7 +89,7 @@ async function getLogs (req: express.Request, res: express.Response) {
49 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break 89 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
50 } 90 }
51 91
52 return res.json(output).end() 92 return output
53} 93}
54 94
55async function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) { 95async function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) {
@@ -58,6 +98,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
58 let logTime: number 98 let logTime: number
59 99
60 const logsLevel: { [ id in LogLevel ]: number } = { 100 const logsLevel: { [ id in LogLevel ]: number } = {
101 audit: -1,
61 debug: 0, 102 debug: 0,
62 info: 1, 103 info: 1,
63 warn: 2, 104 warn: 2,
@@ -93,3 +134,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
93 134
94 return { currentSize, output: output.reverse(), logTime } 135 return { currentSize, output: output.reverse(), logTime }
95} 136}
137
138function generateLogNameFilter (baseName: string) {
139 return new RegExp('^' + baseName.replace(/\.log$/, '') + '\d*.log$')
140}
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts
index f536da439..9b258dc3a 100644
--- a/server/helpers/audit-logger.ts
+++ b/server/helpers/audit-logger.ts
@@ -9,6 +9,7 @@ import { User, VideoAbuse, VideoChannel, VideoDetails, VideoImport } from '../..
9import { VideoComment } from '../../shared/models/videos/video-comment.model' 9import { VideoComment } from '../../shared/models/videos/video-comment.model'
10import { CustomConfig } from '../../shared/models/server/custom-config.model' 10import { CustomConfig } from '../../shared/models/server/custom-config.model'
11import { CONFIG } from '../initializers/config' 11import { CONFIG } from '../initializers/config'
12import { AUDIT_LOG_FILENAME } from '@server/initializers/constants'
12 13
13function getAuditIdFromRes (res: express.Response) { 14function getAuditIdFromRes (res: express.Response) {
14 return res.locals.oauth.token.User.username 15 return res.locals.oauth.token.User.username
@@ -29,7 +30,7 @@ const auditLogger = winston.createLogger({
29 levels: { audit: 0 }, 30 levels: { audit: 0 },
30 transports: [ 31 transports: [
31 new winston.transports.File({ 32 new winston.transports.File({
32 filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube-audit.log'), 33 filename: path.join(CONFIG.STORAGE.LOG_DIR, AUDIT_LOG_FILENAME),
33 level: 'audit', 34 level: 'audit',
34 maxsize: 5242880, 35 maxsize: 5242880,
35 maxFiles: 5, 36 maxFiles: 5,
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts
index d21746963..c2ff2bae6 100644
--- a/server/helpers/logger.ts
+++ b/server/helpers/logger.ts
@@ -5,6 +5,7 @@ import * as winston from 'winston'
5import { FileTransportOptions } from 'winston/lib/winston/transports' 5import { FileTransportOptions } from 'winston/lib/winston/transports'
6import { CONFIG } from '../initializers/config' 6import { CONFIG } from '../initializers/config'
7import { omit } from 'lodash' 7import { omit } from 'lodash'
8import { LOG_FILENAME } from '@server/initializers/constants'
8 9
9const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 10const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
10 11
@@ -58,7 +59,7 @@ const labelFormatter = winston.format.label({
58}) 59})
59 60
60const fileLoggerOptions: FileTransportOptions = { 61const fileLoggerOptions: FileTransportOptions = {
61 filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube.log'), 62 filename: path.join(CONFIG.STORAGE.LOG_DIR, LOG_FILENAME),
62 handleExceptions: true, 63 handleExceptions: true,
63 format: winston.format.combine( 64 format: winston.format.combine(
64 winston.format.timestamp(), 65 winston.format.timestamp(),
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index af70e7b88..bdabe7f66 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -603,6 +603,8 @@ const FEEDS = {
603} 603}
604 604
605const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000 605const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000
606const LOG_FILENAME = 'peertube.log'
607const AUDIT_LOG_FILENAME = 'peertube-audit.log'
606 608
607// --------------------------------------------------------------------------- 609// ---------------------------------------------------------------------------
608 610
@@ -684,6 +686,7 @@ export {
684 BCRYPT_SALT_SIZE, 686 BCRYPT_SALT_SIZE,
685 TRACKER_RATE_LIMITS, 687 TRACKER_RATE_LIMITS,
686 FILES_CACHE, 688 FILES_CACHE,
689 LOG_FILENAME,
687 CONSTRAINTS_FIELDS, 690 CONSTRAINTS_FIELDS,
688 EMBED_SIZE, 691 EMBED_SIZE,
689 REDUNDANCY, 692 REDUNDANCY,
@@ -693,6 +696,7 @@ export {
693 OAUTH_LIFETIME, 696 OAUTH_LIFETIME,
694 CUSTOM_HTML_TAG_COMMENTS, 697 CUSTOM_HTML_TAG_COMMENTS,
695 BROADCAST_CONCURRENCY, 698 BROADCAST_CONCURRENCY,
699 AUDIT_LOG_FILENAME,
696 PAGINATION, 700 PAGINATION,
697 ACTOR_FOLLOW_SCORE, 701 ACTOR_FOLLOW_SCORE,
698 PREVIEWS_SIZE, 702 PREVIEWS_SIZE,
diff --git a/server/middlewares/validators/logs.ts b/server/middlewares/validators/logs.ts
index 07f3f552f..70e4d0d99 100644
--- a/server/middlewares/validators/logs.ts
+++ b/server/middlewares/validators/logs.ts
@@ -24,8 +24,25 @@ const getLogsValidator = [
24 } 24 }
25] 25]
26 26
27const getAuditLogsValidator = [
28 query('startDate')
29 .custom(isDateValid).withMessage('Should have a valid start date'),
30 query('endDate')
31 .optional()
32 .custom(isDateValid).withMessage('Should have a valid end date'),
33
34 (req: express.Request, res: express.Response, next: express.NextFunction) => {
35 logger.debug('Checking getAuditLogsValidator parameters.', { parameters: req.query })
36
37 if (areValidationErrors(req, res)) return
38
39 return next()
40 }
41]
42
27// --------------------------------------------------------------------------- 43// ---------------------------------------------------------------------------
28 44
29export { 45export {
30 getLogsValidator 46 getLogsValidator,
47 getAuditLogsValidator
31} 48}
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts
index 68f442199..d3c877408 100644
--- a/server/tests/api/server/logs.ts
+++ b/server/tests/api/server/logs.ts
@@ -2,17 +2,10 @@
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import { cleanupTests, flushAndRunServer, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index'
6 flushTests,
7 killallServers,
8 flushAndRunServer,
9 ServerInfo,
10 setAccessTokensToServers,
11 cleanupTests
12} from '../../../../shared/extra-utils/index'
13import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 6import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
14import { uploadVideo } from '../../../../shared/extra-utils/videos/videos' 7import { uploadVideo } from '../../../../shared/extra-utils/videos/videos'
15import { getLogs } from '../../../../shared/extra-utils/logs/logs' 8import { getAuditLogs, getLogs } from '../../../../shared/extra-utils/logs/logs'
16 9
17const expect = chai.expect 10const expect = chai.expect
18 11
@@ -26,69 +19,123 @@ describe('Test logs', function () {
26 await setAccessTokensToServers([ server ]) 19 await setAccessTokensToServers([ server ])
27 }) 20 })
28 21
29 it('Should get logs with a start date', async function () { 22 describe('With the standard log file', function () {
30 this.timeout(10000) 23 it('Should get logs with a start date', async function () {
24 this.timeout(10000)
31 25
32 await uploadVideo(server.url, server.accessToken, { name: 'video 1' }) 26 await uploadVideo(server.url, server.accessToken, { name: 'video 1' })
33 await waitJobs([ server ]) 27 await waitJobs([ server ])
34 28
35 const now = new Date() 29 const now = new Date()
36 30
37 await uploadVideo(server.url, server.accessToken, { name: 'video 2' }) 31 await uploadVideo(server.url, server.accessToken, { name: 'video 2' })
38 await waitJobs([ server ]) 32 await waitJobs([ server ])
39 33
40 const res = await getLogs(server.url, server.accessToken, now) 34 const res = await getLogs(server.url, server.accessToken, now)
41 const logsString = JSON.stringify(res.body) 35 const logsString = JSON.stringify(res.body)
42 36
43 expect(logsString.includes('video 1')).to.be.false 37 expect(logsString.includes('video 1')).to.be.false
44 expect(logsString.includes('video 2')).to.be.true 38 expect(logsString.includes('video 2')).to.be.true
45 }) 39 })
40
41 it('Should get logs with an end date', async function () {
42 this.timeout(20000)
43
44 await uploadVideo(server.url, server.accessToken, { name: 'video 3' })
45 await waitJobs([ server ])
46
47 const now1 = new Date()
48
49 await uploadVideo(server.url, server.accessToken, { name: 'video 4' })
50 await waitJobs([ server ])
51
52 const now2 = new Date()
53
54 await uploadVideo(server.url, server.accessToken, { name: 'video 5' })
55 await waitJobs([ server ])
56
57 const res = await getLogs(server.url, server.accessToken, now1, now2)
58 const logsString = JSON.stringify(res.body)
46 59
47 it('Should get logs with an end date', async function () { 60 expect(logsString.includes('video 3')).to.be.false
48 this.timeout(20000) 61 expect(logsString.includes('video 4')).to.be.true
62 expect(logsString.includes('video 5')).to.be.false
63 })
49 64
50 await uploadVideo(server.url, server.accessToken, { name: 'video 3' }) 65 it('Should get filter by level', async function () {
51 await waitJobs([ server ]) 66 this.timeout(10000)
52 67
53 const now1 = new Date() 68 const now = new Date()
54 69
55 await uploadVideo(server.url, server.accessToken, { name: 'video 4' }) 70 await uploadVideo(server.url, server.accessToken, { name: 'video 6' })
56 await waitJobs([ server ]) 71 await waitJobs([ server ])
57 72
58 const now2 = new Date() 73 {
74 const res = await getLogs(server.url, server.accessToken, now, undefined, 'info')
75 const logsString = JSON.stringify(res.body)
59 76
60 await uploadVideo(server.url, server.accessToken, { name: 'video 5' }) 77 expect(logsString.includes('video 6')).to.be.true
61 await waitJobs([ server ]) 78 }
62 79
63 const res = await getLogs(server.url, server.accessToken, now1, now2) 80 {
64 const logsString = JSON.stringify(res.body) 81 const res = await getLogs(server.url, server.accessToken, now, undefined, 'warn')
82 const logsString = JSON.stringify(res.body)
65 83
66 expect(logsString.includes('video 3')).to.be.false 84 expect(logsString.includes('video 6')).to.be.false
67 expect(logsString.includes('video 4')).to.be.true 85 }
68 expect(logsString.includes('video 5')).to.be.false 86 })
69 }) 87 })
70 88
71 it('Should get filter by level', async function () { 89 describe('With the audit log', function () {
72 this.timeout(10000) 90 it('Should get logs with a start date', async function () {
91 this.timeout(10000)
73 92
74 const now = new Date() 93 await uploadVideo(server.url, server.accessToken, { name: 'video 7' })
94 await waitJobs([ server ])
75 95
76 await uploadVideo(server.url, server.accessToken, { name: 'video 6' }) 96 const now = new Date()
77 await waitJobs([ server ])
78 97
79 { 98 await uploadVideo(server.url, server.accessToken, { name: 'video 8' })
80 const res = await getLogs(server.url, server.accessToken, now, undefined, 'info') 99 await waitJobs([ server ])
100
101 const res = await getAuditLogs(server.url, server.accessToken, now)
81 const logsString = JSON.stringify(res.body) 102 const logsString = JSON.stringify(res.body)
82 103
83 expect(logsString.includes('video 6')).to.be.true 104 expect(logsString.includes('video 7')).to.be.false
84 } 105 expect(logsString.includes('video 8')).to.be.true
106
107 expect(res.body).to.have.lengthOf(1)
108
109 const item = res.body[0]
110
111 const message = JSON.parse(item.message)
112 expect(message.domain).to.equal('videos')
113 expect(message.action).to.equal('create')
114 })
115
116 it('Should get logs with an end date', async function () {
117 this.timeout(20000)
118
119 await uploadVideo(server.url, server.accessToken, { name: 'video 9' })
120 await waitJobs([ server ])
121
122 const now1 = new Date()
123
124 await uploadVideo(server.url, server.accessToken, { name: 'video 10' })
125 await waitJobs([ server ])
126
127 const now2 = new Date()
128
129 await uploadVideo(server.url, server.accessToken, { name: 'video 11' })
130 await waitJobs([ server ])
85 131
86 { 132 const res = await getAuditLogs(server.url, server.accessToken, now1, now2)
87 const res = await getLogs(server.url, server.accessToken, now, undefined, 'warn')
88 const logsString = JSON.stringify(res.body) 133 const logsString = JSON.stringify(res.body)
89 134
90 expect(logsString.includes('video 6')).to.be.false 135 expect(logsString.includes('video 9')).to.be.false
91 } 136 expect(logsString.includes('video 10')).to.be.true
137 expect(logsString.includes('video 11')).to.be.false
138 })
92 }) 139 })
93 140
94 after(async function () { 141 after(async function () {