mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Add session ID to server logs for user reporting (#2720)
This commit is contained in:
parent
cfce363fa0
commit
35ac3fa0a7
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from "react-router";
|
||||
import { useLocation } from "react-use";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { getSessionId } from "~/utils/session-id";
|
||||
import {
|
||||
ERROR_GIRL_IMAGE_PATH,
|
||||
LOG_IN_URL,
|
||||
|
|
@ -52,10 +53,11 @@ export function Catcher() {
|
|||
}
|
||||
|
||||
if (!isRouteErrorResponse(error)) {
|
||||
const sessionId = getSessionId();
|
||||
const errorText = (() => {
|
||||
if (!(error instanceof Error)) return;
|
||||
|
||||
return `Time: ${new Date().toISOString()}\nURL: ${location.href}\nUser ID: ${user?.id ?? "Not logged in"}\n${error.stack ?? error.message}`;
|
||||
return `Session ID: ${sessionId}\nTime: ${new Date().toISOString()}\nURL: ${location.href}\nUser ID: ${user?.id ?? "Not logged in"}\n${error.stack ?? error.message}`;
|
||||
})();
|
||||
|
||||
return (
|
||||
|
|
@ -124,12 +126,15 @@ export function Catcher() {
|
|||
<h2>Error {error.status}</h2>
|
||||
<GetHelp />
|
||||
<div className="text-sm text-lighter font-semi-bold">
|
||||
Please include the message below if any and an explanation on what
|
||||
you were doing:
|
||||
Please include the session ID and message below if any and an
|
||||
explanation on what you were doing:
|
||||
</div>
|
||||
{error.data ? (
|
||||
<pre>{JSON.stringify(JSON.parse(error.data), null, 2)}</pre>
|
||||
) : null}
|
||||
<pre>
|
||||
Session ID: {getSessionId()}
|
||||
{error.data
|
||||
? `\n${JSON.stringify(JSON.parse(error.data), null, 2)}`
|
||||
: null}
|
||||
</pre>
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,15 @@ import { I18nextProvider } from "react-i18next";
|
|||
import { HydratedRouter } from "react-router/dom";
|
||||
import { i18nLoader } from "./modules/i18n/loader";
|
||||
import { logger } from "./utils/logger";
|
||||
import { getSessionId } from "./utils/session-id";
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = (input, init) => {
|
||||
const sessionId = getSessionId();
|
||||
const headers = new Headers(init?.headers);
|
||||
headers.set("Sendou-Session-Id", sessionId);
|
||||
return originalFetch(input, { ...init, headers });
|
||||
};
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
|
|
|
|||
|
|
@ -108,3 +108,8 @@ if (!global.appStartSignal && process.env.NODE_ENV === "production") {
|
|||
process.on("unhandledRejection", (reason: string, p: Promise<any>) => {
|
||||
logger.error("Unhandled Rejection at:", p, "reason:", reason);
|
||||
});
|
||||
|
||||
// wrapper so we get request id shown in the server logs
|
||||
export function handleError(error: unknown) {
|
||||
logger.error(error);
|
||||
}
|
||||
|
|
|
|||
18
app/features/session-id/session-id-context.server.ts
Normal file
18
app/features/session-id/session-id-context.server.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
|
||||
interface SessionIdContext {
|
||||
sessionId: string | undefined;
|
||||
}
|
||||
|
||||
export const sessionIdAsyncLocalStorage =
|
||||
new AsyncLocalStorage<SessionIdContext>();
|
||||
|
||||
function getSessionId(): string | undefined {
|
||||
return sessionIdAsyncLocalStorage.getStore()?.sessionId;
|
||||
}
|
||||
|
||||
declare global {
|
||||
var __getServerSessionId: (() => string | undefined) | undefined;
|
||||
}
|
||||
|
||||
globalThis.__getServerSessionId = getSessionId;
|
||||
17
app/features/session-id/session-id-middleware.server.ts
Normal file
17
app/features/session-id/session-id-middleware.server.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { sessionIdAsyncLocalStorage } from "./session-id-context.server";
|
||||
|
||||
type MiddlewareArgs = {
|
||||
request: Request;
|
||||
context: unknown;
|
||||
};
|
||||
|
||||
type MiddlewareFn = (
|
||||
args: MiddlewareArgs,
|
||||
next: () => Promise<Response>,
|
||||
) => Promise<Response>;
|
||||
|
||||
export const sessionIdMiddleware: MiddlewareFn = async ({ request }, next) => {
|
||||
const sessionId = request.headers.get("Sendou-Session-Id") ?? undefined;
|
||||
|
||||
return sessionIdAsyncLocalStorage.run({ sessionId }, () => next());
|
||||
};
|
||||
|
|
@ -37,6 +37,7 @@ import { Ramp } from "./components/ramp/Ramp";
|
|||
import { apiCorsMiddleware } from "./features/api-public/api-cors-middleware.server";
|
||||
import { getUser } from "./features/auth/core/user.server";
|
||||
import { userMiddleware } from "./features/auth/core/user-middleware.server";
|
||||
import { sessionIdMiddleware } from "./features/session-id/session-id-middleware.server";
|
||||
import {
|
||||
isTheme,
|
||||
Theme,
|
||||
|
|
@ -53,6 +54,7 @@ import { allI18nNamespaces } from "./utils/i18n";
|
|||
import { isRevalidation, metaTags, type SerializeFrom } from "./utils/remix";
|
||||
|
||||
export const middleware: Route.MiddlewareFunction[] = [
|
||||
sessionIdMiddleware,
|
||||
apiCorsMiddleware,
|
||||
userMiddleware,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,25 @@
|
|||
/** biome-ignore-all lint/suspicious/noConsole: stub file to enable different solution later */
|
||||
import { getSessionId as getClientSessionId } from "./session-id";
|
||||
|
||||
declare global {
|
||||
var __getServerSessionId: (() => string | undefined) | undefined;
|
||||
}
|
||||
|
||||
function getSessionIdForLog(): string {
|
||||
if (typeof window !== "undefined") {
|
||||
return getClientSessionId();
|
||||
}
|
||||
|
||||
return globalThis.__getServerSessionId?.() ?? "no-session";
|
||||
}
|
||||
|
||||
function formatLog(...args: unknown[]) {
|
||||
const sessionId = getSessionIdForLog();
|
||||
return [`[${sessionId}]`, ...args];
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
info: console.log,
|
||||
error: console.error,
|
||||
warn: console.warn,
|
||||
info: (...args: unknown[]) => console.log(...formatLog(...args)),
|
||||
error: (...args: unknown[]) => console.error(...formatLog(...args)),
|
||||
warn: (...args: unknown[]) => console.warn(...formatLog(...args)),
|
||||
};
|
||||
|
|
|
|||
15
app/utils/session-id.ts
Normal file
15
app/utils/session-id.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { customAlphabet } from "nanoid";
|
||||
|
||||
const nanoid = customAlphabet(
|
||||
// avoid 1/I and 0/O
|
||||
"23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
);
|
||||
|
||||
let sessionId: string | undefined;
|
||||
|
||||
export function getSessionId(): string {
|
||||
if (!sessionId) {
|
||||
sessionId = nanoid(10);
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user