diff --git a/packages/doceditor/config/config.deploy.json b/packages/doceditor/config/config.deploy.json new file mode 100644 index 0000000000..bdd3401e92 --- /dev/null +++ b/packages/doceditor/config/config.deploy.json @@ -0,0 +1,7 @@ +{ + "PORT": "5013", + "HOSTNAME": "localhost", + "app": { + "appsettings": "../../../buildtools/config" + } +} diff --git a/packages/doceditor/config/config.json b/packages/doceditor/config/config.json new file mode 100644 index 0000000000..a0f1b1980a --- /dev/null +++ b/packages/doceditor/config/config.json @@ -0,0 +1,7 @@ +{ + "PORT": "5013", + "HOSTNAME": "localhost", + "app": { + "appsettings": "../../../../buildtools/config" + } +} diff --git a/packages/doceditor/config/index.js b/packages/doceditor/config/index.js new file mode 100644 index 0000000000..0ff0af204d --- /dev/null +++ b/packages/doceditor/config/index.js @@ -0,0 +1,35 @@ +const nconf = require("nconf"); +const path = require("path"); + +const confFile = + typeof process.env.NODE_ENV !== "undefined" + ? "config.json" + : "config.deploy.json"; + +nconf.argv().env().file("config", path.join(__dirname, confFile)); + +getAndSaveAppsettings(); + +function getAndSaveAppsettings() { + let appsettings = nconf.get("app").appsettings; + + if (!path.isAbsolute(appsettings)) { + appsettings = path.join(__dirname, appsettings); + } + + const env = nconf.get("app").environment; + + console.log("environment: " + env); + nconf.file( + "appsettingsWithEnv", + path.join(appsettings, "appsettings." + env + ".json"), + ); + nconf.file("appsettings", path.join(appsettings, "appsettings.json")); + + nconf.file( + "appsettingsServices", + path.join(appsettings, "appsettings.services.json"), + ); +} + +module.exports = nconf; diff --git a/packages/doceditor/lib/logger.js b/packages/doceditor/lib/logger.js new file mode 100644 index 0000000000..71376ed3b6 --- /dev/null +++ b/packages/doceditor/lib/logger.js @@ -0,0 +1,104 @@ +const winston = require("winston"); +const WinstonCloudWatch = require("winston-cloudwatch"); +const date = require("date-and-time"); +const os = require("os"); +const { randomUUID } = require("crypto"); +require("winston-daily-rotate-file"); +const path = require("path"); +const fs = require("fs"); +const config = require("../config/index.js"); + +let logPath = config.get("logPath"); +let logLevel = config.get("logLevel") || "debug"; + +if (logPath != null) { + if (!path.isAbsolute(logPath)) { + logPath = path.join(__dirname, "..", logPath); + } +} + +const fileName = logPath + ? path.join(logPath, "editor.%DATE%.log") + : path.join(__dirname, "..", "..", "..", "Logs", "editor.%DATE%.log"); +const dirName = path.dirname(fileName); + +const aws = config.get("aws").cloudWatch; + +const accessKeyId = aws.accessKeyId; +const secretAccessKey = aws.secretAccessKey; +const awsRegion = aws.region; +const logGroupName = aws.logGroupName; +const logStreamName = aws.logStreamName + .replace("${hostname}", os.hostname()) + .replace("${applicationContext}", "Editor") + .replace("${guid}", randomUUID()) + .replace("${date}", date.format(new Date(), "YYYY/MM/DDTHH.mm.ss")); + +if (!fs.existsSync(dirName)) { + fs.mkdirSync(dirName); +} + +const options = { + file: { + filename: fileName, + level: logLevel, + datePattern: "MM-DD", + handleExceptions: true, + humanReadableUnhandledException: true, + zippedArchive: true, + maxSize: "50m", + maxFiles: "30d", + json: true, + }, + console: { + level: logLevel, + handleExceptions: true, + json: false, + colorize: true, + }, + cloudWatch: { + name: "aws", + level: logLevel, + logStreamName: logStreamName, + logGroupName: logGroupName, + awsRegion: awsRegion, + jsonMessage: true, + awsOptions: { + credentials: { + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, + }, + }, + }, +}; + +let transports = [ + new winston.transports.Console(options.console), + new winston.transports.DailyRotateFile(options.file), +]; + +if (aws != null && aws.accessKeyId !== "") { + transports.push(new WinstonCloudWatch(options.cloudWatch)); +} + +const customFormat = winston.format((info) => { + const now = new Date(); + + info.date = date.format(now, "YYYY-MM-DD HH:mm:ss"); + info.applicationContext = "Editor"; + info.level = info.level.toUpperCase(); + + const hostname = os.hostname(); + + info["instance-id"] = hostname; + + return info; +})(); + +const ws = new winston.createLogger({ + format: winston.format.combine(customFormat, winston.format.json()), + transports: transports, + exitOnError: false, +}); + +module.exports = ws; diff --git a/packages/doceditor/package.json b/packages/doceditor/package.json index d6136f3da6..86b0cc422d 100644 --- a/packages/doceditor/package.json +++ b/packages/doceditor/package.json @@ -4,19 +4,27 @@ "private": true, "scripts": { "build": "node ./scripts/buildTranslations.js && next build", - "start": "node ./scripts/buildTranslations.js && next dev -p 5013", - "start-prod": "next start -p 5013", - "lint": "next lint" + "start": "node ./scripts/buildTranslations.js && NODE_ENV=development node server.js", + "start-prod": "NODE_ENV=production node server.js", + "lint": "next lint", + "clean": "shx rm -rf .next", + "deploy": "shx --silent mkdir -p ../../../publish/web/editor && shx cp -r .next/* ../../../publish/web/editor && shx cp -f server.js ../../../publish/web/editor/server.js && shx cp -r lib/* ../../../publish/web/editor && shx cp -r config/* ../../../publish/web/editor" }, "dependencies": { + "@aws-sdk/client-cloudwatch-logs": "^3.521.0", "@onlyoffice/document-editor-react": "^1.4.1", + "date-and-time": "^3.1.1", "i18next": "^20.6.1", "mobx": "^6.8.0", + "morgan": "^1.10.0", "next": "14.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^14.0.1", - "styled-components": "^5.3.9" + "styled-components": "^5.3.9", + "winston": "^3.11.0", + "winston-cloudwatch": "^6.2.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@svgr/webpack": "^8.1.0", @@ -26,6 +34,7 @@ "eslint": "^8", "eslint-config-next": "14.0.4", "prettier": "^3.2.4", + "shx": "^0.3.4", "typescript": "^5" } } diff --git a/packages/doceditor/server.js b/packages/doceditor/server.js new file mode 100644 index 0000000000..cb8a0d4656 --- /dev/null +++ b/packages/doceditor/server.js @@ -0,0 +1,59 @@ +const logger = require("morgan"); +const { createServer } = require("http"); +const { parse } = require("url"); +const next = require("next"); +const winston = require("./lib/logger"); +const config = require("./config/index.js"); + +const dev = process.env.NODE_ENV === "development"; + +const port = config.get("PORT") ?? 5013; +const hostname = config.get("HOSTNAME") ?? "localhost"; + +// when using middleware `hostname` and `port` must be provided below +const app = next({ dev, hostname, port }); +const handle = app.getRequestHandler(); + +winston.stream = { + write: (message) => winston.info(message), +}; + +// app.use( +// logger("dev", { +// stream: winston.stream, +// skip: function (req, res) { +// if (req.url == "/health") { +// return true; +// } else { +// return false; +// } +// }, +// }), +// ); + +app.prepare().then(() => { + createServer(async (req, res) => { + try { + // Be sure to pass `true` as the second argument to `url.parse`. + // This tells it to parse the query portion of the URL. + const parsedUrl = parse(req.url, true); + + // app.get("/health", (req, res) => { + // res.send({ status: "Healthy" }); + // }); + + await handle(req, res, parsedUrl); + } catch (err) { + winston.error("Error occurred handling", req.url, err); + res.statusCode = 500; + res.end("internal server error"); + } + }) + .once("error", (err) => { + winston.error(err); + process.exit(1); + }) + .listen(port, () => { + winston.info(`Server is listening on port ${port}`); + }); +}); diff --git a/yarn.lock b/yarn.lock index 37ce4a5a0e..83f2497843 100644 --- a/yarn.lock +++ b/yarn.lock @@ -117,7 +117,7 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-cloudwatch-logs@npm:^3.297.0": +"@aws-sdk/client-cloudwatch-logs@npm:^3.297.0, @aws-sdk/client-cloudwatch-logs@npm:^3.521.0": version: 3.521.0 resolution: "@aws-sdk/client-cloudwatch-logs@npm:3.521.0" dependencies: @@ -3034,22 +3034,29 @@ __metadata: version: 0.0.0-use.local resolution: "@docspace/doceditor@workspace:packages/doceditor" dependencies: + "@aws-sdk/client-cloudwatch-logs": "npm:^3.521.0" "@onlyoffice/document-editor-react": "npm:^1.4.1" "@svgr/webpack": "npm:^8.1.0" "@types/node": "npm:^20" "@types/react": "npm:^18" "@types/react-dom": "npm:^18" + date-and-time: "npm:^3.1.1" eslint: "npm:^8" eslint-config-next: "npm:14.0.4" i18next: "npm:^20.6.1" mobx: "npm:^6.8.0" + morgan: "npm:^1.10.0" next: "npm:14.0.4" prettier: "npm:^3.2.4" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-i18next: "npm:^14.0.1" + shx: "npm:^0.3.4" styled-components: "npm:^5.3.9" typescript: "npm:^5" + winston: "npm:^3.11.0" + winston-cloudwatch: "npm:^6.2.0" + winston-daily-rotate-file: "npm:^5.0.0" languageName: unknown linkType: soft @@ -13111,6 +13118,13 @@ __metadata: languageName: node linkType: hard +"date-and-time@npm:^3.1.1": + version: 3.1.1 + resolution: "date-and-time@npm:3.1.1" + checksum: 149c5264e7f8622086c44fab7b29ffae7b2623e22140b97215354844cc31e8aefb773bbac3a4d59b8ea6459f1e428174bafced877ad714733b2479b31001b68e + languageName: node + linkType: hard + "dateformat@npm:^4.5.1": version: 4.6.3 resolution: "dateformat@npm:4.6.3" @@ -22499,6 +22513,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: f498d456a20512ba7be500cef4cf7b3c183cc72c65372a549c9a0e6dd78ce26f375e9b1315c07592d3fde8f10d5019986eba35970570d477ed9a2a702514432a + languageName: node + linkType: hard + "object-inspect@npm:^1.13.1, object-inspect@npm:^1.7.0": version: 1.13.1 resolution: "object-inspect@npm:1.13.1" @@ -27930,7 +27951,7 @@ __metadata: languageName: node linkType: hard -"triple-beam@npm:^1.3.0": +"triple-beam@npm:^1.3.0, triple-beam@npm:^1.4.1": version: 1.4.1 resolution: "triple-beam@npm:1.4.1" checksum: 2e881a3e8e076b6f2b85b9ec9dd4a900d3f5016e6d21183ed98e78f9abcc0149e7d54d79a3f432b23afde46b0885bdcdcbff789f39bc75de796316961ec07f61 @@ -29650,7 +29671,7 @@ __metadata: languageName: node linkType: hard -"winston-cloudwatch@npm:^6.1.1": +"winston-cloudwatch@npm:^6.1.1, winston-cloudwatch@npm:^6.2.0": version: 6.2.0 resolution: "winston-cloudwatch@npm:6.2.0" dependencies: @@ -29682,7 +29703,21 @@ __metadata: languageName: node linkType: hard -"winston-transport@npm:^4.4.0, winston-transport@npm:^4.5.0": +"winston-daily-rotate-file@npm:^5.0.0": + version: 5.0.0 + resolution: "winston-daily-rotate-file@npm:5.0.0" + dependencies: + file-stream-rotator: "npm:^0.6.1" + object-hash: "npm:^3.0.0" + triple-beam: "npm:^1.4.1" + winston-transport: "npm:^4.7.0" + peerDependencies: + winston: ^3 + checksum: 4204deaea0f2a6a9d89747b734547e44d3c30681d6f871d6e146e64ce5807656a6d6fc51057e9c2bda792139356aa36ddd842ce458270e5a77cbd3ed002b7123 + languageName: node + linkType: hard + +"winston-transport@npm:^4.4.0, winston-transport@npm:^4.5.0, winston-transport@npm:^4.7.0": version: 4.7.0 resolution: "winston-transport@npm:4.7.0" dependencies: @@ -29693,7 +29728,7 @@ __metadata: languageName: node linkType: hard -"winston@npm:*, winston@npm:^3.8.2": +"winston@npm:*, winston@npm:^3.11.0, winston@npm:^3.8.2": version: 3.11.0 resolution: "winston@npm:3.11.0" dependencies: