diff --git a/packages/streams/logging.js b/packages/streams/logging.js index 8d8d77fcf..68497d570 100644 --- a/packages/streams/logging.js +++ b/packages/streams/logging.js @@ -17,7 +17,7 @@ const { FileTimestampStream } = require('file-timestamp-stream') const path = require('path') let debug = require('debug')('signalk:streams:logging') -const fs = require('fs') +const { readdir, unlink } = require('fs/promises') const { isUndefined } = require('lodash') const filenamePattern = /skserver-raw_\d\d\d\d-\d\d-\d\dT\d\d\.log/ @@ -52,10 +52,11 @@ class FileTimestampStreamWithDelete extends FileTimestampStream { deleteOldFiles() { debug(`Checking for old log files`) - listLogFiles(this.app, (err, files) => { - if (err) { + listLogFiles(this.app) + .catch((err) => { console.error(err) - } else { + }) + .then(async (files) => { if (files.length > this.filesToKeep) { const sortedFiles = files.sort() const numToDelete = files.length - this.filesToKeep @@ -63,17 +64,15 @@ class FileTimestampStreamWithDelete extends FileTimestampStream { for (let i = 0; i < numToDelete; i++) { const fileName = path.join(this.fullLogDir, sortedFiles[i]) debug(`Deleting ${fileName}`) - fs.unlink(fileName, (err) => { - if (err) { - console.error(err) - } else { - debug(`${fileName} was deleted`) - } - }) + try { + await unlink(fileName) + debug(`${fileName} was deleted`) + } catch (err) { + console.error(err) + } } } - } - }) + }) } } @@ -135,15 +134,7 @@ function getFullLogDir(app, logdir) { : path.join(app.config.configPath, logdir) } -function listLogFiles(app, cb) { - fs.readdir(getFullLogDir(app), (err, files) => { - if (!err) { - cb( - undefined, - files.filter((filename) => filename.match(filenamePattern)) - ) - } else { - cb(err) - } - }) +async function listLogFiles(app) { + const files = await readdir(getFullLogDir(app)) + return files.filter((filename) => filename.match(filenamePattern)) } diff --git a/src/app.ts b/src/app.ts index 59b0b9cc0..64ddbadf1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,11 +2,16 @@ import { Delta, FeatureInfo, ServerAPI, SKVersion } from '@signalk/server-api' import { FullSignalK } from '@signalk/signalk-schema' import { EventEmitter } from 'node:events' - import { Config } from './config/config' import DeltaCache from './deltacache' +import { WithSecurityStrategy } from './security' +import { IRouter } from 'express' -export interface ServerApp extends ServerAPI { +export interface ServerApp + extends ServerAPI, + WithSecurityStrategy, + IRouter, + WithConfig { started: boolean interfaces: { [key: string]: any } intervals: NodeJS.Timeout[] diff --git a/src/index.ts b/src/index.ts index 50993ec37..3176de3ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,6 +67,7 @@ import { pipedProviders } from './pipedproviders' import { EventsActorId, WithWrappedEmitter, wrapEmitter } from './events' import { Zones } from './zones' const debug = createDebug('signalk-server') +import logging from './logging' import { StreamBundle } from './streambundle' @@ -95,7 +96,7 @@ class Server { _.merge(app, opts) load(app) - app.logging = require('./logging')(app) + app.logging = logging(app) app.version = '0.0.1' setupCors(app, getSecurityConfig(app)) diff --git a/src/interfaces/logfiles.js b/src/interfaces/logfiles.ts similarity index 67% rename from src/interfaces/logfiles.js rename to src/interfaces/logfiles.ts index 59927574c..c31c0c834 100644 --- a/src/interfaces/logfiles.js +++ b/src/interfaces/logfiles.ts @@ -14,32 +14,33 @@ * limitations under the License. */ -const moment = require('moment') -const path = require('path') -const { getFullLogDir, listLogFiles } = require('@signalk/streams/logging') +import moment from 'moment' +import path from 'path' +import { getFullLogDir, listLogFiles } from '@signalk/streams/logging' import { SERVERROUTESPREFIX } from '../constants' +import { ServerApp } from '../app' -module.exports = function (app) { +module.exports = function (app: ServerApp) { return { - start: function () { + start() { mountApi(app) }, - stop: () => undefined + + stop() {} } } -function mountApi(app) { +function mountApi(app: ServerApp) { app.securityStrategy.addAdminMiddleware(`${SERVERROUTESPREFIX}/logfiles/`) - app.get(`${SERVERROUTESPREFIX}/logfiles/`, function (req, res) { - listLogFiles(app, (err, files) => { - if (err) { - console.error(err) - res.status(500) - res.json('Error reading logfiles list') - return - } + app.get(`${SERVERROUTESPREFIX}/logfiles/`, async function (_, res) { + try { + const files = await listLogFiles(app) res.json(files) - }) + } catch (err) { + console.error(err) + res.status(500) + res.json('Error reading logfiles list') + } }) app.get(`${SERVERROUTESPREFIX}/logfiles/:filename`, function (req, res) { const sanitizedFilename = req.params.filename.replaceAll(/\.\.(\\|\/)/g, '') @@ -63,3 +64,18 @@ function mountApi(app) { }) }) } + +// Add `res.zip` method to the Response interface +declare module 'express-serve-static-core' { + type ZipOptions = { + filename: string + files: { + path: string + name: string + }[] + } + + interface Response { + zip(opts: ZipOptions): void + } +} diff --git a/src/logging.js b/src/logging.ts similarity index 60% rename from src/logging.js rename to src/logging.ts index e6be8e27f..fcce4409d 100644 --- a/src/logging.js +++ b/src/logging.ts @@ -1,20 +1,35 @@ -const debugCore = require('debug') -const moment = require('moment') -const path = require('path') -const fs = require('fs') - -module.exports = function (app) { - const log = [] - let debugEnabled = '' +import debugCore from 'debug' +import moment from 'moment' +import path from 'path' +import fs from 'fs' +import type { SignalKServer } from './types' +import type { WithConfig } from './app' +import type { WrappedEmitter } from './events' + +export interface Logging { + getLog: () => LogMessage[] + enableDebug: (enabled: string) => boolean + getDebugSettings: () => { debugEnabled: string; rememberDebug: boolean } + rememberDebug: (enabled: boolean) => void + addDebug: (name: string) => void + removeDebug: (name: string) => void +} + +type LogMessage = { + ts: string + row: string + isError?: boolean +} + +export default function ( + app: SignalKServer & WithConfig & WrappedEmitter +): Logging { + const log: LogMessage[] = [] + let debugEnabled = process.env.DEBUG ?? '' let rememberDebug = false const size = 100 - let debugPath - - if (process.env.DEBUG) { - debugEnabled = process.env.DEBUG - } + const debugPath = path.join(app.config.configPath, 'debug') - debugPath = path.join(app.config.configPath, 'debug') if (fs.existsSync(debugPath)) { const enabled = fs.readFileSync(debugPath, 'utf8') if (enabled.length > 0) { @@ -24,8 +39,8 @@ module.exports = function (app) { } } - function storeOutput(output, isError) { - const data = { + function storeOutput(output: string, isError: boolean) { + const data: LogMessage = { ts: moment().format('MMM DD HH:mm:ss'), row: output } @@ -47,22 +62,22 @@ module.exports = function (app) { const outWrite = process.stdout.write const errWrite = process.stderr.write - process.stdout.write = function (string) { - outWrite.apply(process.stdout, arguments) - storeOutput(string, false) + process.stdout.write = function (...args) { + storeOutput(args[0].toString(), false) + return outWrite.apply(process.stdout, args as Parameters) } - process.stderr.write = function (string) { - errWrite.apply(process.stderr, arguments) - storeOutput(string, true) + process.stderr.write = function (...args) { + storeOutput(args[0].toString(), true) + return errWrite.apply(process.stderr, args as Parameters) } // send debug to stdout so it does not look like an error debugCore.log = console.info.bind(console) - function enableDebug(enabled) { + function enableDebug(enabled: string) { if (enabled.length > 0) { - let all = enabled.split(',') + const all = enabled.split(',') if (all.indexOf('*') !== -1) { return false @@ -97,7 +112,7 @@ module.exports = function (app) { getDebugSettings: () => { return { debugEnabled, rememberDebug } }, - rememberDebug: (enabled) => { + rememberDebug: (enabled: boolean) => { if (debugPath) { if (enabled) { fs.writeFileSync(debugPath, debugEnabled) @@ -115,7 +130,7 @@ module.exports = function (app) { } }) }, - addDebug: (name) => { + addDebug: (name: string) => { if (debugEnabled.length > 0) { const all = debugEnabled.split(',') if (all.indexOf(name) === -1) { @@ -125,7 +140,7 @@ module.exports = function (app) { enableDebug(name) } }, - removeDebug: (name) => { + removeDebug: (name: string) => { if (debugEnabled.length > 0) { const all = debugEnabled.split(',') const idx = all.indexOf(name) diff --git a/src/serverroutes.ts b/src/serverroutes.ts index d118499df..eeffcf442 100644 --- a/src/serverroutes.ts +++ b/src/serverroutes.ts @@ -51,6 +51,7 @@ import { import { listAllSerialPorts } from './serialports' import { StreamBundle } from './streambundle' import { WithWrappedEmitter } from './events' +import { Logging } from './logging' const readdir = util.promisify(fs.readdir) const debug = createDebug('signalk-server:serverroutes') import { getAISShipTypeName } from '@signalk/signalk-schema' @@ -75,10 +76,7 @@ interface App PluginManager, WithWrappedEmitter { webapps: Package[] - logging: { - rememberDebug: (r: boolean) => void - enableDebug: (r: string) => boolean - } + logging: Logging activateSourcePriorities: () => void streambundle: StreamBundle }