new logger
This commit is contained in:
parent
275ae6460d
commit
76e85ad9ec
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
/keys.json
|
/keys.json
|
||||||
*.zone
|
*.zone
|
||||||
/dist
|
/dist
|
||||||
|
/logs
|
||||||
|
@ -15,6 +15,8 @@ This application is intended to be run behind a proxy. Requires node v14+ for `f
|
|||||||
- `PORT` - server port
|
- `PORT` - server port
|
||||||
- `ZONEFILES` - path to zone files
|
- `ZONEFILES` - path to zone files
|
||||||
- `CACHE_TTL` - internal zone cache time-to-live
|
- `CACHE_TTL` - internal zone cache time-to-live
|
||||||
|
- `LOG_DIR` - Logs directory
|
||||||
|
- `LOG_FILES` - Log to files (boolean)
|
||||||
- `RNDC_SERVER` - RNDC host
|
- `RNDC_SERVER` - RNDC host
|
||||||
- `RNDC_PORT` - RNDC port
|
- `RNDC_PORT` - RNDC port
|
||||||
- `RNDC_KEYFILE` - location of RNDC's key file
|
- `RNDC_KEYFILE` - location of RNDC's key file
|
||||||
|
29
src/index.ts
29
src/index.ts
@ -2,6 +2,7 @@ import express, { ErrorRequestHandler, NextFunction, Request, RequestHandler, Re
|
|||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import 'express-async-errors';
|
import 'express-async-errors';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import fs from 'fs/promises';
|
||||||
import { DNSCache } from './dns/cache';
|
import { DNSCache } from './dns/cache';
|
||||||
import { DNSRecordType } from './dns/records';
|
import { DNSRecordType } from './dns/records';
|
||||||
import { ReloadExecutor } from './dns/rndc';
|
import { ReloadExecutor } from './dns/rndc';
|
||||||
@ -10,6 +11,7 @@ import { createZoneFile } from './dns/writer';
|
|||||||
import { fromRequest } from './ip/from-request';
|
import { fromRequest } from './ip/from-request';
|
||||||
import { Keys } from './keys';
|
import { Keys } from './keys';
|
||||||
import { CachedZone } from './models/interfaces';
|
import { CachedZone } from './models/interfaces';
|
||||||
|
import { logger } from './log/Logger';
|
||||||
|
|
||||||
const port = parseInt(process.env.PORT || '9129', 10);
|
const port = parseInt(process.env.PORT || '9129', 10);
|
||||||
const cacheTTL = parseInt(process.env.CACHE_TTL || '2629746', 10);
|
const cacheTTL = parseInt(process.env.CACHE_TTL || '2629746', 10);
|
||||||
@ -223,6 +225,8 @@ api.patch('/zone/records/:domain', domainAuthorization, async (req, res) => {
|
|||||||
res.status(400).json({ success: false, message: 'Updating record(s) failed.', changed, errors });
|
res.status(400).json({ success: false, message: 'Updating record(s) failed.', changed, errors });
|
||||||
} else if (changed.length) {
|
} else if (changed.length) {
|
||||||
res.json({ success: true, message: 'Record(s) changed successfully.', changed, errors });
|
res.json({ success: true, message: 'Record(s) changed successfully.', changed, errors });
|
||||||
|
logger.info('zone %s changed records from %s', domain, req.ip);
|
||||||
|
logger.debug(changed);
|
||||||
} else {
|
} else {
|
||||||
res.json({ success: true, message: 'Nothing was changed.', changed, errors });
|
res.json({ success: true, message: 'Nothing was changed.', changed, errors });
|
||||||
}
|
}
|
||||||
@ -275,6 +279,8 @@ api.delete('/zone/records/:domain', domainAuthorization, async (req, res) => {
|
|||||||
res.status(400).json({ success: false, message: 'Deleting record(s) failed.', deleted, errors });
|
res.status(400).json({ success: false, message: 'Deleting record(s) failed.', deleted, errors });
|
||||||
} else if (deleted.length) {
|
} else if (deleted.length) {
|
||||||
res.json({ success: true, message: 'Record(s) deleted successfully.', deleted, errors });
|
res.json({ success: true, message: 'Record(s) deleted successfully.', deleted, errors });
|
||||||
|
logger.info('zone %s deleted records from %s', domain, req.ip);
|
||||||
|
logger.debug(deleted);
|
||||||
} else {
|
} else {
|
||||||
res.json({ success: true, message: 'Nothing was deleted.', deleted, errors });
|
res.json({ success: true, message: 'Nothing was deleted.', deleted, errors });
|
||||||
}
|
}
|
||||||
@ -380,6 +386,8 @@ api.post('/zone/records/:domain', domainAuthorization, async (req, res) => {
|
|||||||
res.status(400).json({ success: false, message: 'Creating record(s) failed.', created, errors });
|
res.status(400).json({ success: false, message: 'Creating record(s) failed.', created, errors });
|
||||||
} else if (created.length) {
|
} else if (created.length) {
|
||||||
res.status(201).json({ success: true, message: 'Record(s) created successfully.', created, errors });
|
res.status(201).json({ success: true, message: 'Record(s) created successfully.', created, errors });
|
||||||
|
logger.info('zone %s created records from %s', domain, req.ip);
|
||||||
|
logger.debug(created);
|
||||||
} else {
|
} else {
|
||||||
res.json({ success: true, message: 'Nothing was created.', created, errors });
|
res.json({ success: true, message: 'Nothing was created.', created, errors });
|
||||||
}
|
}
|
||||||
@ -423,8 +431,10 @@ api.post('/zone/:domain', domainAuthorization, async (req, res) => {
|
|||||||
|
|
||||||
if (req.body.ttl) {
|
if (req.body.ttl) {
|
||||||
res.json({ success: true, message: 'TTL changed successfully.', ttl: cached.zone.ttl });
|
res.json({ success: true, message: 'TTL changed successfully.', ttl: cached.zone.ttl });
|
||||||
|
logger.info('zone %s set ttl: %d from %s', domain, cached.zone.ttl, req.ip);
|
||||||
} else {
|
} else {
|
||||||
res.json({ success: true, message: 'Zone reloaded successfully.' });
|
res.json({ success: true, message: 'Zone reloaded successfully.' });
|
||||||
|
logger.info('zone %s reload from %s', domain, req.ip);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -505,6 +515,7 @@ api.post('/set-ip/:domain', domainAuthorization, async (req, res) => {
|
|||||||
message: 'Waiting for next request..',
|
message: 'Waiting for next request..',
|
||||||
actions
|
actions
|
||||||
});
|
});
|
||||||
|
logger.info('set-ip (partial) from %s: %s', req.ip, actions.join('\n'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,6 +526,7 @@ api.post('/set-ip/:domain', domainAuthorization, async (req, res) => {
|
|||||||
message: 'Successfully updated zone file.',
|
message: 'Successfully updated zone file.',
|
||||||
actions
|
actions
|
||||||
});
|
});
|
||||||
|
logger.info('set-ip from %s: %s', req.ip, actions.join('\n'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorHandler: ErrorRequestHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
|
const errorHandler: ErrorRequestHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
|
||||||
@ -527,5 +539,18 @@ const errorHandler: ErrorRequestHandler = (err: any, req: Request, res: Response
|
|||||||
api.use(errorHandler);
|
api.use(errorHandler);
|
||||||
app.use('/api/v1', api);
|
app.use('/api/v1', api);
|
||||||
|
|
||||||
keys.load().catch((e) => console.error(e.stack));
|
async function load() {
|
||||||
app.listen(port, () => console.log(`listening on ${port}`));
|
await keys.load();
|
||||||
|
|
||||||
|
if (logger.logToFile) {
|
||||||
|
try {
|
||||||
|
await fs.stat(logger.logDir);
|
||||||
|
} catch {
|
||||||
|
await fs.mkdir(logger.logDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.listen(port, () => logger.info(`listening on ${port}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
load().catch((e) => console.error(e.stack));
|
||||||
|
91
src/log/Logger.ts
Normal file
91
src/log/Logger.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { createWriteStream, WriteStream } from 'fs';
|
||||||
|
import util from 'util';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const p = (x: number) => x.toString().padStart(2, '0')
|
||||||
|
|
||||||
|
export enum LogLevel {
|
||||||
|
Info = "INFO",
|
||||||
|
Warn = "WARN",
|
||||||
|
Error = "ERROR",
|
||||||
|
Debug = "DEBUG"
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
private fileName = '';
|
||||||
|
private day = 0;
|
||||||
|
private stream?: WriteStream;
|
||||||
|
|
||||||
|
constructor(public logDir: string, public logToFile = true) {
|
||||||
|
this.day = new Date().getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
static formatLogDate(date: Date): string {
|
||||||
|
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static formatLogTime(date: Date): string {
|
||||||
|
return `${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static formatLogDateTime(date: Date): string {
|
||||||
|
return Logger.formatLogDate(date) + ' ' + Logger.formatLogTime(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromEnvironment(): Logger {
|
||||||
|
const logsPath = path.resolve(process.env.LOG_DIR || 'logs');
|
||||||
|
const enableFileLog = process.env.LOG_FILES === "true" || true;
|
||||||
|
return new Logger(logsPath, enableFileLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public log(level: LogLevel, message: any, ...fmt: any[]): void {
|
||||||
|
const input = util.format(message, ...fmt);
|
||||||
|
const composed = `[${level.toString().padStart(5)}] [${Logger.formatLogDateTime(new Date())}] ${input}`;
|
||||||
|
|
||||||
|
if (level == LogLevel.Error) {
|
||||||
|
process.stderr.write(`${composed}\r\n`);
|
||||||
|
} else {
|
||||||
|
process.stdout.write(`${composed}\r\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.logToFile) {
|
||||||
|
this.append(composed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(message: any, ...fmt: any[]): void {
|
||||||
|
this.log(LogLevel.Info, message, ...fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(message: any, ...fmt: any[]): void {
|
||||||
|
this.log(LogLevel.Error, message, ...fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(message: any, ...fmt: any[]): void {
|
||||||
|
this.log(LogLevel.Warn, message, ...fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public debug(message: any, ...fmt: any[]): void {
|
||||||
|
this.log(LogLevel.Debug, message, ...fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateOutputFile(): void {
|
||||||
|
const date = new Date();
|
||||||
|
if (this.day !== date.getDate() || !this.stream) {
|
||||||
|
if (this.stream) {
|
||||||
|
this.stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.day = date.getDate();
|
||||||
|
this.fileName = `icydns-${Logger.formatLogDate(date)}.log`;
|
||||||
|
this.stream = createWriteStream(path.join(this.logDir, this.fileName), { flags: 'a' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private append(str: string): void {
|
||||||
|
this.updateOutputFile();
|
||||||
|
this.stream?.write(`${str}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = Logger.fromEnvironment();
|
Reference in New Issue
Block a user