From 649ed164afd85c4c0079cdc2f7823733b54d3b48 Mon Sep 17 00:00:00 2001 From: Alexander Tsybulsky Date: Sun, 19 Apr 2020 10:13:23 +0300 Subject: [PATCH] Log tail (#25) * docker comments * logtails module * rewire frontend log * minor mistype fixes * docs * test for log tail * delete dummy test * remove duplicate smoke test --- README.md | 11 ++++++++++- docker/Dockerfile | 2 +- src/api/index.ts | 24 +++++++++++++++--------- src/app.smoke.test.ts | 14 -------------- src/app.ts | 17 ++++++----------- src/front_page.ts | 14 ++++++++++++-- src/lib/log-tail.test.ts | 10 ++++++++++ src/lib/log-tail.ts | 29 +++++++++++++++++++++++++++++ test/dummy.test.ts | 3 --- 9 files changed, 83 insertions(+), 41 deletions(-) delete mode 100644 src/app.smoke.test.ts create mode 100644 src/lib/log-tail.test.ts create mode 100644 src/lib/log-tail.ts delete mode 100644 test/dummy.test.ts diff --git a/README.md b/README.md index 91e3e8c1..9e29adf7 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,14 @@ your can change the port adding `-e "port=xxx"` param See the content of `docker` directory. There are Dockerfile and docker-compose template. E.g. run `docker build -f docker/Dockerfile -t abaplint-backend .` to build your image from scratch. -TODO: https docker compose, logging advices +*TODO: https docker compose, logging advices* + +### Env variables + +The package respects `.env` file (must not be committed to the repo though!). Here are the available variables: + +- PORT - port to listen at +- ALB_SUPPRESS_FRONPAGE_LOG - disable frontpage log: set `1` to disable ## Development @@ -50,3 +57,5 @@ Useful scripts - docker - `bin/docker-build.sh` - build docker container from command line (supposes bash environment) - `bin/docker-run.sh` - run the built above container + +See also: [docker dev notes](./docker/dev-notes.md) diff --git a/docker/Dockerfile b/docker/Dockerfile index 25a8a86c..14d40a2c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -27,6 +27,6 @@ COPY ["package*.json", "LICENSE", "./"] RUN npm ci --quiet --only=production && npm cache clean --force --silent COPY --from=builder /usr/src/app/build/ ./build -# HEALTHCHECK --interval=30s CMD node healthcheck.js ? +# HEALTHCHECK --interval=30s CMD node healthcheck.js ? or curl localhost/healthz = OK ? ENTRYPOINT [ "/sbin/tini", "--", "node", "build/server.js" ] diff --git a/src/api/index.ts b/src/api/index.ts index c0038cb2..9f80c8ca 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,6 @@ import * as express from "express"; import {checkObject} from "./check_object"; +import { addInfoEx } from "../lib/log-tail"; const router = express.Router(); @@ -7,7 +8,7 @@ router.use(express.json({limit: "50mb"})); router.use(express.urlencoded({limit: "50mb", extended: false})); router.post("/ping", (_req, res) => { - // addInfo("ping, " + new Date()); + addInfoEx("ping"); res.json({ success: true, payload: "abap is forevah!" }); }); @@ -15,13 +16,17 @@ router.post("/check_file", (req, res) => { // TODO validate request // TODO define response type ? // TODO capture exceptions, return json with error + const hrstart = process.hrtime(); const result = checkObject(req.body); - // addInfo("check_file, " + - // result.object.objectType + " " + - // result.object.objectName + ", " + - // result.issues.length + " issues, " + - // new Date() + ", " + - // req.socket.bytesRead + " bytes"); + const hrend = process.hrtime(hrstart); + addInfoEx([ + "check_file", + result.object.objectType, + result.object.objectName, + `${result.issues.length} issues`, + `${req.socket.bytesRead} bytes`, + `${(hrend[0] * 1000 + hrend[1] / 1000000).toFixed()} ms`, + ]); res.json(result); }); @@ -29,8 +34,9 @@ router.post("/check_file", (req, res) => { // app.post("/api/v1/get_default_configuration", // app.post("/api/v1/pretty_print", -router.all("*", (_req, res) => { - return res.status(404).json({ success: false, error: { message: "Wrong API call" } }); +router.all("*", (req, res) => { + addInfoEx("unexpected API call: " + req.originalUrl); + res.status(404).json({ success: false, error: { message: "Wrong API call" } }); }); export default router; diff --git a/src/app.smoke.test.ts b/src/app.smoke.test.ts deleted file mode 100644 index bf52f886..00000000 --- a/src/app.smoke.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import app from "./app"; -import * as request from "supertest"; - -test("smoke test: ping", async () => { - const res = await request(app).post("/api/v1/ping"); - expect(res.status).toBe(200); - expect(res.body).toEqual({ success: true, payload: "abap is forevah!" }); -}); - -test("smoke test: 404", async () => { - const res = await request(app).get("/api/v1/zzz"); - expect(res.status).toBe(404); - expect(res.body).toEqual({ success: false, error:{ message: "Wrong API call" }}); -}); diff --git a/src/app.ts b/src/app.ts index 14061be3..376c5770 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,27 +3,22 @@ import * as morgan from "morgan"; import * as helmet from "helmet"; import { frontPage } from "./front_page"; import api from "./api"; +import { addInfoEx } from "./lib/log-tail"; const app = express(); -// const info: string[] = []; - -// function addInfo(s: string): void { -// info.push(s); -// if (info.length > 10) { -// info.shift(); -// } -// } - if (process.env.NODE_ENV !== "test") { // app.use(cors()); app.use(helmet()); app.use(morgan("common")); } -app.get("/", (_req, res) => res.send(frontPage([]))); +app.get("/", (_req, res) => res.send(frontPage())); app.get("/healthz", (_req, res) => res.send("OK")); app.use("/api/v1", api); -app.use("*", (_req, res) => res.status(404).send("forbidden") ); +app.use("*", (req, res) => { + addInfoEx("unexpected request: " + req.originalUrl); + res.status(404).send("forbidden"); +}); export default app; diff --git a/src/front_page.ts b/src/front_page.ts index f6766bf7..12769834 100644 --- a/src/front_page.ts +++ b/src/front_page.ts @@ -1,5 +1,6 @@ import * as abaplint from "@abaplint/core"; import * as os from "os"; +import { getLogTail } from "./lib/log-tail"; function osInfo(): string { return "load: " + os.loadavg() + "
" + @@ -10,7 +11,16 @@ function osInfo(): string { "cpus: " + JSON.stringify(os.cpus()) + "
"; } -export function frontPage(info: string[]): string { +function renderLogTail(): string { + if (process.env.ALB_SUPPRESS_FRONPAGE_LOG === "1") { + return ""; + } else { + const info = getLogTail(); + return info.join("
"); + } +} + +export function frontPage(): string { return ` @@ -21,7 +31,7 @@ export function frontPage(info: string[]): string {
${osInfo()}
- ${info.join("
")} + ${renderLogTail()} `; diff --git a/src/lib/log-tail.test.ts b/src/lib/log-tail.test.ts new file mode 100644 index 00000000..80884f40 --- /dev/null +++ b/src/lib/log-tail.test.ts @@ -0,0 +1,10 @@ +import { addInfo, getLogTail } from "./log-tail"; + +test("should add", () => { + addInfo("hello"); + addInfo("world"); + expect(getLogTail()).toEqual([ + "hello", + "world", + ]); +}); diff --git a/src/lib/log-tail.ts b/src/lib/log-tail.ts new file mode 100644 index 00000000..bd118e7f --- /dev/null +++ b/src/lib/log-tail.ts @@ -0,0 +1,29 @@ +const logTail: string[] = []; +let tailLength = 10; // default + +export function setTailLength(newTailLength: number): void { + if (newTailLength <= 0) { + throw Error("Unexpected tailLength"); + } + tailLength = newTailLength; +} + +export function addInfo(s: string): void { + logTail.push(s); + if (logTail.length > tailLength) { + logTail.shift(); + } +} + +export function getLogTail(): string[] { + return logTail; +} + +export function formatDate(date: Date): string { + return date.toISOString().substr(0, 19).replace("T", " "); +} + +export function addInfoEx(entries: string | string[]): void { + entries = Array.isArray(entries) ? entries : [entries]; + addInfo([ formatDate(new Date()), ...entries ].join(", ")); +} diff --git a/test/dummy.test.ts b/test/dummy.test.ts deleted file mode 100644 index b032af5a..00000000 --- a/test/dummy.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -test("should run with jest", () => { - expect(true).toBeTruthy(); -}); \ No newline at end of file