Skip to content

Commit

Permalink
Improve logging (#604)
Browse files Browse the repository at this point in the history
* Log request status 300 - 400

* Use remote logger in fetch schema

* Properly parse log message

* Format reponse log message better

* Use response end event to capture response log

* Add request received trace log

* Update changelog

* Fix tests
  • Loading branch information
kmcginnes authored Sep 11, 2024
1 parent 4021224 commit 997f20d
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 69 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- **Fixed** context menu styles
([#600](https://github.com/aws/graph-explorer/pull/600))
- **Improved** logging on server and around schema sync
([#604](https://github.com/aws/graph-explorer/pull/604))
- **Improved** support for databases with thousands of node & edge types
([#599](https://github.com/aws/graph-explorer/pull/599))
- **Fixed** alignment of close button in search panel
Expand Down
6 changes: 2 additions & 4 deletions packages/graph-explorer-proxy-server/src/error-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
import { logger, logRequestAndResponse } from "./logging.js";
import { logger } from "./logging.js";

/**
* Global error handler
Expand All @@ -14,16 +14,14 @@ export function handleError(error: unknown) {
export function errorHandlingMiddleware() {
return (
error: unknown,
request: Request,
_request: Request,
response: Response,
_next: NextFunction
) => {
const errorInfo = extractErrorInfo(error);

response.status(errorInfo.status);

logRequestAndResponse(request, response);

response.send({
error: errorInfo,
});
Expand Down
10 changes: 6 additions & 4 deletions packages/graph-explorer-proxy-server/src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function logLevelFromStatusCode(statusCode: number): LogLevel {
} else if (statusCode >= 500) {
return "error";
} else if (statusCode >= 300 && statusCode < 400) {
return "silent";
return "debug";
}
return "debug";
}
Expand All @@ -49,7 +49,7 @@ function logLevelFromStatusCode(statusCode: number): LogLevel {
export function logRequestAndResponse(req: Request, res: Response) {
const logLevel = logLevelFromStatusCode(res.statusCode);

const requestMessage = `${res.statusCode} - ${req.method} ${req.path}`;
const requestMessage = `[${req.method} ${req.path}] Response ${res.statusCode} ${res.statusMessage}`;

switch (logLevel) {
case "debug":
Expand Down Expand Up @@ -79,8 +79,10 @@ export function requestLoggingMiddleware() {
return;
}

// Wait for the request to complete.
req.on("end", () => {
logger.trace(`[${req.method} ${req.path}] Request received`);

// Wait for the response to finish
res.on("finish", () => {
logRequestAndResponse(req, res);
});

Expand Down
4 changes: 2 additions & 2 deletions packages/graph-explorer-proxy-server/src/node-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ app.post("/gremlin", (req, res, next) => {
if (!queryString) {
return res
.status(400)
.send({ error: "[Proxy]Gremlin: query not provided" });
.send({ error: "[Proxy] Gremlin: query not provided" });
}

if (shouldLogDbQuery) {
Expand Down Expand Up @@ -484,7 +484,7 @@ app.post("/logger", (req, res, next) => {
if (headers["message"] === undefined) {
throw new Error("No log message passed.");
} else {
message = headers["message"].replaceAll("\\", "");
message = JSON.parse(headers["message"]).replaceAll("\\", "");
}
if (level.toLowerCase() === "error") {
proxyLogger.error(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function createGremlinExplorer(
const summary = await fetchSummary(connection, featureFlags, options);
return fetchSchema(
_gremlinFetch(connection, featureFlags, options),
remoteLogger,
summary
);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import globalMockFetch from "@/connector/testUtils/globalMockFetch";
import mockGremlinFetch from "@/connector/testUtils/mockGremlinFetch";
import fetchSchema from "./fetchSchema";
import { ClientLoggerConnector } from "@/connector/LoggerConnector";

describe("Gremlin > fetchSchema", () => {
beforeEach(globalMockFetch);

it("Should return an inferred schema", async () => {
const schemaResponse = await fetchSchema(mockGremlinFetch());
const schemaResponse = await fetchSchema(
mockGremlinFetch(),
new ClientLoggerConnector()
);

expect(schemaResponse).toMatchObject({
vertices: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { logger, sanitizeText } from "@/utils";
import { sanitizeText } from "@/utils";
import type { SchemaResponse } from "@/connector/useGEFetchTypes";
import edgeLabelsTemplate from "../templates/edgeLabelsTemplate";
import edgesSchemaTemplate from "../templates/edgesSchemaTemplate";
Expand All @@ -7,6 +7,7 @@ import verticesSchemaTemplate from "../templates/verticesSchemaTemplate";
import type { GEdge, GInt64, GVertex } from "../types";
import { GraphSummary, GremlinFetch } from "../types";
import { chunk } from "lodash";
import { LoggerConnector } from "@/connector/LoggerConnector";

const BATCH_SIZE = 100;

Expand Down Expand Up @@ -87,10 +88,11 @@ type RawEdgesSchemaResponse = {
};

const fetchVertexLabels = async (
gremlinFetch: GremlinFetch
gremlinFetch: GremlinFetch,
remoteLogger: LoggerConnector
): Promise<Record<string, number>> => {
const labelsTemplate = vertexLabelsTemplate();
logger.log("[Gremlin Explorer] Fetching vertex labels with counts...");
remoteLogger.info("[Gremlin Explorer] Fetching vertex labels with counts...");
const data = await gremlinFetch<RawVertexLabelsResponse>(labelsTemplate);

const values = data.result.data["@value"][0]["@value"];
Expand All @@ -99,6 +101,10 @@ const fetchVertexLabels = async (
labelsWithCounts[values[i] as string] = (values[i + 1] as GInt64)["@value"];
}

remoteLogger.info(
`[Gremlin Explorer] Found ${Object.keys(labelsWithCounts).length} vertex labels.`
);

return labelsWithCounts;
};

Expand All @@ -112,6 +118,7 @@ const TYPE_MAP = {

const fetchVerticesAttributes = async (
gremlinFetch: GremlinFetch,
remoteLogger: LoggerConnector,
labels: Array<string>,
countsByLabel: Record<string, number>
): Promise<SchemaResponse["vertices"]> => {
Expand All @@ -124,12 +131,12 @@ const fetchVerticesAttributes = async (
// Batch in to sets of 100
const batches = chunk(labels, BATCH_SIZE);

remoteLogger.info("[Gremlin Explorer] Fetching vertices attributes...");
for (const batch of batches) {
const verticesTemplate = verticesSchemaTemplate({
types: batch,
});

logger.log("[Gremlin Explorer] Fetching vertices attributes...");
const response =
await gremlinFetch<RawVerticesSchemaResponse>(verticesTemplate);
const verticesSchemas = response.result.data["@value"][0]["@value"];
Expand Down Expand Up @@ -157,23 +164,34 @@ const fetchVerticesAttributes = async (
}
}

remoteLogger.info(
`[Gremlin Explorer] Found ${vertices.flatMap(v => v.attributes).length} vertex attributes across ${vertices.length} vertex types.`
);

return vertices;
};

const fetchVerticesSchema = async (
gremlinFetch: GremlinFetch
gremlinFetch: GremlinFetch,
remoteLogger: LoggerConnector
): Promise<SchemaResponse["vertices"]> => {
const countsByLabel = await fetchVertexLabels(gremlinFetch);
const countsByLabel = await fetchVertexLabels(gremlinFetch, remoteLogger);
const labels = Object.keys(countsByLabel);

return fetchVerticesAttributes(gremlinFetch, labels, countsByLabel);
return fetchVerticesAttributes(
gremlinFetch,
remoteLogger,
labels,
countsByLabel
);
};

const fetchEdgeLabels = async (
gremlinFetch: GremlinFetch
gremlinFetch: GremlinFetch,
remoteLogger: LoggerConnector
): Promise<Record<string, number>> => {
const labelsTemplate = edgeLabelsTemplate();
logger.log("[Gremlin Explorer] Fetching edge labels with counts...");
remoteLogger.info("[Gremlin Explorer] Fetching edge labels with counts...");
const data = await gremlinFetch<RawEdgeLabelsResponse>(labelsTemplate);

const values = data.result.data["@value"][0]["@value"];
Expand All @@ -182,11 +200,16 @@ const fetchEdgeLabels = async (
labelsWithCounts[values[i] as string] = (values[i + 1] as GInt64)["@value"];
}

remoteLogger.info(
`[Gremlin Explorer] Found ${Object.keys(labelsWithCounts).length} edge labels.`
);

return labelsWithCounts;
};

const fetchEdgesAttributes = async (
gremlinFetch: GremlinFetch,
remoteLogger: LoggerConnector,
labels: Array<string>,
countsByLabel: Record<string, number>
): Promise<SchemaResponse["edges"]> => {
Expand All @@ -198,11 +221,11 @@ const fetchEdgesAttributes = async (
// Batch in to sets of 100
const batches = chunk(labels, BATCH_SIZE);

remoteLogger.info("[Gremlin Explorer] Fetching edges attributes...");
for (const batch of batches) {
const edgesTemplate = edgesSchemaTemplate({
types: batch,
});
logger.log("[Gremlin Explorer] Fetching edges attributes...");
const data = await gremlinFetch<RawEdgesSchemaResponse>(edgesTemplate);

const edgesSchemas = data.result.data["@value"][0]["@value"];
Expand All @@ -227,16 +250,26 @@ const fetchEdgesAttributes = async (
}
}

remoteLogger.info(
`[Gremlin Explorer] Found ${edges.flatMap(e => e.attributes).length} edge attributes across ${edges.length} edge types.`
);

return edges;
};

const fetchEdgesSchema = async (
gremlinFetch: GremlinFetch
gremlinFetch: GremlinFetch,
remoteLogger: LoggerConnector
): Promise<SchemaResponse["edges"]> => {
const countsByLabel = await fetchEdgeLabels(gremlinFetch);
const countsByLabel = await fetchEdgeLabels(gremlinFetch, remoteLogger);
const labels = Object.keys(countsByLabel);

return fetchEdgesAttributes(gremlinFetch, labels, countsByLabel);
return fetchEdgesAttributes(
gremlinFetch,
remoteLogger,
labels,
countsByLabel
);
};

/**
Expand All @@ -252,21 +285,26 @@ const fetchEdgesSchema = async (
*/
const fetchSchema = async (
gremlinFetch: GremlinFetch,
remoteLogger: LoggerConnector,
summary?: GraphSummary
): Promise<SchemaResponse> => {
if (!summary) {
logger.log("[Gremlin Explorer] No summary statistics");
remoteLogger.info("[Gremlin Explorer] No summary statistics");

const vertices = await fetchVerticesSchema(gremlinFetch);
const vertices = await fetchVerticesSchema(gremlinFetch, remoteLogger);
const totalVertices = vertices.reduce((total, vertex) => {
return total + (vertex.total ?? 0);
}, 0);

const edges = await fetchEdgesSchema(gremlinFetch);
const edges = await fetchEdgesSchema(gremlinFetch, remoteLogger);
const totalEdges = edges.reduce((total, edge) => {
return total + (edge.total ?? 0);
}, 0);

remoteLogger.info(
`[Gremlin Explorer] Schema sync successful (${totalVertices} vertices; ${totalEdges} edges; ${vertices.length} vertex types; ${edges.length} edge types)`
);

return {
totalVertices,
vertices,
Expand All @@ -275,19 +313,25 @@ const fetchSchema = async (
};
}

logger.log("[Gremlin Explorer] Using summary statistics");
remoteLogger.info("[Gremlin Explorer] Using summary statistics");

const vertices = await fetchVerticesAttributes(
gremlinFetch,
remoteLogger,
summary.nodeLabels,
{}
);
const edges = await fetchEdgesAttributes(
gremlinFetch,
remoteLogger,
summary.edgeLabels,
{}
);

remoteLogger.info(
`[Gremlin Explorer] Schema sync successful (${summary.numNodes} vertices; ${summary.numEdges} edges; ${vertices.length} vertex types; ${edges.length} edge types)`
);

return {
totalVertices: summary.numNodes,
vertices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function createOpenCypherExplorer(
);
return fetchSchema(
_openCypherFetch(connection, featureFlags, options),
remoteLogger,
summary
);
},
Expand Down
Loading

0 comments on commit 997f20d

Please sign in to comment.