函数计算目前支持 Node.js 6.10(runtime = nodejs6)、Node.js 8.9.0(runtime = nodejs8)、 Node.js 10.15.3(runtime = nodejs10)和 Node.js 12.16.1(runtime = nodejs12)运行环境。本文介绍了 Node.js 运行环境的日志、函数、错误、模块使用、外部命令调用等内容。

打印日志

您的函数通过 console.log 打印的内容会被收集到创建服务时指定的 Logstore 中。函数日志的更多信息,请参见函数日志

执行以下命令获取日志。

exports.handler = function (event, context, callback) {
    console.info(null, 'hello world');
    callback(null, 'hello world');
};            

输出的日志内容如下。

message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [INFO] hello world           
您还可以通过接口 setLogLevel 来指定打印日志的级别,其中日志级别从高到低如下所示。
日志 对应接口
error console.error
warn console.warn
info console.info
verbose console.log
debug console.debug

执行以下代码,打印 WARN、ERROR 级别的日志。

'use strict';
exports.handler = function(evt, ctx, cb) {
    console.setLogLevel("error");
    console.error("console error 1");
    console.info("console info 1");
    console.warn("console warn 1");
    console.log("console log 1");

    console.setLogLevel("warn");
    console.error("console error 2");
    console.info("console info 2");
    console.warn("console warn 2");
    console.log("console log 2");

    console.setLogLevel("info");
    cb(null, evt);
};
			

输出的日志内容如下。

message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [ERROR] console error 1
message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [ERROR] console error 2
message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [WARN] console warn 2
			

函数返回

Node.js 采用异步编程的模型,您的函数需要使用 callback 入参返回信息。callback 的语法如下所示。

callback(Error error, Object result);           
其中:
  • error:可选参数,在函数执行内部失败时使用此参数返回错误内容,成功情况下设置为 null。
  • result:可选参数,使用此参数返回函数成功的执行结果。result 可以是任意类型,函数计算会将其序列化成字节流,放到响应体中返回给调用方。
说明 根据调用函数时的调用类型不同,返回值会有不同的处理方式。同步调用的返回值将会序列化字节流返回给调用方,异步调用的返回值将会被抛弃,需要您将重要信息记录到日志中。
  • callback 被调用后则函数结束

    callback 被调用后函数就会停止运行,重复调用 callback 只接受第一次调用的结果。需要确保所有任务在 callback 调用之前完成,否则有些任务可能不会被运行。例如调用下面的函数,将返回 hello world ,并且 message 不会被打印。

    exports.handler = function(event, context, callback) {
      callback(null, 'hello world');
      callback(null, 'done');
      setTimeout(function() {
        console.log('message');
      }, 1000);
    };          
  • callback 未被调用则函数超时

    如果在函数中没有调用 callback,则系统将认为函数没有结束,会等待函数结果直到超时。例如调用下面的函数时将收到超时错误。

    exports.handler = function(event, context, callback) {
      console.log('hello world');
    };            

    调用结果如下。

    {"errorMessage":"Function timed out after 3 seconds"}           

错误处理

对于 Node.js 运行环境的函数,用户可能收到以下两种错误,错误类型记录在返回的 HTTP Header 字段中的 X-Fc-Error-Type。

  • HandledInvocationError:通过 callback 的第一个参数返回的错误。

    示例:

    执行以下命令调用 callback 命令。

    exports.handler = function(event, context, callback) {
      callback(new Error('oops'));
    };

    收到的响应如下所示。

    {
      "errorMessage": "oops",
      "errorType": "Error",
      "stackTrace": [
        "at exports.handler (/code/index.js:2:12)"
      ]
    }
  • UnhandledInvocationError:其他错误,包括未捕获异常、超时错误和 OOM(Out of memory)错误等。

    当用户逻辑未捕获到错误时,函数计算系统会尽可能捕获错误,并返回具体的信息。当遇到系统无法捕获的错误时,例如用户函数在运行过程中突然崩溃退出,系统会返回一个通用的错误信息。

    exports.handler = function(event, context, callback) {
      throw new Error('oops');
    };

    收到的响应如下所示。

    {"errorMessage":"Process exited unexpectedly before completing request"}

更多错误类型相关信息,请参见错误处理

使用内置模块

除了 Node.js 的标准模块,函数计算的 Node.js 运行环境中还包含了一些常用模块,您可以直接引用这些常用模块,目前函数计算包含的常见模块如下所示。

模块名称 版本 模块介绍 相关链接
co 4.6.0 控制流 https://github.com/tj/co
gm 1.23.0 图片处理库 https://github.com/aheckmann/gm
ali-oss 4.10.1 OSS SDK https://github.com/ali-sdk/ali-oss
ali-mns 2.6.5 MNS SDK https://github.com/InCar/ali-mns
tablestore 4.2.0 OTS SDK https://github.com/aliyun/aliyun-tablestore-nodejs-sdk
aliyun-sdk 1.10.12 Aliyun SDK https://github.com/aliyun-UED/aliyun-sdk-js
@alicloud/fc2 2.1.0 函数计算SDK https://github.com/aliyun/fc-nodejs-sdk
opencv 6.0.0 视觉算法库 https://github.com/peterbraden/node-opencv
body 5.1.0 http body 解析库 https://github.com/Raynos/body
raw-body 2.3.2 http body 解析库 https://github.com/stream-utils/raw-body

访问 OSS 的示例代码如下所示。

var gm = require('gm').subClass({ imageMagick: true });
exports.handler = function (event, context, callback) {
    gm(event)
        .flip()
        .toBuffer('PNG', function (err, buffer) {
            if (err) return callback(err);
            callback(null, buffer); 
       });
};
说明 上面的函数直接使用 event 作为图片的二进制数据,并且直接把生成的图片作为二进制数据返回。

使用自定义模块

如果您需要使用自定义模块,则需要将您的自定义模块与代码一起打包上传。您可以通过以下方式进行依赖管理。

  • 方法一:使用 npm 包管理器进行依赖管理。

    本文以安装 Mysql 数据库为例进行详细介绍。

    1. 执行以下命令建立一个目录,用于存放代码和依赖模块。
      mkdir /tmp/code
    2. 执行以下命令在 /tmp/code 目录下安装依赖。
      cd /tmp/code
      npm install mysql
    3. 新建代码文件,例如 /tmp/code/index.js,在代码中使用 mysql。
      var mysql = require('mysql');
      var connection;
      // exports.initializer: initializer function
      exports.initializer = function (context, callback) {
        connection = mysql.createConnection({
          host: 'localhost',
          user: '***',
          password: '*******',
          database: 'my_db'
        });
        connection.connect();
      }
      
      exports.handler = function (event, context, callback) {
        connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
          if (error) return callback(error);
          console.log('The solution is: ', results[0].solution);
          callback(null, results[0].solution);
        });
        connection.end();
      };
    4. 打包上传。

      打包时,需要针对文件进行打包,而不是针对代码整体目录进行打包。打包完成后,入口函数文件需要位于包内的根目录。

      • 在 Windows 下打包时,可以进入函数代码目录,全选所有文件以后,单击鼠标右键,选择压缩为 zip 包,生成代码包。
      • 在 Linux 下打包时,通过调用 zip 命令时,将源文件指定为代码目录下的所有文件,实现生成部署代码包,例如 zip code.zip /home/code/*

      打包后,在函数计算控制台指定函数中的代码执行页面,您可以选择 OSS 上传或者代码包上传方式上传代码包。

  • 方法二:使用 fun install 安装依赖。

    如果您使用 Funcraft 部署应用,可以使用 fun install 命令来安装依赖,详情请参见 使用 fun install 安装第三方依赖。本文以安装 mysql 库为例进行详细介绍。

    1. 执行以下命令在函数根目录下初始化 Funfile 文件。
      $ fun install init
      ? Select a runtime
        nodejs6 
      ❯ nodejs8
        nodejs10
        python2.7
        python3
        java8
        php7.2
       (Move up and down to reveal more choices)
    2. 选择语言 nodejs8,在当前目录生成一个名为 Funfile 的文件,编辑文件内容。
      RUNTIME nodejs8
      RUN npm install mysql
    3. 新建 template.yml 文件,详情请参见 template.yml。例如 /tmp/code/template.yml,示例如下。
      ROSTemplateFormatVersion: '2015-09-01'
      Transform: 'Aliyun::Serverless-2018-04-03'
      Resources:
        FunDemo: 
          Type: 'Aliyun::Serverless::Service'
          nodejsdemo: 
            Type: 'Aliyun::Serverless::Function'
            Properties:
              Handler: index.handler
              Runtime: nodejs8
              Initializer: index.initializer
              CodeUri: './'           

      该 template.yml 文件含义如下:声明名为 FunDemo 的服务,在这个服务下,声明一个名为 nodejsdemo 的函数,配置函数入口为 index.handler,Initializer 为 index.initializer 函数的 runtime 为 nodejs8。并指定 CodeUri 为当前目录,详情请参见CodeUri。在部署时,Funcraft 会将 CodeUri 指定的目录打包上传。

      更多的配置规则,请参见 Serverless Application Model

    4. 在函数根目录下执行 fun install 命令安装依赖。
      $ fun install
      using template: template.yml
      start installing function dependencies without docker
      
      building FunDemo/nodejsdemo
      Funfile exist, Fun will use container to build forcely
      Step 1/2 : FROM registry.cn-beijing.aliyuncs.com/aliyunfc/runtime-nodejs8:build-1.9.4
      ---> 8c6c75614064
      Step 2/2 : RUN npm install mysql
      ---> Using cache
      ---> 715f4427faf9
      sha256:715f4427faf9b66238328e44736078f2ac4bbc0d403e441a6c460b6cc2f405ae
      Successfully built 715f4427faf9
      Successfully tagged fun-cache-58fa61db-dd07-416b-aa47-a6ee752ec6d7:latest
      copying function artifact to /Users/txd123/Desktop/express
      
      Install Success
      
      
      Tips for next step
      ======================
      * Invoke Event Function: fun local invoke
      * Invoke Http Function: fun local start
      * Build Http Function: fun build
      * Deploy Resources: fun deploy
      							
    5. 使用 Funcraft 工具部署。
      fun deploy           

      执行成功时,会看到相关日志。

      using region: cn-hangzhou
      using accountId: ***********3557
      using accessKeyId: ***********r3Ra
      using timeout: 300
      
      Waiting for service FunDemo to be deployed...
          Waiting for function nodejsdemo to be deployed...
              Waiting for packaging function nodejs code...
              package function nodejs code done
          function nodejsdemo deploy success
      service FunDemo deploy success
      							

    登录函数计算控制台,即可看到相关的服务、函数被创建成功,且触发执行可以返回正确的结果。

调用外部命令

您的函数可能会用到一些工具,而这些工具并不是用 Node.js 写的。例如,Shell 、C++ 或者 Go 编译出来的可执行文件。您仍然可以将这些工具与代码一起打包,然后在函数中通过运行外部命令的方法来使用它们。以下代码演示了如何运行一个 Shell 脚本。

var exec = require('child_process');

exports.handler = function(event, context, callback) {
  var scriptPath = process.env['FC_FUNC_CODE_PATH'] + '/script.sh';
  exec.exec('bash '+scriptPath, {}, function(err, stdout, stderr) {
    if (err) return callback(err);
    console.log(stdout, stderr);
    callback(null, stdout);
  });
};
			
说明 使用 C 、 C++ 、Go 编译出来的可执行文件,需要与函数计算的运行环境兼容。函数计算的 Node.js 运行环境如下所示。
  • Linux 内核版本:Linux 4.4.24-2.al7.x86_64。
  • Docker 基础镜像:docker pull node:6.10。