diff --git a/CHANGELOG.md b/CHANGELOG.md index daa80cf9..9658d479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Starting with Peertube 3.2.0, builtin prosody save room history on server. So when a user connects, he can get previously send messages. * Starting with Peertube 3.2.0, builtin prosody also activate mod_muc_moderation, enabling moderators to moderate messages. * Prosody log level will be the same as the Peertube's one. +* Prosody log rotation every 24 hour. ### Fixes diff --git a/ROADMAP.md b/ROADMAP.md index 938dc17c..82a6c43d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -12,7 +12,7 @@ This roadmap is given as an indication. It will be updated as we go along accord [x] | [x] | Common | Chat should not be displayed in playlists | v2.2.0 [x] | [x] | Builtin Prosody | Do not use a temp folder, use the one provided by Peertube>=3.2.0. | Not Released Yet [x] | [ ] | Builtin Prosody | Use Peertube log level for prosody. | Not Released Yet -[ ] | [x] | Builtin Prosody | Rotate prosody logs. +[ ] | [x] | Builtin Prosody | Rotate prosody logs. | Not Released Yet [x] | [x] | Builtin Prosody | Data Persistence | Not Released Yet [ ] | [x] | Common | Add a checkbox per video to activate livechat. Only on lives. [ ] | [x] | Builtin Prosody | Docker: check how to install and use Prosody on docker installations. Do the documentation. diff --git a/package-lock.json b/package-lock.json index 539635bd..8d18e0bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3620,6 +3620,11 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, + "log-rotate": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/log-rotate/-/log-rotate-0.2.8.tgz", + "integrity": "sha512-r9I3eKh2EH+jjbg+tqvACtBjmCdyyyKQsElVF+a/tYCQDOmHQxTvlDEB6GFn2UAZcsF13LTV814bhXspWMu96g==" + }, "logform": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", diff --git a/package.json b/package.json index d7f6b7c4..a7f0a539 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "async": "^3.2.0", "body-parser": "^1.19.0", "decache": "^4.6.0", - "express-http-proxy": "^1.6.2" + "express-http-proxy": "^1.6.2", + "log-rotate": "^0.2.8" }, "devDependencies": { "@purtuga/esm-webpack-plugin": "^1.1.1", diff --git a/server/lib/prosody/ctl.ts b/server/lib/prosody/ctl.ts index ecb46dbc..71c56f5c 100644 --- a/server/lib/prosody/ctl.ts +++ b/server/lib/prosody/ctl.ts @@ -1,4 +1,5 @@ import { getProsodyConfig, getProsodyFilePaths, writeProsodyConfig } from './config' +import { startProsodyLogRotate, stopProsodyLogRotate } from './logrotate' import { changeHttpBindRoute } from '../routers/webchat' import * as fs from 'fs' import * as child_process from 'child_process' @@ -58,6 +59,15 @@ async function getProsodyAbout (options: RegisterServerOptions): Promise return ctl.message } +async function reloadProsody (options: RegisterServerOptions): Promise { + const reload = await prosodyCtl(options, 'reload') + if (reload.code) { + options.peertubeHelpers.logger.error('reloadProsody failed: ' + JSON.stringify(reload)) + return false + } + return true +} + interface ProsodyRunning { ok: boolean messages: string[] @@ -208,9 +218,10 @@ async function ensureProsodyRunning (options: RegisterServerOptions): Promise { @@ -218,6 +229,8 @@ async function ensureProsodyNotRunning (options: RegisterServerOptions): Promise const logger = peertubeHelpers.logger logger.info('Checking if Prosody is running, and shutting it down if so') + stopProsodyLogRotate(options) + // NB: this function is called on plugin unregister, even if prosody is not used // so we must avoid creating the working dir now const filePaths = await getProsodyFilePaths(options) diff --git a/server/lib/prosody/logrotate.ts b/server/lib/prosody/logrotate.ts new file mode 100644 index 00000000..d3c89f99 --- /dev/null +++ b/server/lib/prosody/logrotate.ts @@ -0,0 +1,91 @@ +import type { ProsodyFilePaths } from './config/paths' + +type Rotate = (file: string, options: { + count?: number + compress?: boolean +}, cb: Function) => void +const rotate: Rotate = require('log-rotate') + +interface ProsodyLogRotate { + timer: NodeJS.Timeout + lastRotation: number +} +type ReloadProsody = (options: RegisterServerOptions) => Promise + +let logRotate: ProsodyLogRotate | undefined + +async function _rotate (options: RegisterServerOptions, path: string): Promise { + const p = new Promise((resolve) => { + // I dont use compress. + // I guess that this could cause log losses, because the prosody reload will not happen immediatly. + rotate(path, { count: 14, compress: false }, (err: any) => { + if (err) { + options.peertubeHelpers.logger.error('Failed to rotate file ' + path, err) + return resolve() + } + return resolve() + }) + }) + return p +} + +function startProsodyLogRotate (options: RegisterServerOptions, paths: ProsodyFilePaths, reload: ReloadProsody): void { + const logger = options.peertubeHelpers.logger + const checkInterval = process.env.NODE_ENV === 'test' ? 60 * 1000 : 60 * 60 * 1000 // check every hour + const rotateEvery = process.env.NODE_ENV === 'test' ? 2 * 60 * 1000 : 24 * 60 * 60 * 1000 // rotate every 24hour + // TODO: also rotate when file is too big + + if (logRotate) { + stopProsodyLogRotate(options) + } + + logger.info('Starting Prosody log rotation') + const timer = setInterval(() => { + logger.debug('Checking if Prosody logs need to be rotated') + if (!logRotate) { + logger.error('Seems that we dont need to rotate Prosody logs, but the timer was called.') + return + } + if (logRotate.lastRotation + rotateEvery - 1000 > Date.now()) { + // minus 1000 to not miss next check + logger.debug('To soon to rotate.') + return + } + + logger.info('Rotating Prosody log files.') + logRotate.lastRotation = Date.now() + + const p = Promise.all([ + _rotate(options, paths.log), + _rotate(options, paths.error) + ]) + p.then(() => { + reload(options).then(() => { + logger.debug('Prosody reloaded') + }, () => { + logger.error('Prosody failed to reload') + }) + }, (err) => { + logger.error('Failed rotating logs', err) + }) + }, checkInterval) + + logRotate = { + timer: timer, + lastRotation: Date.now() + } +} + +function stopProsodyLogRotate (options: RegisterServerOptions): void { + const logger = options.peertubeHelpers.logger + if (logRotate === undefined) { + return + } + logger.info('Stoping Prosody log rotation') + clearInterval(logRotate.timer) +} + +export { + startProsodyLogRotate, + stopProsodyLogRotate +}