log_rotation

Massimo Melina committed Mar 8, 2022 at 14:50 UTC 9c330c0c2a145cfbb3b71a8fae90957d3017ad47
5 files changed +56 -13
.gitignore
+1 -1
@@ -3,7 +3,7 @@ node_modules
3 dist
4 #produced by running
5 counters.yaml
6 -*.log
6 +*.log*
7 config.yaml
8 accounts.yaml
9 shared/lib
README.md
+7 -4
@@ -76,9 +76,9 @@ This will give you auto-restarting of the server on back-end changes.
76 Set an env `DEV=1` to let the code know we are in a dev environment.
77
78 If you want to work on the frontend and admin too, you should *first*
79 -1. set an env `FRONTEND_PROXY=3000`
79 +1. set an env `FRONTEND_PROXY=3005`
80 2. `npm run start-frontend`
81 -1. set an env `ADMIN_PROXY=3001`
81 +1. set an env `ADMIN_PROXY=3006`
82 2. `npm run start-admin`
83
84 Having this env-s will make the server get all related stuff from the other dev servers.
@@ -193,6 +193,7 @@ Supported entries are:
193 - `admin_port` the port where to reach admin interface. Default is 63636.
194 - `admin_network` the network address where to reach admin interface. Default is 127.0.0.1 .
195 - `log` path of the log file. Default is `access.log`.
196 +- `log_rotation` frequency of log rotation. Accepted values are `daily`, `weekly`, `monthly`, or empty string to disable. Default is `weekly`.
197 - `error_log` path of the log file for errors. Default is `error.log`.
198 - `errors_in_main_log` if you want to use a single file for both kind of entries. Default is false.
199 - `accounts` path of the accounts file. Default is `accounts.yaml`.
@@ -202,7 +203,7 @@ Supported entries are:
203 You can use the special value `auto` to attempt automatic detection.
204 - `max_kbps` throttle output speed. Default is Infinity.
205 - `max_kbps_per_ip` throttle output speed on a per-ip basis. Default is Infinity.
205 -- `zip-calculate-size-for-seconds` how long should we wait before the zip archive starts streaming, trying to understand its finale size. Default is 1.
206 +- `zip_calculate_size_for_seconds` how long should we wait before the zip archive starts streaming, trying to understand its finale size. Default is 1.
207 - `open_browser_at_start` should HFS open browser on localhost on start? Default is true.
208 - `https_port` listen on a specific port. Default is 443.
209 - `cert` use this file for https certificate. Minimum to start https is to give a cert and a private_key. Default is none.
@@ -265,7 +266,9 @@ gather multiple accounts and refer to them collectively as `group1`, so you can
266 Other options you can define as properties of an account:
267
268 - `ignore_limits` to ignore speed limits. Default is `false`.
268 -- `redirect` provide a URL if you want the user to be redirected upon login. Default is none.
269 +- `redirect` provide a URL if you want the user to be redirected upon login. Default is none.
270 +- `admin` set `true` if you want to give access to the Admin interface when it's configured to require login.
271 +- `belongs` an array of usernames of other accounts from which to inherit their permissions.
272
273 ## License
274
admin/src/ConfigPage.ts
+3
@@ -70,6 +70,9 @@ export default function ConfigPage() {
70 { k: 'max_kbps_per_ip', comp: NumberField, label: 'Max KB/s per-ip' },
71 { k: 'log', comp: StringField, label: 'Main log file' },
72 { k: 'error_log', comp: StringField, label: 'Error log file' },
73 + { k: 'log_rotation', comp: SelectField, options: [{ value:'', label:"disabled" }, 'daily', 'weekly', 'monthly' ],
74 + helperText: "To avoid an endlessly-growing single log file, you can opt for rotation"
75 + },
76 { k: 'accounts', comp: StringField, label: 'Accounts file' },
77 { k: 'open_browser_at_start', comp: BoolField },
78 { k: 'zip_calculate_size_for_seconds', comp: NumberField, label: 'Calculate ZIP size for seconds',
server/src/log.ts
+45 -7
@@ -2,18 +2,32 @@
2
3 import Koa from 'koa'
4 import { Writable } from 'stream'
5 -import { getConfig, subscribeConfig } from './config'
5 +import { defineConfig, getConfig, subscribeConfig } from './config'
6 import { createWriteStream } from 'fs'
7 import * as util from 'util'
8 +import { rename, stat } from 'fs/promises'
9
10 class Logger {
11 stream?: Writable
12 + last?: Date
13 + path: string = ''
14
12 - setPath(path: string) {
15 + async setPath(path: string) {
16 + this.path = path
17 this.stream?.end()
18 + this.last = undefined
19 if (!path)
20 return this.stream = undefined
16 - this.stream = createWriteStream(path, { flags: 'a' })
21 + try {
22 + const stats = await stat(path)
23 + this.last = stats.mtime || stats.ctime
24 + }
25 + catch {}
26 + this.reopen()
27 + }
28 +
29 + reopen() {
30 + this.stream = createWriteStream(this.path, { flags: 'a' })
31 }
32 }
33
@@ -30,16 +44,36 @@ subscribeConfig({ k: 'error_log', defaultValue: 'error.log' }, path => {
44 errorLogger.setPath(path)
45 })
46
47 +function getMidnight(date: Date=new Date) {
48 + date.setHours(0,0,0,0)
49 + return date
50 +}
51 +
52 +defineConfig('log_rotation', { defaultValue: 'weekly' })
53 +
54 export function log(): Koa.Middleware {
55 return async (ctx, next) => { // wrapping in a function will make it use current 'mw' value
56 await next()
57 const isError = ctx.status >= 400
37 - let st = isError && !getConfig('errors_in_main_log') && errorLogger.stream || accessLogger.stream
38 - if (!st) return
58 + const logger = isError && !getConfig('errors_in_main_log') && errorLogger || accessLogger
59 + const freq = getConfig('log_rotation')
60 + const { stream, last, path } = logger
61 + if (!stream) return
62 + const now = new Date()
63 + const a = now.toString().split(' ')
64 + if (freq && last) {
65 + const method = freq[0] === 'w' ? 'getDay' : freq[0] === 'm' ? 'getDate' : 'getTime'
66 + if (getMidnight(now)[method]() !== getMidnight(last)[method]()) {
67 + stream.end()
68 + const postfix = last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate())
69 + await rename(path, path + '-' + postfix)
70 + logger.reopen()
71 + }
72 + }
73 + logger.last = now
74 const format = '%s - - [%s] "%s %s HTTP/%s" %d %s\n';
40 - const a = new Date().toString().split(' ')
75 const date = a[2]+'/'+a[1]+'/'+a[3]+':'+a[4]+' '+a[5].slice(3)
42 - st.write(util.format( format,
76 + logger.stream!.write(util.format( format,
77 ctx.ip,
78 date,
79 ctx.method,
@@ -50,3 +84,7 @@ export function log(): Koa.Middleware {
84 ))
85 }
86 }
87 +
88 +function doubleDigit(n: number) {
89 + return n > 9 ? n : '0'+n
90 +}
todo.md
-1
@@ -11,7 +11,6 @@
11 - allowed referer
12 - admin/plugins
13 - download-counter: expose results on admin
14 -- log rotation
14 - log filter option
15 - log filter plugin
16 - publish to npm (so people can "npm install hfs")