@biblioteksentralen/cloud-run-core
Version:
Core package for NodeJS services using Cloud Run
1 lines • 32.7 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/service/createService.ts","../src/service/createExpressApp.ts","../src/exceptions/AppError.ts","../src/exceptions/assertIsError.ts","../src/middleware/timeout.ts","../src/tracing.ts","../src/logging.ts","../src/middleware/logging.ts","../src/middleware/sentry.ts","../src/service/CloudRunService.ts","../src/exceptions/errorHandler.ts","../src/service/createRouter.ts","../src/pubsub.ts","../src/util/getClientIpAddress.ts","../src/util/isCloudRunEnvironment.ts"],"sourcesContent":["// Service and routing\nexport { createService, type CreateServiceOptions } from \"./service/createService.js\";\nexport { createRouter } from \"./service/createRouter.js\";\nexport type { CloudRunService } from \"./service/CloudRunService.js\";\nexport type { RequestHandler, Response, NextFunction, IRouter, RouterOptions } from \"express\";\n\n// Error handling\nexport * from \"./exceptions/index.js\";\nexport type { SentryOptions } from \"./middleware/sentry.js\";\n\n// PubSub\nexport { parsePubSubMessage, PubSubMessage } from \"./pubsub.js\";\n\n// Logging\nexport { logger, type LogLevel } from \"./logging.js\";\nexport { createPinoLoggerMiddleware } from \"./middleware/logging.js\";\nexport type { Request } from \"./middleware/logging.js\";\nexport type { Logger, ChildLoggerOptions, Bindings } from \"pino\";\n\n// Tracing\nexport { parseTraceparent } from \"./tracing.js\";\n\n// Util\nexport { getClientIpAddress } from \"./util/getClientIpAddress.js\";\nexport { isCloudRunEnvironment } from \"./util/isCloudRunEnvironment.js\";\n","import { randomUUID } from \"node:crypto\";\nimport type { Bindings, ChildLoggerOptions } from \"pino\";\nimport { createExpressApp } from \"./createExpressApp.js\";\nimport { logger as rootLogger } from \"../logging.js\";\nimport { CloudRunService } from \"./CloudRunService.js\";\nimport { SentryOptions } from \"../middleware/sentry.js\";\n\nexport interface CreateServiceOptions {\n /**\n * The port to listen on. Defaults to the PORT environment variable, or 8080.\n */\n port?: number;\n\n /**\n * The Google Cloud project ID. Used in logs to connect logs to traces.\n */\n projectId?: string;\n\n /**\n * Options to be passed to the root Pino logger.\n */\n logOptions?: ChildLoggerOptions;\n\n /**\n * Additional bindings to be added to the root Pino logger.\n */\n logBindings?: Bindings;\n\n /**\n * Whether to parse incoming request bodies using\n * [body-parser](https://www.npmjs.com/package/body-parser). Defaults to true.\n */\n parseBody?: boolean;\n\n /**\n * Maximum incoming request body size. Defaults to \"512mb\". Only used if\n * `parseBody` is true.\n */\n requestLimit?: string;\n\n /**\n * Sentry options. If set, Sentry will be configured for error reporting and tracing.\n */\n sentry?: SentryOptions;\n\n /**\n * Whether the service should handle automatically handle process events\n * (SIGTERM, SIGINT, uncaughtException). Defaults to true, but can be disabled\n * if more fine-grained process control is needed.\n */\n handleProcessEvents?: boolean;\n\n /**\n * Socket timeout in milliseconds (default: none).\n */\n timeoutMsecs?: number;\n}\n\nconst getPort = (options: CreateServiceOptions = {}): number => {\n if (options.port) {\n return options.port;\n }\n const envPort = process.env.PORT ? Number(process.env.PORT) : undefined;\n if (envPort && !Number.isNaN(envPort)) {\n return envPort;\n }\n return 8080;\n};\n\nexport const createService = (name: string, options: CreateServiceOptions = {}): CloudRunService => {\n const serviceName = process.env.K_REVISION ?? name;\n const instanceId = randomUUID(); // Unique ID to support debugging\n const serviceLogger = rootLogger.child({ instanceId });\n const port = getPort(options);\n const handleProcessEvents = options.handleProcessEvents ?? true;\n const router = createExpressApp({\n ...options,\n instanceId,\n });\n const service = new CloudRunService({\n router,\n logger: serviceLogger,\n port,\n name: serviceName,\n instanceId,\n useSentry: !!options.sentry?.dsn,\n });\n\n if (handleProcessEvents) {\n process.on(\"SIGTERM\", () => void service.stop());\n process.on(\"SIGINT\", () => void service.stop());\n process.on(\"uncaughtException\", (err) => {\n serviceLogger.fatal({ err }, `Exited with uncaught exception: ${err?.message}`);\n process.exit(1);\n });\n }\n\n return service;\n};\n","import express from \"express\";\nimport bodyParser from \"body-parser\";\nimport type { Bindings, ChildLoggerOptions } from \"pino\";\nimport { createTimeoutMiddleware } from \"../middleware/timeout.js\";\nimport { createPinoLoggerMiddleware } from \"../middleware/logging.js\";\nimport { SentryOptions, configureSentry } from \"../middleware/sentry.js\";\n\ninterface CreateRouterOptions {\n instanceId: string;\n projectId?: string;\n logOptions?: ChildLoggerOptions;\n logBindings?: Bindings;\n parseBody?: boolean;\n requestLimit?: string;\n timeoutMsecs?: number;\n sentry?: SentryOptions;\n}\n\nexport const createExpressApp = ({\n projectId,\n instanceId,\n logOptions = {},\n logBindings = {},\n parseBody = true,\n requestLimit = \"512mb\",\n timeoutMsecs = 3600000,\n sentry: sentryOptions,\n}: CreateRouterOptions): express.Express => {\n const pinoLoggerMiddleware = createPinoLoggerMiddleware({\n projectId,\n options: logOptions,\n bindings: { ...logBindings, instanceId },\n });\n\n const app = express();\n\n // 1. Configure app settings *first*\n\n // Respect X-Forwarded-For header used by Load Balancer.\n app.enable(\"trust proxy\");\n\n // Disable X-Powered-By header to reduce the ability to fingerprint the server's software:\n // http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header\n app.disable(\"x-powered-by\");\n\n // Disable etag response header\n app.disable(\"etag\");\n\n // 2. Add app middleware\n\n if (sentryOptions?.dsn) {\n // The Sentry request handler must be the first middleware on the app\n configureSentry(app, sentryOptions);\n }\n\n // Then follows Pino as number two\n app.use(pinoLoggerMiddleware);\n\n if (timeoutMsecs) {\n app.use(createTimeoutMiddleware(timeoutMsecs));\n }\n\n if (parseBody) {\n app.use(bodyParser.json({ limit: requestLimit }));\n app.use(bodyParser.urlencoded({ limit: requestLimit, extended: true }));\n }\n\n app.use(\"/favicon.ico|/robots.txt\", (req, res) => {\n // Neither crawlers nor browsers attempting to pull the icon find the body\n // contents particularly useful, so we send nothing in the response body.\n res.status(404).send(null);\n });\n\n return app;\n};\n","export enum HttpCode {\n OK = 200,\n ACCEPTED = 202,\n NO_CONTENT = 204,\n BAD_REQUEST = 400,\n UNAUTHORIZED = 401,\n NOT_FOUND = 404,\n REQUEST_TIMEOUT = 408,\n INTERNAL_SERVER_ERROR = 500,\n SERVICE_UNAVAILABLE = 503,\n}\n\ntype AppErrorOptions = ErrorOptions & {\n httpCode?: HttpCode;\n isOperational?: boolean;\n};\n\nexport class AppError extends Error {\n /**\n * HTTP code to be used when responding to the client.\n */\n public readonly httpCode: HttpCode;\n\n /**\n * Whether the error is operational, i.e. whether it is safe to return the error message to the client.\n */\n public readonly isOperational: boolean;\n\n constructor(message: string, options: AppErrorOptions = {}) {\n super(message, { cause: options.cause });\n Object.setPrototypeOf(this, new.target.prototype);\n this.name = this.constructor.name;\n this.httpCode = options.httpCode ?? HttpCode.INTERNAL_SERVER_ERROR;\n this.isOperational = options.isOperational ?? true;\n Error.captureStackTrace(this);\n }\n}\n\ntype ErrorDetail = {\n code: string;\n message?: string;\n};\n\nexport class ClientRequestError extends AppError {\n /**\n * Request body that caused the error\n */\n request?: unknown;\n\n /**\n * List of validation issues\n */\n details?: ErrorDetail[];\n\n constructor(message: string, args: AppErrorOptions & { request?: unknown; details?: ErrorDetail[] } = {}) {\n const { request, details, ...rest } = args;\n super(message, {\n httpCode: HttpCode.BAD_REQUEST,\n ...rest,\n });\n this.request = request;\n this.details = details;\n }\n}\n\nexport class Unauthorized extends AppError {\n constructor(message: string, args: AppErrorOptions = {}) {\n super(message, {\n httpCode: HttpCode.UNAUTHORIZED,\n ...args,\n });\n }\n}\n\nexport const isAppError = (error: unknown): error is AppError => error instanceof AppError;\n\nexport const isClientError = (error: Error): boolean =>\n isAppError(error) && error.httpCode >= HttpCode.BAD_REQUEST && error.httpCode < HttpCode.INTERNAL_SERVER_ERROR;\n","import { AppError } from \"./AppError.js\";\n\nexport function assertIsError(error: unknown): asserts error is Error {\n if (!(error instanceof Error)) {\n throw new AppError(`Thrown error should be proper Error instances! Got: ${JSON.stringify(error)}`);\n }\n}\n","import { Request, Response, NextFunction } from \"express\";\nimport { AppError } from \"../exceptions/index.js\";\n\nexport const createTimeoutMiddleware = (msecs: number) => (req: Request, res: Response, next: NextFunction) => {\n // Set timeout for all HTTP requests\n req.setTimeout(msecs, () => {\n next(new AppError(\"Request Timeout\", { httpCode: 408 }));\n });\n\n // Set server response timeout for all HTTP requests\n res.setTimeout(msecs, () => {\n next(new AppError(\"Service Timeout\", { httpCode: 503 }));\n });\n\n next();\n};\n","export function parseTraceparent(traceparent: string | undefined) {\n const traceParts = traceparent?.split(/-/g) ?? [];\n return { traceId: traceParts[1], spanId: traceParts[2] };\n}\n","import { pino } from \"pino\";\n\nconst logLevels = [\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\", \"silent\"] as const;\n\nexport type LogLevel = (typeof logLevels)[number];\n\n/**\n * Logs are formatted in a human-readable way with pino-pretty if output is sent to a TTY (meaning\n * that we're not running in GCP and that output is not piped to another process like jq) and the\n * environment variable PINO_PRETTY is not set to \"false\". Otherwise, logs are formatted as JSON for\n * ingest by GCP Cloud Logging.\n */\nconst enablePinoPretty = process.stdout.isTTY && process.env.PINO_PRETTY !== \"false\";\n\nconst getLogLevel = (): LogLevel => {\n const rawLogLevel = process.env.LOG_LEVEL ?? \"debug\";\n const logLevel = logLevels.find((level) => level === rawLogLevel);\n if (!logLevel) {\n throw new Error(`Invalid LOG_LEVEL environment variable: ${rawLogLevel}. Must be one of: ${logLevels.join(\", \")}`);\n }\n return logLevel;\n};\n\nconst logLevel = getLogLevel();\n\nconst transport = enablePinoPretty\n ? {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n messageKey: \"message\",\n levelKey: \"severity\",\n },\n }\n : undefined;\n\nconst pinoConfig: pino.LoggerOptions = {\n level: logLevel,\n transport,\n messageKey: \"message\",\n formatters: {\n /**\n * Changes the shape of the log level to match https://cloud.google.com/logging/docs/structured-logging\n */\n level(severity) {\n return { severity };\n },\n },\n serializers: {\n err: pino.stdSerializers.errWithCause,\n },\n};\n\n/**\n * Pino logger that formats logs as JSON for ingest by Cloud Logging when running in GCP Cloud Run,\n * and uses pino-pretty for human readability otherwise.\n */\nexport const logger = pino(pinoConfig);\n","import type { Request as ExpressRequest, RequestHandler } from \"express\";\nimport { type Bindings, type ChildLoggerOptions, type Logger } from \"pino\";\nimport { parseTraceparent } from \"../tracing.js\";\nimport { logger } from \"../logging.js\";\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n // Add additional properties on express.Request using declaration merging\n interface Request {\n log: Logger;\n }\n }\n}\n\ntype LoggerProps = {\n projectId?: string;\n bindings?: Bindings;\n options?: ChildLoggerOptions;\n};\n\nexport const createPinoLoggerMiddleware =\n ({ projectId, options = {}, bindings = {} }: LoggerProps): RequestHandler =>\n (req, res, next) => {\n const { traceId, spanId } = parseTraceparent(req.get(\"traceparent\"));\n const trace = projectId && traceId ? `projects/${projectId}/traces/${traceId}` : undefined;\n const traceBindings = trace\n ? {\n \"logging.googleapis.com/trace\": trace,\n \"logging.googleapis.com/spanId\": spanId,\n }\n : {};\n req.log = logger.child({ ...bindings, ...traceBindings }, options);\n next();\n };\n\nexport interface Request extends ExpressRequest {\n /**\n * Pino Logger configured for Google Cloud Logging\n */\n log: Logger;\n}\n","import * as Sentry from \"@sentry/node\";\nimport type { Express, RequestHandler } from \"express\";\nimport { parseTraceparent } from \"../tracing.js\";\n\nexport type SentryOptions = Sentry.NodeOptions;\n\nexport function configureSentry(router: Express, options: SentryOptions) {\n Sentry.init(options);\n\n router.use(sentryContextMiddleware);\n}\n\nexport const sentryContextMiddleware: RequestHandler = (req, res, next) => {\n const { traceId } = parseTraceparent(req.get(\"traceparent\"));\n if (traceId) Sentry.setTag(\"gcp_trace\", traceId);\n\n const clientIdentifier = req.get(\"client-identifier\");\n if (clientIdentifier) Sentry.setTag(\"client_identifier\", clientIdentifier);\n\n next();\n};\n","import * as Sentry from \"@sentry/node\";\nimport type { Logger } from \"pino\";\nimport type { Application } from \"express\";\nimport { errorHandler, notFoundHandler } from \"../exceptions/errorHandler.js\";\nimport { HttpTerminator, createHttpTerminator } from \"http-terminator\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\n\nexport type CloudRunServiceOptions = {\n router: Application;\n logger: Logger;\n port: number;\n name: string;\n instanceId: string;\n useSentry?: boolean;\n};\n\ninterface CloudRunServiceEvents {\n shutdown: () => void;\n}\nexport class CloudRunService extends TypedEmitter<CloudRunServiceEvents> {\n public readonly name: string;\n public readonly instanceId: string;\n public readonly router: Application;\n public readonly port: number;\n public readonly logger: Logger;\n private readonly useSentry?: boolean;\n protected httpTerminator?: HttpTerminator = undefined;\n private state: \"initialized\" | \"started\" | \"stopped\" = \"initialized\";\n\n constructor({ router, logger, port, name, instanceId, useSentry }: CloudRunServiceOptions) {\n super();\n this.router = router;\n this.logger = logger;\n this.port = port;\n this.name = name;\n this.instanceId = instanceId;\n this.useSentry = useSentry;\n }\n\n async start() {\n if (this.state !== \"initialized\") {\n throw new Error(`Cannot start ${this.name} instance because it is already started.`);\n }\n\n // 404 Not Found\n this.router.use(notFoundHandler);\n\n // The Sentry error handler must be before any other error middleware and after all controllers\n if (this.useSentry) {\n this.router.use(Sentry.Handlers.errorHandler());\n }\n // Our own error handler should be the very last middleware.\n this.router.use(errorHandler);\n\n return new Promise<void>((resolve) => {\n const server = this.router.listen(this.port, () => {\n this.logger.info(`Started ${this.name} instance ${this.instanceId}, listening on port ${this.port}`);\n this.state = \"started\";\n resolve();\n });\n\n this.httpTerminator = createHttpTerminator({\n server,\n gracefulTerminationTimeout: 5000,\n });\n });\n }\n\n async stop() {\n this.logger.info(`Stopping service ${this.name} instance ${this.instanceId}.`);\n if (this.state === \"stopped\") {\n this.logger.warn(`${this.name} instance is already in state ${this.state}. Forcing shutdown`);\n this.emit(\"shutdown\");\n } else {\n if (this.httpTerminator) {\n // Graceful shutdown: Wait for open connections to terminate normally.\n await this.httpTerminator.terminate();\n }\n this.emit(\"shutdown\");\n this.state = \"stopped\";\n }\n }\n}\n","// Inspired by: https://www.codeconcisely.com/posts/how-to-handle-errors-in-express-with-typescript/\nimport type { ErrorRequestHandler, RequestHandler } from \"express\";\nimport { ClientRequestError, HttpCode, isAppError, isClientError } from \"./AppError.js\";\nimport { parseTraceparent } from \"../tracing.js\";\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport const notFoundHandler: RequestHandler = (req, res) => {\n res.status(404).send(JSON.stringify({ error: \"Route not found\" }));\n};\n\nconst hasType = (err: unknown): err is { type: string } =>\n !!err && typeof err === \"object\" && \"type\" in err && typeof err.type === \"string\";\n\nconst hasBody = (err: unknown): err is { body: string } =>\n !!err && typeof err === \"object\" && \"body\" in err && typeof err.body === \"string\";\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport const errorHandler: ErrorRequestHandler = (err: unknown, req, res, next) => {\n if (res?.headersSent) {\n // Delegate to the default Express error handler\n return next(err);\n }\n\n // Case 1: Operational errors\n if (isAppError(err) && err.isOperational) {\n if (isClientError(err)) {\n req.log.info(err);\n } else {\n req.log.error(err);\n }\n if (res) {\n const { traceId } = parseTraceparent(req.get(\"traceparent\"));\n res.status(err.httpCode).json({\n error: err.message,\n details: err instanceof ClientRequestError ? err.details : undefined,\n traceId,\n });\n }\n return;\n }\n\n // Case 2: Invalid JSON in request body\n if (hasType(err) && err.type === \"entity.parse.failed\") {\n req.log.info(err);\n if (res) {\n const { traceId } = parseTraceparent(req.get(\"traceparent\"));\n res.status(400).json({\n error: String(err),\n details: hasBody(err) ? { body: err.body } : undefined,\n traceId,\n });\n }\n }\n\n // Case 3: Unknown unknowns\n req.log.error(err);\n if (res) {\n const { traceId } = parseTraceparent(req.get(\"traceparent\"));\n res.status(HttpCode.INTERNAL_SERVER_ERROR).json({\n error: \"Det oppsto en ukjent feil. Feilen er logget.\",\n traceId,\n });\n }\n\n req.log.fatal(\n { err },\n `Cloud-run-core error handler received unknown error. Restarting process in case the error is related to state.`,\n );\n process.exit(1);\n};\n","import express, { type IRouter, type RouterOptions } from \"express\";\n\nexport const createRouter = (options?: RouterOptions): IRouter => express.Router(options);\n","import type { Request } from \"express\";\nimport { AppError } from \"./exceptions/AppError.js\";\n\nexport function parsePubSubMessage(req: Request): PubSubMessage {\n const rawMessage = JSON.stringify(req.body);\n try {\n const { message, deliveryAttempt } = parseReceivedMessage(req.body);\n req.log.debug(\n { rawMessage },\n `Received Pub/Sub message ${message.messageId} ${deliveryAttempt ? `(delivery attempt ${deliveryAttempt})` : \"\"}`,\n );\n const { data, ...metadata } = message;\n return {\n data: Buffer.from(data, \"base64\").toString(),\n metadata: {\n deliveryAttempt,\n ...metadata,\n },\n };\n } catch (err) {\n req.log.error({ err, rawMessage }, \"Received invalid Pub/Sub request\");\n throw new AppError(err instanceof Error ? err.message : \"Failed to parse Pub/Sub message\", {\n httpCode: 202, // since there is no point of Pub/Sub retrying an invalid message.\n });\n }\n}\n\nexport type PubSubMessage = {\n data: string;\n metadata: {\n deliveryAttempt?: number;\n messageId: string;\n publishTime?: string; // Not included when using the Pub/Sub emulator\n attributes?: Record<string, unknown>;\n orderingKey?: string;\n };\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === \"object\" && value !== null;\n\nconst isString = (value: unknown): value is string => typeof value === \"string\";\n\nconst isNumber = (value: unknown): value is number => typeof value === \"number\";\n\nconst isUndefined = (value: unknown): value is undefined => typeof value === \"undefined\";\n\nconst parseReceivedMessage = (body: unknown) => {\n // See protos.google.pubsub.v1.IReceivedMessage from \"@google-cloud/pubsub\"\n if (!isRecord(body)) {\n throw new Error(\"Invalid Pub/Sub message: missing body\");\n }\n const { message, subscription, deliveryAttempt } = body;\n if (!isRecord(message)) {\n throw new Error(\"Invalid Pub/Sub message: missing 'message'\");\n }\n if (!isString(subscription)) {\n throw new Error(\"Invalid Pub/Sub message: missing 'subscription'\");\n }\n if (!isNumber(deliveryAttempt) && !isUndefined(deliveryAttempt)) {\n throw new Error(`Invalid Pub/Sub message: invalid 'deliveryAttempt' type: '${typeof deliveryAttempt}'`);\n }\n return {\n message: parseMessage(message),\n subscription,\n deliveryAttempt,\n };\n};\n\nconst parseMessage = (message: Record<string, unknown>) => {\n // See protos.google.pubsub.v1.IPubsubMessage from \"@google-cloud/pubsub\"\n const { messageId, data, publishTime, attributes, orderingKey } = message;\n if (!isString(messageId)) {\n throw new Error(\"Invalid Pub/Sub message: missing 'message.messageId'\");\n }\n if (!isString(data)) {\n throw new Error(\"Invalid Pub/Sub message: missing 'message.data'\");\n }\n if (!isString(publishTime) && !isUndefined(publishTime)) {\n throw new Error(`Invalid Pub/Sub message: invalid 'message.publishTime' type: '${typeof publishTime}'`);\n }\n if (!isRecord(attributes) && !isUndefined(attributes)) {\n throw new Error(`Invalid Pub/Sub message: invalid 'message.attributes' type: '${typeof attributes}'`);\n }\n if (!isString(orderingKey) && !isUndefined(orderingKey)) {\n throw new Error(`Invalid Pub/Sub message: invalid 'message.orderingKey' type: '${typeof orderingKey}'`);\n }\n return {\n messageId,\n data,\n publishTime,\n orderingKey,\n };\n};\n","import type { Request } from \"express\";\n\n/**\n * Get the client IP address when behind a GCP load balancer.\n *\n * When behind a load blancer, the client IP address is the penultimate element in the \n * comma separated x-forwarded-for header.\n * Ref: https://cloud.google.com/load-balancing/docs/https#x-forwarded-for_header\n */\nexport const getClientIpAddress = (req: Request) =>\n typeof req.headers[\"x-forwarded-for\"] === \"string\"\n ? req.headers[\"x-forwarded-for\"].split(\",\").slice(-2)[0]\n : undefined;\n","export const isCloudRunEnvironment = () =>\n !!process.env.CLOUD_RUN_EXECUTION || // => Cloud Run Job\n !!process.env.K_REVISION; // => Cloud Run Service\n"],"mappings":"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","names":["HttpCode","logLevel","express","bodyParser","Sentry","logger","import_express","express"]}