Skip to content

Commit

Permalink
Setting to enable or disable DB query logging (#575)
Browse files Browse the repository at this point in the history
  • Loading branch information
kmcginnes authored Sep 3, 2024
1 parent 21a930e commit 63128ec
Show file tree
Hide file tree
Showing 16 changed files with 618 additions and 166 deletions.
6 changes: 4 additions & 2 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
exist ([#542](https://github.com/aws/graph-explorer/pull/542))
- **Added** global error page if the React app crashes
([#547](https://github.com/aws/graph-explorer/pull/547))
- **Added** server logging of database queries when using the proxy server
([#574](https://github.com/aws/graph-explorer/pull/574))
- **Added** optional server logging of database queries when using the proxy
server which can be enabled within settings
([#574](https://github.com/aws/graph-explorer/pull/574),
[#575](https://github.com/aws/graph-explorer/pull/575))
- **Improved** handling of server errors with more consistent logging
([#557](https://github.com/aws/graph-explorer/pull/557))
- **Transition** to Tailwind instead of EmotionCSS for styles, which should make
Expand Down
2 changes: 1 addition & 1 deletion packages/graph-explorer-proxy-server/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { clientRoot } from "./paths.js";
import { z } from "zod";

/** Coerces a string to a boolean value in a case insensitive way. */
const BooleanStringSchema = z
export const BooleanStringSchema = z
.string()
.refine(s => s.toLowerCase() === "true" || s.toLowerCase() === "false")
.transform(s => s.toLowerCase() === "true");
Expand Down
6 changes: 6 additions & 0 deletions packages/graph-explorer-proxy-server/src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ export function requestLoggingMiddleware() {
return;
}

// Ignore CORS options requests
if (req.method === "OPTIONS") {
next();
return;
}

// Wait for the request to complete.
req.on("end", () => {
logRequestAndResponse(req, res);
Expand Down
29 changes: 24 additions & 5 deletions packages/graph-explorer-proxy-server/src/node-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IncomingHttpHeaders } from "http";
import { logger as proxyLogger, requestLoggingMiddleware } from "./logging.js";
import { clientRoot, proxyServerRoot } from "./paths.js";
import { errorHandlingMiddleware, handleError } from "./error-handler.js";
import { env } from "./env.js";
import { BooleanStringSchema, env } from "./env.js";

const app = express();

Expand All @@ -23,6 +23,7 @@ interface DbQueryIncomingHttpHeaders extends IncomingHttpHeaders {
"graph-db-connection-url"?: string;
"aws-neptune-region"?: string;
"service-type"?: string;
"db-query-logging-enabled"?: string;
}

interface LoggerIncomingHttpHeaders extends IncomingHttpHeaders {
Expand Down Expand Up @@ -175,6 +176,9 @@ app.post("/sparql", (req, res, next) => {
const headers = req.headers as DbQueryIncomingHttpHeaders;
const queryId = headers["queryid"];
const graphDbConnectionUrl = headers["graph-db-connection-url"];
const shouldLogDbQuery = BooleanStringSchema.default("false").parse(
headers["db-query-logging-enabled"]
);
const isIamEnabled = !!headers["aws-neptune-region"];
const region = isIamEnabled ? headers["aws-neptune-region"] : "";
const serviceType = isIamEnabled
Expand Down Expand Up @@ -228,7 +232,11 @@ app.post("/sparql", (req, res, next) => {
if (!queryString) {
return res.status(400).send({ error: "[Proxy]SPARQL: Query not provided" });
}
proxyLogger.debug("[SPARQL] Received database query:\n%s", queryString);

if (shouldLogDbQuery) {
proxyLogger.debug("[SPARQL] Received database query:\n%s", queryString);
}

const rawUrl = `${graphDbConnectionUrl}/sparql`;
let body = `query=${encodeURIComponent(queryString)}`;
if (queryId) {
Expand Down Expand Up @@ -260,6 +268,9 @@ app.post("/gremlin", (req, res, next) => {
const headers = req.headers as DbQueryIncomingHttpHeaders;
const queryId = headers["queryid"];
const graphDbConnectionUrl = headers["graph-db-connection-url"];
const shouldLogDbQuery = BooleanStringSchema.default("false").parse(
headers["db-query-logging-enabled"]
);
const isIamEnabled = !!headers["aws-neptune-region"];
const region = isIamEnabled ? headers["aws-neptune-region"] : "";
const serviceType = isIamEnabled
Expand All @@ -274,7 +285,9 @@ app.post("/gremlin", (req, res, next) => {
.send({ error: "[Proxy]Gremlin: query not provided" });
}

proxyLogger.debug("[Gremlin] Received database query:\n%s", queryString);
if (shouldLogDbQuery) {
proxyLogger.debug("[Gremlin] Received database query:\n%s", queryString);
}

/// Function to cancel long running queries if the client disappears before completion
async function cancelQuery() {
Expand Down Expand Up @@ -336,6 +349,11 @@ app.post("/gremlin", (req, res, next) => {

// POST endpoint for openCypher queries.
app.post("/openCypher", (req, res, next) => {
const headers = req.headers as DbQueryIncomingHttpHeaders;
const shouldLogDbQuery = BooleanStringSchema.default("false").parse(
headers["db-query-logging-enabled"]
);

const queryString = req.body.query;
// Validate the input before making any external calls.
if (!queryString) {
Expand All @@ -344,9 +362,10 @@ app.post("/openCypher", (req, res, next) => {
.send({ error: "[Proxy]OpenCypher: query not provided" });
}

proxyLogger.debug("[openCypher] Received database query:\n%s", queryString);
if (shouldLogDbQuery) {
proxyLogger.debug("[openCypher] Received database query:\n%s", queryString);
}

const headers = req.headers as DbQueryIncomingHttpHeaders;
const rawUrl = `${headers["graph-db-connection-url"]}/openCypher`;
const requestOptions = {
method: "POST",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type ConnectionConfig } from "@shared/types";
import { DEFAULT_SERVICE_TYPE } from "@/utils/constants";
import { anySignal } from "./utils/anySignal";
import { FeatureFlags } from "@/core";

type NeptuneError = {
code: string;
Expand Down Expand Up @@ -76,11 +77,15 @@ async function decodeErrorSafely(response: Response): Promise<any> {
// Construct the request headers based on the connection settings
function getAuthHeaders(
connection: ConnectionConfig | undefined,
featureFlags: FeatureFlags,
typeHeaders: HeadersInit | undefined
) {
const headers: HeadersInit = {};
if (connection?.proxyConnection) {
headers["graph-db-connection-url"] = connection.graphDbUrl || "";
headers["db-query-logging-enabled"] = String(
featureFlags.allowLoggingDbQuery
);
}
if (connection?.awsAuthEnabled) {
headers["aws-neptune-region"] = connection.awsRegion || "";
Expand All @@ -105,13 +110,14 @@ function getFetchTimeoutSignal(connection: ConnectionConfig | undefined) {

export async function fetchDatabaseRequest(
connection: ConnectionConfig | undefined,
featureFlags: FeatureFlags,
uri: URL | RequestInfo,
options: RequestInit
) {
// Apply connection settings to fetch options
const fetchOptions: RequestInit = {
...options,
headers: getAuthHeaders(connection, options.headers),
headers: getAuthHeaders(connection, featureFlags, options.headers),
signal: anySignal(getFetchTimeoutSignal(connection), options.signal),
};

Expand Down
64 changes: 47 additions & 17 deletions packages/graph-explorer/src/connector/gremlin/gremlinExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ import keywordSearch from "./queries/keywordSearch";
import { fetchDatabaseRequest } from "../fetchDatabaseRequest";
import { GraphSummary } from "./types";
import { v4 } from "uuid";
import { Explorer } from "../useGEFetchTypes";
import { Explorer, ExplorerRequestOptions } from "../useGEFetchTypes";
import { logger } from "@/utils";
import { createLoggerFromConnection } from "@/core/connector";
import { FeatureFlags } from "@/core";

function _gremlinFetch(connection: ConnectionConfig, options: any) {
function _gremlinFetch(
connection: ConnectionConfig,
featureFlags: FeatureFlags,
options?: ExplorerRequestOptions
) {
return async (queryTemplate: string) => {
logger.debug(queryTemplate);
const body = JSON.stringify({ query: queryTemplate });
Expand All @@ -23,22 +28,29 @@ function _gremlinFetch(connection: ConnectionConfig, options: any) {
headers.queryId = options.queryId;
}

return fetchDatabaseRequest(connection, `${connection.url}/gremlin`, {
method: "POST",
headers,
body,
...options,
});
return fetchDatabaseRequest(
connection,
featureFlags,
`${connection.url}/gremlin`,
{
method: "POST",
headers,
body,
...options,
}
);
};
}

async function fetchSummary(
connection: ConnectionConfig,
options: RequestInit
featureFlags: FeatureFlags,
options?: RequestInit
) {
try {
const response = await fetchDatabaseRequest(
connection,
featureFlags,
`${connection.url}/pg/statistics/summary?mode=detailed`,
{
method: "GET",
Expand All @@ -54,33 +66,51 @@ async function fetchSummary(
}
}

export function createGremlinExplorer(connection: ConnectionConfig): Explorer {
export function createGremlinExplorer(
connection: ConnectionConfig,
featureFlags: FeatureFlags
): Explorer {
const remoteLogger = createLoggerFromConnection(connection);
return {
connection: connection,
async fetchSchema(options) {
remoteLogger.info("[Gremlin Explorer] Fetching schema...");
const summary = await fetchSummary(connection, options);
return fetchSchema(_gremlinFetch(connection, options), summary);
const summary = await fetchSummary(connection, featureFlags, options);
return fetchSchema(
_gremlinFetch(connection, featureFlags, options),
summary
);
},
async fetchVertexCountsByType(req, options) {
remoteLogger.info("[Gremlin Explorer] Fetching vertex counts by type...");
return fetchVertexTypeCounts(_gremlinFetch(connection, options), req);
return fetchVertexTypeCounts(
_gremlinFetch(connection, featureFlags, options),
req
);
},
async fetchNeighbors(req, options) {
remoteLogger.info("[Gremlin Explorer] Fetching neighbors...");
return fetchNeighbors(_gremlinFetch(connection, options), req);
return fetchNeighbors(
_gremlinFetch(connection, featureFlags, options),
req
);
},
async fetchNeighborsCount(req, options) {
remoteLogger.info("[Gremlin Explorer] Fetching neighbors count...");
return fetchNeighborsCount(_gremlinFetch(connection, options), req);
return fetchNeighborsCount(
_gremlinFetch(connection, featureFlags, options),
req
);
},
async keywordSearch(req, options) {
options ??= {};
options.queryId = v4();

remoteLogger.info("[Gremlin Explorer] Fetching keyword search...");
return keywordSearch(_gremlinFetch(connection, options), req);
return keywordSearch(
_gremlinFetch(connection, featureFlags, options),
req
);
},
};
} satisfies Explorer;
}
Loading

0 comments on commit 63128ec

Please sign in to comment.