Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 12 additions & 3 deletions functions/send-email-link/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,22 @@ const getRequiredEnv = (name: string): string => {
return value;
};

const createGraphQLClient = (url: string): GraphQLClient => {
const createGraphQLClient = (
url: string,
hostHeaderEnvVar?: string
): GraphQLClient => {
const headers: Record<string, string> = {};

if (process.env.GRAPHQL_AUTH_TOKEN) {
headers.Authorization = `Bearer ${process.env.GRAPHQL_AUTH_TOKEN}`;
}

const envName = hostHeaderEnvVar || 'GRAPHQL_HOST_HEADER';
const hostHeader = process.env[envName];
if (hostHeader) {
headers.host = hostHeader;
}

return new GraphQLClient(url, { headers });
};

Expand Down Expand Up @@ -275,8 +284,8 @@ app.post('*', async (req: any, res: any, next: any) => {
const graphqlUrl = getRequiredEnv('GRAPHQL_URL');
const metaGraphqlUrl = process.env.META_GRAPHQL_URL || graphqlUrl;

const client = createGraphQLClient(graphqlUrl);
const meta = createGraphQLClient(metaGraphqlUrl);
const client = createGraphQLClient(graphqlUrl, 'GRAPHQL_HOST_HEADER');
const meta = createGraphQLClient(metaGraphqlUrl, 'META_GRAPHQL_HOST_HEADER');

const result = await sendEmailLink(params, {
client,
Expand Down
77 changes: 77 additions & 0 deletions jobs/knative-job-fn/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,41 @@ const app: any = express();

app.use(bodyParser.json());

// Basic request logging for all incoming job invocations.
app.use((req: any, res: any, next: any) => {
try {
// Log only the headers we care about plus a shallow body snapshot
const headers = {
'x-worker-id': req.get('X-Worker-Id'),
'x-job-id': req.get('X-Job-Id'),
'x-database-id': req.get('X-Database-Id'),
'x-callback-url': req.get('X-Callback-Url')
};

let body: any;
if (req.body && typeof req.body === 'object') {
// Only log top-level keys to avoid exposing sensitive body contents.
body = { keys: Object.keys(req.body) };
} else if (typeof req.body === 'string') {
// For string bodies, log only the length.
body = { length: req.body.length };
} else {
body = undefined;
}

// eslint-disable-next-line no-console
console.log('[knative-job-fn] Incoming job request', {
method: req.method,
path: req.originalUrl || req.url,
headers,
body
});
} catch {
// best-effort logging; never block the request
}
next();
});

// Echo job headers back on responses for debugging/traceability.
app.use((req: any, res: any, next: any) => {
res.set({
Expand Down Expand Up @@ -151,6 +186,13 @@ app.use((req: any, res: any, next: any) => {
// If an error handler already sent a callback, skip.
if (res.locals.jobCallbackSent) return;
res.locals.jobCallbackSent = true;
// eslint-disable-next-line no-console
console.log('[knative-job-fn] Function completed', {
workerId: ctx.workerId,
jobId: ctx.jobId,
databaseId: ctx.databaseId,
statusCode: res.statusCode
});
void sendJobCallback(ctx, 'success');
});
}
Expand Down Expand Up @@ -185,6 +227,41 @@ export default {
console.error('[knative-job-fn] Failed to send error callback', err);
}

// Log the full error context for debugging.
try {
const headers = {
'x-worker-id': req.get('X-Worker-Id'),
'x-job-id': req.get('X-Job-Id'),
'x-database-id': req.get('X-Database-Id'),
'x-callback-url': req.get('X-Callback-Url')
};
Comment on lines +232 to +237
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header extraction logic is duplicated between the request logging middleware (lines 24-29) and the error logging handler (lines 226-231). Consider extracting this into a shared helper function to improve maintainability and ensure consistency.

Copilot uses AI. Check for mistakes.

// Some error types (e.g. GraphQL ClientError) expose response info.
const errorDetails: any = {
message: error?.message,
name: error?.name,
stack: error?.stack
};

if (error?.response) {
errorDetails.response = {
status: error.response.status,
statusText: error.response.statusText,
errors: error.response.errors,
data: error.response.data
};
}

// eslint-disable-next-line no-console
console.error('[knative-job-fn] Function error', {
headers,
path: req.originalUrl || req.url,
error: errorDetails
});
} catch {
// never throw from the error logger
}

res.status(200).json({ message: error.message });
});
app.listen(port, cb);
Expand Down