diff --git a/runtimes/nodejs/src/cloud-sdk/index.ts b/runtimes/nodejs/src/cloud-sdk/index.ts index a74b3fe302..d0d3cfd947 100644 --- a/runtimes/nodejs/src/cloud-sdk/index.ts +++ b/runtimes/nodejs/src/cloud-sdk/index.ts @@ -1,51 +1,51 @@ -import { AxiosStatic } from "axios"; -import { Db, getDb } from "database-proxy"; -import { CloudFunction, FunctionContext } from "../support/function-engine"; -import * as mongodb from "mongodb"; -import { DatabaseAgent } from "../db"; -import request from "axios"; -import { SchedulerInstance } from "../support/scheduler"; -import { getToken, parseToken } from "../support/token"; -import { WebSocket } from "ws"; -import { WebSocketAgent } from "../support/ws"; -import Config from "../config"; +import { AxiosStatic } from 'axios' +import { Db, getDb } from 'database-proxy' +import { CloudFunction, FunctionContext } from '../support/function-engine' +import * as mongodb from 'mongodb' +import { DatabaseAgent } from '../db' +import request from 'axios' +import { SchedulerInstance } from '../support/scheduler' +import { getToken, parseToken } from '../support/token' +import { WebSocket } from 'ws' +import { WebSocketAgent } from '../support/ws' +import Config from '../config' export type InvokeFunctionType = ( name: string, - param: FunctionContext -) => Promise; -export type EmitFunctionType = (event: string, param: any) => void; -export type GetTokenFunctionType = (payload: any, secret?: string) => string; + param: FunctionContext, +) => Promise +export type EmitFunctionType = (event: string, param: any) => void +export type GetTokenFunctionType = (payload: any, secret?: string) => string export type ParseTokenFunctionType = ( token: string, - secret?: string -) => any | null; + secret?: string, +) => any | null export interface MongoDriverObject { - client: mongodb.MongoClient; - db: mongodb.Db; + client: mongodb.MongoClient + db: mongodb.Db } export interface CloudSdkInterface { /** * Sending an HTTP request is actually an Axios instance. You can refer to the Axios documentation directly */ - fetch: AxiosStatic; + fetch: AxiosStatic /** * Get a laf.js database-ql instance */ - database(): Db; + database(): Db /** * Invoke cloud function */ - invoke: InvokeFunctionType; + invoke: InvokeFunctionType /** * Emit a cloud function event that other cloud functions can set triggers to listen for */ - emit: EmitFunctionType; + emit: EmitFunctionType /** * Cloud function global memory `shared` object, which can share data across multiple requests and different cloud functions @@ -53,17 +53,17 @@ export interface CloudSdkInterface { * 2. You can share some common methods, such as checkPermission(), to improve the performance of cloud functions * 3. It can cache hot data and is recommended to use it in a small amount (this object is allocated in the node VM heap because of the memory limit of the node VM heap) */ - shared: Map; + shared: Map /** * Generate a JWT Token, if don't provide `secret` fields, use current server secret key to do signature */ - getToken: GetTokenFunctionType; + getToken: GetTokenFunctionType /** * Parse a JWT Token, if don't provide `secret` fields, use current server secret key to verify signature */ - parseToken: ParseTokenFunctionType; + parseToken: ParseTokenFunctionType /** * The mongodb instance of MongoDB node.js native driver. @@ -93,44 +93,44 @@ export interface CloudSdkInterface { * .toArray() * ``` */ - mongo: MongoDriverObject; + mongo: MongoDriverObject /** * Websocket connection list */ - sockets: Set; + sockets: Set /** * Current app id */ - appid: string; + appid: string env: { - DB_URI?: string; - SERVER_SECRET?: string; - APP_ID?: string; - OSS_ACCESS_KEY?: string; - OSS_ACCESS_SECRET?: string; - OSS_REGION?: string; - OSS_INTERNAL_ENDPOINT?: string; - OSS_EXTERNAL_ENDPOINT?: string; - NPM_INSTALL_FLAGS?: string; - RUNTIME_IMAGE?: string; - }; + DB_URI?: string + SERVER_SECRET?: string + APP_ID?: string + OSS_ACCESS_KEY?: string + OSS_ACCESS_SECRET?: string + OSS_REGION?: string + OSS_INTERNAL_ENDPOINT?: string + OSS_EXTERNAL_ENDPOINT?: string + NPM_INSTALL_FLAGS?: string + RUNTIME_IMAGE?: string + } } /** * Cloud SDK instance */ -const cloud: CloudSdkInterface = create(); +const cloud: CloudSdkInterface = create() /** * After the database connection is successful, update its Mongo object, otherwise it is null */ DatabaseAgent.accessor.ready.then(() => { - cloud.mongo.client = DatabaseAgent.accessor.conn; - cloud.mongo.db = DatabaseAgent.accessor.db; -}); + cloud.mongo.client = DatabaseAgent.accessor.conn + cloud.mongo.db = DatabaseAgent.accessor.db +}) /** * Create a new Cloud SDK instance @@ -161,14 +161,14 @@ export function create() { OSS_REGION: process.env.OSS_REGION, OSS_INTERNAL_ENDPOINT: process.env.OSS_INTERNAL_ENDPOINT, OSS_EXTERNAL_ENDPOINT: process.env.OSS_EXTERNAL_ENDPOINT, - NPM_INSTALL_FLAGS: process.env.NPM_INSTALL_FLAGS || "", + NPM_INSTALL_FLAGS: process.env.NPM_INSTALL_FLAGS || '', RUNTIME_IMAGE: process.env.RUNTIME_IMAGE, }, - }; - return cloud; + } + return cloud } -export default cloud; +export default cloud /** * The cloud function is invoked in the cloud function, which runs in the cloud function. @@ -178,24 +178,25 @@ export default cloud; * @returns */ async function invokeInFunction(name: string, param?: FunctionContext) { - const data = await CloudFunction.getFunctionByName(name); - const func = new CloudFunction(data); + const data = await CloudFunction.getFunctionByName(name) + const func = new CloudFunction(data) if (!func) { - throw new Error(`invoke() failed to get function: ${name}`); + throw new Error(`invoke() failed to get function: ${name}`) } - param = param ?? {}; + param = param ?? {} as any + param.__function_name = name - param.requestId = param.requestId ?? "invoke"; + param.requestId = param.requestId ?? 'invoke' - param.method = param.method ?? "call"; + param.method = param.method ?? 'call' - const result = await func.invoke(param); + const result = await func.invoke(param) if (result.error) { - throw result.error; + throw result.error } - return result.data; + return result.data } diff --git a/runtimes/nodejs/src/handler/db-proxy.ts b/runtimes/nodejs/src/handler/db-proxy.ts index a1391f8b5a..9a35446ba3 100644 --- a/runtimes/nodejs/src/handler/db-proxy.ts +++ b/runtimes/nodejs/src/handler/db-proxy.ts @@ -4,19 +4,20 @@ * @LastEditTime: 2022-02-03 00:39:18 * @Description: */ -import { Request, Response } from 'express' +import { Response } from 'express' import { Proxy } from 'database-proxy' import Config from '../config' import { DatabaseAgent } from '../db' import { logger } from '../support/logger' import { PolicyAgent } from '../support/policy' +import { IRequest } from '../support/types' -export async function handleDatabaseProxy(req: Request, res: Response) { +export async function handleDatabaseProxy(req: IRequest, res: Response) { const accessor = DatabaseAgent.accessor const requestId = req['requestId'] - const auth = req['auth'] ?? {} + const auth = req.user || {} const policy_name = req.params?.policy // get corresponding policy diff --git a/runtimes/nodejs/src/handler/debug-func.ts b/runtimes/nodejs/src/handler/debug-func.ts index 4ee1ed9522..8615abaaa4 100644 --- a/runtimes/nodejs/src/handler/debug-func.ts +++ b/runtimes/nodejs/src/handler/debug-func.ts @@ -5,15 +5,16 @@ * @Description: */ -import { Request, Response } from 'express' +import { Response } from 'express' import { FunctionContext } from '../support/function-engine' import { logger } from '../support/logger' import { CloudFunction } from '../support/function-engine' +import { IRequest } from '../support/types' /** * Handler of debugging cloud function */ -export async function handleDebugFunction(req: Request, res: Response) { +export async function handleDebugFunction(req: IRequest, res: Response) { const requestId = req['requestId'] const func_name = req.params?.name @@ -25,7 +26,7 @@ export async function handleDebugFunction(req: Request, res: Response) { } // verify the debug token - const auth = req['auth'] + const auth = req.user if (!auth || auth.type !== 'debug') { return res.status(403).send('permission denied: invalid debug token') } @@ -40,9 +41,11 @@ export async function handleDebugFunction(req: Request, res: Response) { body: param, headers: req.headers, method: req.method, - auth: req['auth'], + auth: req.user, + user: req.user, requestId, - response: res + response: res, + __function_name: func.name, } const result = await func.invoke(ctx) diff --git a/runtimes/nodejs/src/handler/invoke-func.ts b/runtimes/nodejs/src/handler/invoke-func.ts index f8f3161bb3..396fdf03db 100644 --- a/runtimes/nodejs/src/handler/invoke-func.ts +++ b/runtimes/nodejs/src/handler/invoke-func.ts @@ -5,39 +5,40 @@ * @Description: */ -import { Request, Response } from "express"; -import { FunctionContext } from "../support/function-engine"; -import { logger } from "../support/logger"; -import { CloudFunction } from "../support/function-engine"; +import { Response } from 'express' +import { FunctionContext } from '../support/function-engine' +import { logger } from '../support/logger' +import { CloudFunction } from '../support/function-engine' +import { IRequest } from '../support/types' -const DEFAULT_FUNCTION_NAME = "__default__"; +const DEFAULT_FUNCTION_NAME = '__default__' /** * Handler of invoking cloud function */ -export async function handleInvokeFunction(req: Request, res: Response) { - const requestId = req["requestId"]; - const func_name = req.params?.name; +export async function handleInvokeFunction(req: IRequest, res: Response) { + const requestId = req['requestId'] + const func_name = req.params?.name // load function data from db - let funcData = await CloudFunction.getFunctionByName(func_name); + let funcData = await CloudFunction.getFunctionByName(func_name) if (!funcData) { - if (func_name === "healthz") { - return res.status(200).send("ok"); + if (func_name === 'healthz') { + return res.status(200).send('ok') } // load default function from db - funcData = await CloudFunction.getFunctionByName(DEFAULT_FUNCTION_NAME); + funcData = await CloudFunction.getFunctionByName(DEFAULT_FUNCTION_NAME) if (!funcData) { - return res.status(404).send("Function Not Found"); + return res.status(404).send('Function Not Found') } } - const func = new CloudFunction(funcData); + const func = new CloudFunction(funcData) // reject while no HTTP enabled if (!func.methods.includes(req.method.toUpperCase())) { - return res.status(405).send("Method Not Allowed"); + return res.status(405).send('Method Not Allowed') } try { @@ -48,38 +49,40 @@ export async function handleInvokeFunction(req: Request, res: Response) { body: req.body, headers: req.headers, method: req.method, - auth: req["auth"], + auth: req['auth'], + user: req.user, requestId, request: req, response: res, - }; - const result = await func.invoke(ctx); + __function_name: func.name, + } + const result = await func.invoke(ctx) if (result.error) { logger.error( requestId, `invoke function ${func_name} invoke error: `, - result - ); + result, + ) return res.status(400).send({ error: - "invoke cloud function got error, please check the function logs", + 'invoke cloud function got error, please check the function logs', requestId, - }); + }) } logger.trace( requestId, `invoke function ${func_name} invoke success: `, - result - ); + result, + ) if (res.writableEnded === false) { - return res.send(result.data); + return res.send(result.data) } } catch (error) { - logger.error(requestId, "failed to invoke error", error); - return res.status(500).send("Internal Server Error"); + logger.error(requestId, 'failed to invoke error', error) + return res.status(500).send('Internal Server Error') } } diff --git a/runtimes/nodejs/src/handler/typings.ts b/runtimes/nodejs/src/handler/typings.ts index 05e4cea5cc..74ee38d56c 100644 --- a/runtimes/nodejs/src/handler/typings.ts +++ b/runtimes/nodejs/src/handler/typings.ts @@ -5,17 +5,18 @@ * @Description: */ -import { Request, Response } from 'express' +import { Response } from 'express' import { PackageDeclaration, NodePackageDeclarations } from 'node-modules-utils' import path = require('path') import { logger } from '../support/logger' +import { IRequest } from '../support/types' const nodeModulesRoot = path.resolve(__dirname, '../../node_modules') /** * Gets declaration files of a dependency package */ -export async function handlePackageTypings(req: Request, res: Response) { +export async function handlePackageTypings(req: IRequest, res: Response) { const requestId = req['requestId'] const packageName = req.query.packageName as string diff --git a/runtimes/nodejs/src/index.ts b/runtimes/nodejs/src/index.ts index 513dc1f2be..0b09c59360 100644 --- a/runtimes/nodejs/src/index.ts +++ b/runtimes/nodejs/src/index.ts @@ -52,7 +52,7 @@ process.on('uncaughtException', err => { app.use(function (req, res, next) { const token = splitBearerToken(req.headers['authorization'] ?? '') const auth = parseToken(token) || null - req['auth'] = auth + req['user'] = auth const requestId = req['requestId'] = req.headers['x-request-id'] || generateUUID() if (req.url !== '/healthz') { diff --git a/runtimes/nodejs/src/support/function-engine/console.ts b/runtimes/nodejs/src/support/function-engine/console.ts index ce6b76e489..71e009aafe 100644 --- a/runtimes/nodejs/src/support/function-engine/console.ts +++ b/runtimes/nodejs/src/support/function-engine/console.ts @@ -1,12 +1,13 @@ import * as util from 'util' +import { FunctionContext } from './types' export class FunctionConsole { - requestId: string = '' + ctx: FunctionContext - static write: (message: string, requestId: string) => void = console.log + static write: (message: string, ctx: FunctionContext) => void = console.log - constructor(requestId: string) { - this.requestId = requestId + constructor(ctx: FunctionContext) { + this.ctx = ctx } private _log(...params: any[]) { @@ -16,7 +17,7 @@ export class FunctionConsole { }) .join(' ') - FunctionConsole.write(content, this.requestId) + FunctionConsole.write(content, this.ctx) } debug(...params: any[]) { diff --git a/runtimes/nodejs/src/support/function-engine/engine.ts b/runtimes/nodejs/src/support/function-engine/engine.ts index 3dc4f7d4b3..577c1e3ab0 100644 --- a/runtimes/nodejs/src/support/function-engine/engine.ts +++ b/runtimes/nodejs/src/support/function-engine/engine.ts @@ -75,7 +75,7 @@ export class FunctionEngine { * @returns */ buildSandbox(functionContext: FunctionContext): RuntimeContext { - const fconsole = new FunctionConsole(functionContext.requestId) + const fconsole = new FunctionConsole(functionContext) const _module = { exports: {}, diff --git a/runtimes/nodejs/src/support/function-engine/trigger-scheduler.ts b/runtimes/nodejs/src/support/function-engine/trigger-scheduler.ts index 5d60258c30..915c3e973f 100644 --- a/runtimes/nodejs/src/support/function-engine/trigger-scheduler.ts +++ b/runtimes/nodejs/src/support/function-engine/trigger-scheduler.ts @@ -6,6 +6,7 @@ import { Trigger } from "./trigger" import assert = require('node:assert') import WebSocket = require('ws') import { IncomingMessage } from 'node:http' +import { generateUUID } from '../utils' export class TriggerScheduler { private _triggers: Trigger[] = [] @@ -68,7 +69,8 @@ export class TriggerScheduler { const param: FunctionContext = { params: data, method: 'trigger', - requestId: `trigger_${tri.id}` + requestId: generateUUID(), + __function_name: '' } this.executeFunction(tri.func_id, param) } @@ -104,7 +106,7 @@ export class TriggerScheduler { const param: any = { params: data, method: event, - requestId: `trigger_${tri.id}`, + requestId: generateUUID(), socket, headers: request?.headers } @@ -117,6 +119,7 @@ export class TriggerScheduler { */ protected async executeFunction(func_id: string, param: FunctionContext) { const func = await this.getFunctionById(func_id) + param.__function_name = func.name await func.invoke(param) } @@ -169,7 +172,8 @@ export class TriggerScheduler { const param: FunctionContext = { params: tri, method: 'trigger', - requestId: `trigger_${tri.id}` + requestId: generateUUID(), + __function_name: tri.func_id } // execute function this.executeFunction(tri.func_id, param) diff --git a/runtimes/nodejs/src/support/function-engine/types.ts b/runtimes/nodejs/src/support/function-engine/types.ts index fbb7f6c4cf..24cd5a6056 100644 --- a/runtimes/nodejs/src/support/function-engine/types.ts +++ b/runtimes/nodejs/src/support/function-engine/types.ts @@ -1,56 +1,58 @@ -import { FunctionConsole } from "./console"; -import { IncomingHttpHeaders } from "http"; -import { Request, Response } from "express"; +import { FunctionConsole } from './console' +import { IncomingHttpHeaders } from 'http' +import { Request, Response } from 'express' -export type RequireFuncType = (module: string) => any; +export type RequireFuncType = (module: string) => any /** * vm run context (global) */ export interface RuntimeContext { - __context__: FunctionContext; - module: { exports: Object }; - exports: Object; - console: FunctionConsole; - require: RequireFuncType; - Buffer: typeof Buffer; - setTimeout: typeof setTimeout; - clearTimeout: typeof clearTimeout; - setInterval: typeof setInterval; - clearInterval: typeof clearInterval; - setImmediate: typeof setImmediate; - clearImmediate: typeof clearImmediate; - __filename: string; + __context__: FunctionContext + module: { exports: Object } + exports: Object + console: FunctionConsole + require: RequireFuncType + Buffer: typeof Buffer + setTimeout: typeof setTimeout + clearTimeout: typeof clearTimeout + setInterval: typeof setInterval + clearInterval: typeof clearInterval + setImmediate: typeof setImmediate + clearImmediate: typeof clearImmediate + __filename: string process: { - env: { [key: string]: string }; - }; - global: RuntimeContext; + env: { [key: string]: string } + } + global: RuntimeContext } /** * ctx passed to function */ export interface FunctionContext { - files?: File[]; - headers?: IncomingHttpHeaders; - query?: any; - body?: any; - params?: any; - auth?: any; - requestId?: string; - method?: string; - request?: Request; - response?: Response; - __function_name?: string; + files?: File[] + headers?: IncomingHttpHeaders + query?: any + body?: any + params?: any + // @Deprecated use user instead + auth?: any + user?: any + requestId: string + method?: string + request?: Request + response?: Response + __function_name: string } /** * Result object returned by the running function */ export interface FunctionResult { - data?: any; - error?: Error; - time_usage: number; + data?: any + error?: Error + time_usage: number } export enum FunctionStatus { @@ -63,51 +65,51 @@ export enum FunctionStatus { * */ export type CloudFunctionSource = { - code: string; - compiled: string | null; - uri: string | null; - version: number; - hash: string | null; - lang: string | null; -}; + code: string + compiled: string | null + uri: string | null + version: number + hash: string | null + lang: string | null +} /** * cloud function data structure */ export interface ICloudFunctionData { - id: string; - appid: string; - name: string; - source: CloudFunctionSource; - desc: string; - tags: string[]; - websocket: boolean; - methods: string[]; - createdAt: Date; - updatedAt: Date; - createdBy: string; + id: string + appid: string + name: string + source: CloudFunctionSource + desc: string + tags: string[] + websocket: boolean + methods: string[] + createdAt: Date + updatedAt: Date + createdBy: string } /** Object containing file metadata and access information. */ interface File { /** Name of the form field associated with this file. */ - fieldname: string; + fieldname: string /** Name of the file on the uploader's computer. */ - originalname: string; + originalname: string /** * Value of the `Content-Transfer-Encoding` header for this file. * @deprecated since July 2015 * @see RFC 7578, Section 4.7 */ - encoding: string; + encoding: string /** Value of the `Content-Type` header for this file. */ - mimetype: string; + mimetype: string /** Size of the file in bytes. */ - size: number; + size: number /** `DiskStorage` only: Directory to which this file has been uploaded. */ - destination: string; + destination: string /** `DiskStorage` only: Name of this file within `destination`. */ - filename: string; + filename: string /** `DiskStorage` only: Full path to the uploaded file. */ - path: string; + path: string } diff --git a/runtimes/nodejs/src/support/function-log.ts b/runtimes/nodejs/src/support/function-log.ts index 47d90c0ac3..94aba369b1 100644 --- a/runtimes/nodejs/src/support/function-log.ts +++ b/runtimes/nodejs/src/support/function-log.ts @@ -7,15 +7,16 @@ import { Constants } from '../constants' import { DatabaseAgent } from '../db' -import { FunctionConsole } from './function-engine' +import { FunctionConsole, FunctionContext } from './function-engine' export interface IFunctionLog { request_id: string + func: string data: string created_at: Date } -FunctionConsole.write = async (message: string, request_id: string) => { +FunctionConsole.write = async (message: string, ctx: FunctionContext) => { const db = DatabaseAgent.db if (!db) return @@ -23,7 +24,8 @@ FunctionConsole.write = async (message: string, request_id: string) => { Constants.function_log_collection, ) const doc = { - request_id: request_id, + request_id: ctx.requestId, + func: ctx.__function_name, data: message, created_at: new Date(), } diff --git a/runtimes/nodejs/src/support/init.ts b/runtimes/nodejs/src/support/init.ts index 090a4a4d2a..412ada2fd2 100644 --- a/runtimes/nodejs/src/support/init.ts +++ b/runtimes/nodejs/src/support/init.ts @@ -116,9 +116,6 @@ export async function ensureCollectionIndexes(): Promise { key: { created_at: 1 }, expireAfterSeconds: Config.FUNCTION_LOG_EXPIRED_TIME, }, - // { - // key: { request_id: 1 } - // }, ]) return true diff --git a/runtimes/nodejs/src/support/policy.ts b/runtimes/nodejs/src/support/policy.ts index 9886a32eef..28a5e3f6e0 100644 --- a/runtimes/nodejs/src/support/policy.ts +++ b/runtimes/nodejs/src/support/policy.ts @@ -11,6 +11,7 @@ import { logger } from "./logger" import { CloudFunction } from "./function-engine" import { DatabaseAgent } from "../db" import { Constants } from "../constants" +import { generateUUID } from "./utils" export type InjectionGetter = (payload: any, params: Params) => Promise @@ -77,11 +78,13 @@ export class PolicyAgent { assert.ok(func_data, 'getFunctionByName(): function not found') const func = new CloudFunction(func_data) - const ret = await func.invoke({}) + const ret = await func.invoke({ + __function_name: func.name, + requestId: generateUUID() + }) assert(typeof ret.data === 'function', 'function type needed') return ret.data - } catch (error) { logger.error(`failed to get injector by cloud function: ${injectorName}, now using default injector`, error) return defaultInjectionGetter diff --git a/runtimes/nodejs/src/support/types.ts b/runtimes/nodejs/src/support/types.ts new file mode 100644 index 0000000000..0c18413dd4 --- /dev/null +++ b/runtimes/nodejs/src/support/types.ts @@ -0,0 +1,5 @@ +import { Request } from 'express' + +export interface IRequest extends Request { + user?: any +} diff --git a/server/src/constants.ts b/server/src/constants.ts index 0a69671da2..564788402a 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -101,6 +101,7 @@ export const HTTP_METHODS = ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'] export const CN_PUBLISHED_FUNCTIONS = '__functions__' export const CN_PUBLISHED_POLICIES = '__policies__' +export const CN_FUNCTION_LOGS = '__function_logs__' export const CPU_UNIT = 1000 export const MB = 1024 * 1024 diff --git a/server/src/database/collection.service.ts b/server/src/database/collection.service.ts index ba4a1d46ff..6b3efe7e7c 100644 --- a/server/src/database/collection.service.ts +++ b/server/src/database/collection.service.ts @@ -20,12 +20,12 @@ export class CollectionService { assert(db, 'Database not found') try { await db.createCollection(dto.name) - await client.close() return true } catch (error) { this.logger.error(error) - await client.close() return false + } finally { + await client.close() } } @@ -40,12 +40,12 @@ export class CollectionService { try { const collections = await db.listCollections().toArray() const result = collections.filter((coll) => !coll.name.startsWith('__')) - await client.close() return result } catch (error) { this.logger.error(error) - await client.close() return null + } finally { + await client.close() } } @@ -69,6 +69,7 @@ export class CollectionService { async update(appid: string, name: string, dto: UpdateCollectionDto) { const { client, db } = await this.databaseService.findAndConnect(appid) assert(db, 'Database not found') + const command = { collMod: name, validationAction: 'error', @@ -88,12 +89,12 @@ export class CollectionService { try { await db.command(command) - await client.close() return true } catch (error) { this.logger.error(error) - await client.close() return false + } finally { + await client.close() } } @@ -108,12 +109,12 @@ export class CollectionService { assert(db, 'Database not found') try { const res = await db.dropCollection(name) - await client.close() return res } catch (error) { this.logger.error(error) - await client.close() return false + } finally { + await client.close() } } } diff --git a/server/src/function/function.controller.ts b/server/src/function/function.controller.ts index aa1283f1f6..43435f24db 100644 --- a/server/src/function/function.controller.ts +++ b/server/src/function/function.controller.ts @@ -10,6 +10,7 @@ import { HttpException, HttpStatus, Req, + Query, } from '@nestjs/common' import { CreateFunctionDto } from './dto/create-function.dto' import { UpdateFunctionDto } from './dto/update-function.dto' @@ -163,4 +164,39 @@ export class FunctionController { const res = this.functionsService.compile(func) return res } + + /** + * Get function logs + * @param appid + * @param name + * @returns + */ + @ApiResponse({ type: ResponseUtil }) + @ApiOperation({ summary: 'Get function logs' }) + @UseGuards(JwtAuthGuard, ApplicationAuthGuard) + @Get('logs') + async getLogs( + @Param('appid') appid: string, + @Query('requestId') requestId?: string, + @Query('functionName') functionName?: string, + @Query('limit') limit?: number, + @Query('page') page?: number, + ) { + page = page || 1 + limit = limit || 10 + + const res = await this.functionsService.findLogs(appid, { + requestId, + functionName, + limit, + page, + }) + + return ResponseUtil.ok({ + list: res.data, + total: res.total, + page, + limit, + }) + } } diff --git a/server/src/function/function.service.ts b/server/src/function/function.service.ts index 1512e574d0..3f3fc814dd 100644 --- a/server/src/function/function.service.ts +++ b/server/src/function/function.service.ts @@ -1,7 +1,11 @@ -import { Injectable } from '@nestjs/common' +import { Injectable, Logger } from '@nestjs/common' import { CloudFunction, Prisma } from '@prisma/client' import { compileTs2js } from '../utils/lang' -import { APPLICATION_SECRET_KEY, CN_PUBLISHED_FUNCTIONS } from '../constants' +import { + APPLICATION_SECRET_KEY, + CN_FUNCTION_LOGS, + CN_PUBLISHED_FUNCTIONS, +} from '../constants' import { DatabaseCoreService } from '../core/database.cr.service' import { PrismaService } from '../prisma.service' import { CreateFunctionDto } from './dto/create-function.dto' @@ -11,6 +15,7 @@ import { JwtService } from '@nestjs/jwt' @Injectable() export class FunctionService { + private readonly logger = new Logger(FunctionService.name) constructor( private readonly db: DatabaseCoreService, private readonly prisma: PrismaService, @@ -82,20 +87,25 @@ export class FunctionService { async publish(func: CloudFunction) { const { db, client } = await this.db.findAndConnect(func.appid) const session = client.startSession() - await session.withTransaction(async () => { - const coll = db.collection(CN_PUBLISHED_FUNCTIONS) - await coll.deleteOne({ name: func.name }, { session }) - await coll.insertOne(func, { session }) - }) + try { + await session.withTransaction(async () => { + const coll = db.collection(CN_PUBLISHED_FUNCTIONS) + await coll.deleteOne({ name: func.name }, { session }) + await coll.insertOne(func, { session }) + }) + } finally { + await client.close() + } } async unpublish(appid: string, name: string) { const { db, client } = await this.db.findAndConnect(appid) - const session = client.startSession() - await session.withTransaction(async () => { + try { const coll = db.collection(CN_PUBLISHED_FUNCTIONS) - await coll.deleteOne({ name }, { session }) - }) + await coll.deleteOne({ name }) + } finally { + await client.close() + } } compile(func: CloudFunction) { @@ -124,4 +134,44 @@ export class FunctionService { ) return token } + + async findLogs( + appid: string, + params: { + page: number + limit: number + requestId?: string + functionName?: string + }, + ) { + const { page, limit, requestId, functionName } = params + const { db, client } = await this.db.findAndConnect(appid) + + try { + const coll = db.collection(CN_FUNCTION_LOGS) + const query: any = {} + if (requestId) { + query.requestId = requestId + } + if (functionName) { + query.func = functionName + } + + const data = await coll + .find(query, { + limit, + skip: (page - 1) * limit, + sort: { created_at: -1 }, + }) + .toArray() + + const total = await coll.countDocuments(query) + return { + data, + total, + } + } finally { + await client.close() + } + } }