Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 122 additions & 16 deletions apps/webapp/app/db.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,21 @@ function getClient() {
url: databaseUrl.href,
},
},
// @ts-expect-error
log: [
// events
{
emit: "stdout",
emit: "event",
level: "error",
},
{
emit: "event",
level: "info",
},
{
emit: "event",
level: "warn",
},
// stdout
{
emit: "stdout",
level: "info",
Expand All @@ -136,16 +145,60 @@ function getClient() {
emit: "stdout",
level: "warn",
},
].concat(
process.env.VERBOSE_PRISMA_LOGS === "1"
...((process.env.PRISMA_ERRORS_STDOUT_ENABLED === "1"
? [
{
emit: "stdout",
level: "error",
},
]
: []) satisfies Prisma.LogDefinition[]),
// verbose
...((process.env.VERBOSE_PRISMA_LOGS === "1"
? [
{ emit: "event", level: "query" },
{ emit: "stdout", level: "query" },
{
emit: "event",
level: "query",
},
{
emit: "stdout",
level: "query",
},
]
: []
),
: []) satisfies Prisma.LogDefinition[]),
],
});

client.$on("info", (log) => {
logger.info("PrismaClient info", {
clientType: "writer",
timestamp: log.timestamp,
message: log.message,
target: log.target,
});
});

client.$on("warn", (log) => {
logger.warn("PrismaClient warn", {
clientType: "writer",
timestamp: log.timestamp,
message: log.message,
target: log.target,
});
});

// Only use structured logging for errors if we're not already logging them to stdout
if (process.env.PRISMA_ERRORS_STDOUT_ENABLED !== "1") {
client.$on("error", (log) => {
logger.error("PrismaClient error", {
clientType: "writer",
timestamp: log.timestamp,
message: log.message,
target: log.target,
});
});
}

// connect eagerly
client.$connect();

Expand Down Expand Up @@ -174,12 +227,21 @@ function getReplicaClient() {
url: replicaUrl.href,
},
},
// @ts-expect-error
log: [
// events
{
emit: "stdout",
emit: "event",
level: "error",
},
{
emit: "event",
level: "info",
},
{
emit: "event",
level: "warn",
},
// stdout
{
emit: "stdout",
level: "info",
Expand All @@ -188,16 +250,60 @@ function getReplicaClient() {
emit: "stdout",
level: "warn",
},
].concat(
process.env.VERBOSE_PRISMA_LOGS === "1"
...((process.env.PRISMA_ERRORS_STDOUT_ENABLED === "1"
? [
{
emit: "stdout",
level: "error",
},
]
: []) satisfies Prisma.LogDefinition[]),
// verbose
...((process.env.VERBOSE_PRISMA_LOGS === "1"
? [
{ emit: "event", level: "query" },
{ emit: "stdout", level: "query" },
{
emit: "event",
level: "query",
},
{
emit: "stdout",
level: "query",
},
]
: []
),
: []) satisfies Prisma.LogDefinition[]),
],
});

replicaClient.$on("info", (log) => {
logger.info("PrismaClient info", {
clientType: "reader",
timestamp: log.timestamp,
message: log.message,
target: log.target,
});
});

replicaClient.$on("warn", (log) => {
logger.warn("PrismaClient warn", {
clientType: "reader",
timestamp: log.timestamp,
message: log.message,
target: log.target,
});
});

// Only use structured logging for errors if we're not already logging them to stdout
if (process.env.PRISMA_ERRORS_STDOUT_ENABLED !== "1") {
replicaClient.$on("error", (log) => {
logger.error("PrismaClient error", {
clientType: "reader",
timestamp: log.timestamp,
message: log.message,
target: log.target,
});
});
}

// connect eagerly
replicaClient.$connect();

Expand Down
92 changes: 72 additions & 20 deletions apps/webapp/app/v3/eventRepository.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1230,25 +1230,23 @@ export class EventRepository {

return events;
} catch (error) {
if (error instanceof Prisma.PrismaClientUnknownRequestError) {
logger.error("Failed to insert events, most likely because of null characters", {
error: {
name: error.name,
message: error.message,
stack: error.stack,
clientVersion: error.clientVersion,
},
if (isRetriablePrismaError(error)) {
const isKnownError = error instanceof Prisma.PrismaClientKnownRequestError;
span.setAttribute("prisma_error_type", isKnownError ? "known" : "unknown");

const errorDetails = getPrismaErrorDetails(error);
if (errorDetails.code) {
span.setAttribute("prisma_error_code", errorDetails.code);
}

logger.error("Failed to insert events, will attempt bisection", {
error: errorDetails,
});

if (events.length === 1) {
logger.debug("Attempting to insert event individually and it failed", {
event: events[0],
error: {
name: error.name,
message: error.message,
stack: error.stack,
clientVersion: error.clientVersion,
},
error: errorDetails,
});

span.setAttribute("failed_event_count", 1);
Expand All @@ -1258,12 +1256,7 @@ export class EventRepository {

if (depth > MAX_FLUSH_DEPTH) {
logger.error("Failed to insert events, reached maximum depth", {
error: {
name: error.name,
message: error.message,
stack: error.stack,
clientVersion: error.clientVersion,
},
error: errorDetails,
depth,
eventsCount: events.length,
});
Expand Down Expand Up @@ -1917,3 +1910,62 @@ export async function recordRunDebugLog(
},
});
}

/**
* Extracts error details from Prisma errors in a type-safe way.
* Only includes 'code' property for PrismaClientKnownRequestError.
*/
function getPrismaErrorDetails(
error: Prisma.PrismaClientUnknownRequestError | Prisma.PrismaClientKnownRequestError
): {
name: string;
message: string;
stack: string | undefined;
clientVersion: string;
code?: string;
} {
const base = {
name: error.name,
message: error.message,
stack: error.stack,
clientVersion: error.clientVersion,
};

if (error instanceof Prisma.PrismaClientKnownRequestError) {
return { ...base, code: error.code };
}

return base;
}

/**
* Checks if a PrismaClientKnownRequestError is a Unicode/hex escape error.
*/
function isUnicodeError(error: Prisma.PrismaClientKnownRequestError): boolean {
return (
error.message.includes("lone leading surrogate in hex escape") ||
error.message.includes("unexpected end of hex escape") ||
error.message.includes("invalid Unicode") ||
error.message.includes("invalid escape sequence")
);
}

/**
* Determines if a Prisma error should be retried with bisection logic.
* Returns true for errors that might be resolved by splitting the batch.
*/
function isRetriablePrismaError(
error: unknown
): error is Prisma.PrismaClientUnknownRequestError | Prisma.PrismaClientKnownRequestError {
if (error instanceof Prisma.PrismaClientUnknownRequestError) {
// Always retry unknown errors with bisection
return true;
}

if (error instanceof Prisma.PrismaClientKnownRequestError) {
// Only retry known errors if they're Unicode/hex escape related
return isUnicodeError(error);
}

return false;
}
Loading