new logger

This commit is contained in:
Evert Prants 2021-07-08 13:57:20 +03:00
parent 275ae6460d
commit 76e85ad9ec
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
4 changed files with 121 additions and 2 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/keys.json
*.zone
/dist
/logs

View File

@ -15,6 +15,8 @@ This application is intended to be run behind a proxy. Requires node v14+ for `f
- `PORT` - server port
- `ZONEFILES` - path to zone files
- `CACHE_TTL` - internal zone cache time-to-live
- `LOG_DIR` - Logs directory
- `LOG_FILES` - Log to files (boolean)
- `RNDC_SERVER` - RNDC host
- `RNDC_PORT` - RNDC port
- `RNDC_KEYFILE` - location of RNDC's key file

View File

@ -2,6 +2,7 @@ import express, { ErrorRequestHandler, NextFunction, Request, RequestHandler, Re
import cors from 'cors';
import 'express-async-errors';
import path from 'path';
import fs from 'fs/promises';
import { DNSCache } from './dns/cache';
import { DNSRecordType } from './dns/records';
import { ReloadExecutor } from './dns/rndc';
@ -10,6 +11,7 @@ import { createZoneFile } from './dns/writer';
import { fromRequest } from './ip/from-request';
import { Keys } from './keys';
import { CachedZone } from './models/interfaces';
import { logger } from './log/Logger';
const port = parseInt(process.env.PORT || '9129', 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 });
} else if (changed.length) {
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 {
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 });
} else if (deleted.length) {
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 {
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 });
} else if (created.length) {
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 {
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) {
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 {
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..',
actions
});
logger.info('set-ip (partial) from %s: %s', req.ip, actions.join('\n'));
return;
}
@ -515,6 +526,7 @@ api.post('/set-ip/:domain', domainAuthorization, async (req, res) => {
message: 'Successfully updated zone file.',
actions
});
logger.info('set-ip from %s: %s', req.ip, actions.join('\n'));
});
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);
app.use('/api/v1', api);
keys.load().catch((e) => console.error(e.stack));
app.listen(port, () => console.log(`listening on ${port}`));
async function load() {
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
View 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();