-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Logger status #33
Logger status #33
Changes from 14 commits
57b6937
94fce30
639fde4
e4e864a
6deabb7
84ceb2c
efa2ed9
40b1df0
6834d72
c34cb9a
20217ae
ff748f6
05d5e98
a7cf4f3
e4709cc
b7807a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/bin/bash -e | ||
|
||
LATEST_SEMVER=$(git describe --tags --abbrev=0 --always) | ||
SHORT_COMMIT=$(git rev-parse HEAD | cut -c1-8) | ||
echo "$LATEST_SEMVER-$SHORT_COMMIT" > .version |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,21 @@ | ||
import express, { Express, NextFunction, Request, Response } from "express"; | ||
import { request, ClientRequest, RequestOptions, IncomingMessage } from "http"; | ||
import {writeStatusToDisk} from "./status"; | ||
|
||
const app: Express = express(); | ||
const port: number = 80; | ||
|
||
const serviceLaunchTime = Math.round(new Date().getTime() / 1000); | ||
const statusFilePath = process.env.STATUS_FILE_PATH || "/opt/orbs/status/status.json"; | ||
|
||
let error = ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I asked ChatGPT for advice about how we can avoid this "global" error variable and it suggested this: import express, { Express, NextFunction, Request, Response } from "express";
import { request, ClientRequest, RequestOptions, IncomingMessage } from "http";
import { writeStatusToDisk } from "./status";
const app: Express = express();
const port: number = 80;
const serviceLaunchTime = Math.round(new Date().getTime() / 1000);
const statusFilePath = process.env.STATUS_FILE_PATH || "/opt/orbs/status/status.json";
setInterval(() => {
writeStatusToDisk(statusFilePath, serviceLaunchTime);
}, 5 * 60 * 1000);
const validNameRegex = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
app.use((_: Request, res: Response, next: NextFunction) => {
res.setHeader("Content-Disposition", "inline");
res.setHeader("Content-Type", "text/plain; charset=utf-8");
next();
});
const decodeDockerLogs = (data: Buffer): string => {
// ... [no change in this function]
};
app.get("/service/:name/log", (req: Request, res: Response, next: NextFunction) => {
const containerName: string = req.params.name;
if (!validNameRegex.test(containerName)) {
return next(new Error("Invalid container name"));
}
const options: RequestOptions = {
// ... [no change in this part]
};
const clientRequest: ClientRequest = request(options, (resp: IncomingMessage) => {
// ... [no change in this function]
});
clientRequest.on("error", (e: Error) => {
next(e); // Pass the error to the error handling middleware
});
clientRequest.end();
});
// Dedicated error handling middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.message);
writeStatusToDisk(statusFilePath, serviceLaunchTime, err.message); // Log the error
res.status(500).send("An unexpected error occurred. Try again later");
});
app.listen(port, () => {
console.log(`Logging service listening at http://localhost:${port}`);
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think? I do like that it centralises the whole error handling logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So how does it work? |
||
|
||
setInterval(function status() { // setInterval that also run immediately | ||
writeStatusToDisk(statusFilePath, serviceLaunchTime, error); | ||
error = ''; | ||
return status; | ||
}(), 5 * 60 * 1000); | ||
|
||
// TODO: This can happen at the nginx level | ||
const validNameRegex = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/; | ||
|
||
|
@@ -28,11 +40,12 @@ const decodeDockerLogs = (data: Buffer): string => { | |
return str; | ||
}; | ||
|
||
app.get("/service/:name/logs", (req: Request, res: Response) => { | ||
app.get("/service/:name/log", (req: Request, res: Response) => { | ||
const containerName: string = req.params.name; | ||
|
||
if (!validNameRegex.test(containerName)) { | ||
return res.status(400).send("Invalid container name"); | ||
error = "Invalid container name"; | ||
return res.status(400).send(error); | ||
} | ||
|
||
const options: RequestOptions = { | ||
|
@@ -42,33 +55,36 @@ app.get("/service/:name/logs", (req: Request, res: Response) => { | |
}; | ||
|
||
const clientRequest: ClientRequest = request( | ||
options, | ||
(resp: IncomingMessage) => { | ||
if (resp.statusCode === 404) { | ||
// TODO: add proper logger | ||
console.log( | ||
`User ${req.ip} requested logs for non-existent service ${containerName}` | ||
); | ||
res.status(404).send("Service not found"); | ||
} else if (resp.statusCode !== 200) { | ||
console.error("500 error: ", resp); | ||
res.status(500).send("An internal error occurred. Try again later"); | ||
} else { | ||
console.log("BACK TO SQUARE THREE!!!"); | ||
let data = ""; | ||
// Log will be max 10MB due to log rotation | ||
resp.on("data", (chunk: Buffer) => { | ||
const logs = decodeDockerLogs(chunk); | ||
data += logs; | ||
}); | ||
resp.on("end", () => { | ||
res.send(data); | ||
}); | ||
options, | ||
(resp: IncomingMessage) => { | ||
if (resp.statusCode === 404) { | ||
// TODO: add proper logger | ||
console.log( | ||
`User ${req.ip} requested logs for non-existent service ${containerName}` | ||
); | ||
error = "Service not found"; | ||
res.status(404).send(error); | ||
} else if (resp.statusCode !== 200) { | ||
error = String(resp.statusMessage); | ||
console.error("500 error: ", resp); | ||
res.status(500).send("An internal error occurred. Try again later"); | ||
} else { | ||
console.log("BACK TO SQUARE THREE!!!"); | ||
let data = ""; | ||
// Log will be max 10MB due to log rotation | ||
resp.on("data", (chunk: Buffer) => { | ||
const logs = decodeDockerLogs(chunk); | ||
data += logs; | ||
}); | ||
resp.on("end", () => { | ||
res.send(data); | ||
}); | ||
} | ||
} | ||
} | ||
); | ||
|
||
clientRequest.on("error", (e: Error) => { | ||
error = e.message; | ||
console.error("onError: ", e); | ||
res.status(500).send("An unexpected error occurred. Try again later"); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import * as fs from 'fs'; | ||
import {dirname} from "path"; | ||
|
||
export function generateStatusObj(serviceLaunchTime: number, err?: string) { | ||
let status: any = {} | ||
if (err) { | ||
status['Status'] = 'Error'; | ||
status['Error'] = err; | ||
} | ||
else { | ||
status['Status'] = 'OK'; | ||
} | ||
status = Object.assign({ | ||
Timestamp: new Date().toISOString(), | ||
Payload: { | ||
Uptime: Math.round(new Date().getTime() / 1000) - serviceLaunchTime, | ||
MemoryBytesUsed: process.memoryUsage().heapUsed, | ||
Version: { | ||
Semantic: getCurrentVersion(), | ||
}, | ||
}, | ||
}, status); | ||
return status; | ||
} | ||
|
||
export function writeStatusToDisk(filePath: string, serviceLaunchTime: number, err?: string) { | ||
const status = generateStatusObj(serviceLaunchTime, err); | ||
|
||
fs.mkdirSync(dirname(filePath), { recursive: true }); | ||
const content = JSON.stringify(status, null, 2); | ||
fs.writeFileSync(filePath, content); | ||
|
||
console.log(`Wrote status JSON to ${filePath} (${content.length} bytes).`); | ||
} | ||
|
||
export function getCurrentVersion() { | ||
try { | ||
return fs.readFileSync('/app/.version').toString().trim(); | ||
} catch (err: any) { | ||
console.error(`Could not find version: ${err.message}`); | ||
} | ||
return ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just return something like "Version not found" for clarity? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,34 @@ | ||
#!/bin/bash | ||
|
||
echo -e "${BLUE}Performing a health check...${NC}\n" | ||
check_services() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could also look at using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, wasn't familiar. |
||
compose_file="$HOME/deployment/docker-compose.yml" | ||
# Get the number of services defined in docker-compose file | ||
num_services=$(docker-compose -f $compose_file config --services | wc -l) | ||
|
||
sleep 10 # Wait for management service to start | ||
for i in {1..5} | ||
do | ||
# Get the number of services that are up | ||
num_up=$(docker-compose -f $compose_file ps | grep "Up" | wc -l) | ||
if [ $num_up -eq $num_services ]; then | ||
echo "All services are up and running." | ||
return 0 | ||
else | ||
echo "Waiting for services to start..." | ||
sleep 5 | ||
fi | ||
done | ||
return 1 | ||
} | ||
|
||
mgmt_svs_status_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/service/ethereum-reader/status) | ||
if [ $mgmt_svs_status_code -eq 200 ]; then | ||
echo -e "${GREEN}Installation complete! 🚀🚀🚀${NC}" | ||
echo "------------------------------------" | ||
echo -e "\n👉👉👉 ${YELLOW}Please register your Guardian using the following website: https://guardians.orbs.network?name=$name&website=$website&ip=$myip&node_address=$public_add ${NC} 👈👈👈\n" # TODO: only show once - during first installation | ||
if check_services; then | ||
mgmt_svs_status_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/service/ethereum-reader/status) | ||
if [ $mgmt_svs_status_code -eq 200 ]; then | ||
echo -e "${GREEN}Installation complete! 🚀🚀🚀${NC}" | ||
echo "------------------------------------" | ||
echo -e "\n👉👉👉 ${YELLOW}Please register your Guardian using the following website: https://guardians.orbs.network?name=$name&website=$website&ip=$myip&node_address=$public_add ${NC} 👈👈👈\n" # TODO: only show once - during first installation | ||
else | ||
echo -e "${RED}Installation incomplete!${NC}" | ||
fi | ||
else | ||
echo -e "${RED}Installation incomplete!${NC}" | ||
echo -e "${RED}Installation incomplete!${NC}" | ||
fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I be annoying and ask you to run Prettier on this file? Formatting is quite different in places