开启链路追踪后,函数计算会自动记录请求在系统侧的耗时,包含冷启动耗时、Initializer函数的耗时和函数的执行时间等。如果您还需查看函数内业务侧的耗时,例如在函数内访问RDS、NAS等服务的耗时,则可通过本文提供的创建自定义Span步骤实现。

流程说明

您可以基于函数计算的链路创建自定义Span,将自定义的Span串联到函数计算的调用链中。函数计算的链路分析基于OpenTracing协议的Jaeger实现,提供以下两种方式创建自定义Span:

Jaeger和OpenTelemetry的详细信息,请分别参见JaegerOpenTelemetry

两种方式的流程说明如下:

  1. 在函数中引入Jaeger或OpenTelemetry依赖包。引入三方依赖的具体操作,请参见为函数安装第三方依赖
  2. 在函数中获取函数计算调用链的Span Context,根据函数计算的Span Context,创建自定义Span,在需要记录耗时的代码片段前后添加埋点。
  3. 完成后,您可以在函数详情页面的链路追踪页签或链路追踪控制台查看调用链信息。

使用Jaeger SDK

本文以Node.js运行时为例,介绍如何通过Funcraft创建自定义Span,创建并部署函数的步骤。

说明 您可以使用Funcraft工具安装依赖包,并打包部署到函数计算,您也可以通过其他方式打包并部署。Funcraft的更多信息,请参见 功能概览
  1. 创建代码目录。
    mkdir jaeger-demo
  2. 进入该目录。
    cd jaeger-demo
  3. 初始化Node.js运行时模板。
    1. 触发初始化脚本。
      fun init
      系统返回以下信息。
      ? Select a template to init (Use arrow keys or type to search)
      > event-nodejs12
        event-nodejs10
        event-nodejs8
        event-nodejs6
        event-python3
        event-python2.7
        event-java8
        event-java11
        event-php7.2
        event-dotnetcore2.1
        http-trigger-nodejs12
        http-trigger-nodejs10
        http-trigger-nodejs8
        http-trigger-nodejs6
        http-trigger-python3
        http-trigger-python2.7
      (Move up and down to reveal more choices)
    2. 按键盘下键,选中 event-nodejs8,然后按Enter键。
      ? Select a template to init (Use arrow keys or type to search)
        event-nodejs12
        event-nodejs10
      > event-nodejs8
        event-nodejs6
        event-python3
        event-python2.7
        event-java8
        event-java11
        event-php7.2
        event-dotnetcore2.1
        http-trigger-nodejs12
        http-trigger-nodejs10
        http-trigger-nodejs8
        http-trigger-nodejs6
        http-trigger-python3
        http-trigger-python2.7
      (Move up and down to reveal more choices)
  4. 安装Jaeger依赖。
    fun install --runtime nodejs8 --package-type npm --save jaeger-client
    返回信息示例如下。
    begin pulling image registry.cn-beijing.aliyuncs.com/aliyunfc/runtime-nodejs8:build-1.9.9, you can also use 'docker pull registry.cn-beijing.aliyuncs.com/aliyunfc/runtime-nodejs8:build-1.9.9' to pull image by yourself.
    build-1.9.9: Pulling from aliyunfc/runtime-nodejs8
    85b1f47fba49: Already exists
    ba6bd283713a: Already exists
    817c8cd48a09: Already exists
    47cc0ed96dc3: Already exists
    8888adcbd08b: Already exists
    6f2de60646b9: Already exists
    ae482e0e9b0a: Already exists
    7415dec84f57: Already exists
    ff5113c4bbac: Pull complete
    6291e34f3366: Pull complete
    20817eb8e89c: Pull complete
    e3678ffecff1: Pull complete
    91dda143d2a4: Pull complete
    75d1af1873c8: Pull complete
    c1482d5e884e: Pull complete
    4afe2b55e98b: Pull complete
    923e0823aa03: Pull complete
    87df1bdb59d9: Pull complete
    b3ce0f864d0a: Pull complete
    971ac0161e30: Pull complete
    35720a3848e8: Pull complete
    b6c6adc1307b: Pull complete
    af503f04d53a: Pull complete
    79bb0bf8b16a: Pull complete
    446a55ebb1a5: Pull complete
    95c19f9fcde1: Pull complete
    5d7f2f97533a: Pull complete
    Digest: sha256:9615a2500ceec7597f4ae5ee64705830bf4f2a881533042547ffa737afcc4ab5
    Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/aliyunfc/runtime-nodejs8:build-1.9.9
    Task => NpmTask
         => npm install -q --no-audit --production jaeger-client
    
    save package install commnad to /tmp/test/jaeger-demo/Funfile
  5. 编写函数代码。
    代码内容如下。
    var initTracer = require('jaeger-client').initTracer;
    var spanContext = require('jaeger-client').SpanContext;
    
    
    module.exports.handler = function(event, context, callback) 
    {
        console.log('invoking...',context.tracing);
    
        var config = {
            serviceName: 'e2e-test',
            reporter: {
                // Provide the traces endpoint; this forces the client to connect directly to the Collector and send
                // spans over HTTP
                collectorEndpoint: context.tracing.jaegerEndpoint,
                flushIntervalMs: 10,
            },
            sampler: {
                type: "const",
                param: 1
            },
        };
        var options = {
            tags: {
                'version': 'fc-e2e-tags',
            },
        };
        var tracer = initTracer(config, options);
        var invokSpanContextStr = context.tracing.openTracingSpanContext;
        console.log('spanContext', invokSpanContextStr);
        var invokSpanContext = spanContext.fromString(invokSpanContextStr);
        var span = tracer.startSpan("CustomSpan", {
            childOf: invokSpanContext
        });
        span.finish();
        var params = {
            openTracingSpanContext: context.tracing.openTracingSpanContext,
            openTracingSpanBaggages: context.tracing.openTracingSpanBaggages,
            jaegerEndpoint: context.tracing.jaegerEndpoint
        }
       callback(null,'success')
    }
  6. 更新template.yml
    ROSTemplateFormatVersion: '2015-09-01'
    Transform: 'Aliyun::Serverless-2018-04-03'
    Resources:
      jaeger-demo:
        Type: 'Aliyun::Serverless::Service'
        Properties:
          Description: 'Node.js jaeger demo'
          TracingConfig: Enable
        jaeger-demo:
          Type: 'Aliyun::Serverless::Function'
          Properties:
            Handler: index.handler
            Runtime: nodejs8
            CodeUri: './'
  7. 部署到函数计算。
    fun deploy -y
    返回信息示例如下。
    using template: template.yml
    using region: cn-hangzhou
    using accountId: ***********3743
    using accessKeyId: ***********mKCr
    using timeout: 10
    
    Collecting your services information, in order to caculate devlopment changes...
    
    Resources Changes(Beta version! Only FC resources changes will be displayed):
    
    ┌──────┬────────────────┬────┬──────┐
    │ Resource   │ ResourceType                   │ Action │ Property   │
    ├──────┼────────────────┼────┼──────┤
    │ jaeger-demo│ Aliyun::Serverless::Service    │ Add    │ Description│
    ├──────┼────────────────┼────┼──────┤
    │            │                                │        │ Handler    │
    │            │                                │        ├──────┤
    │ jaeger-demo│ Aliyun::Serverless::Function   │ Add    │ Runtime    │
    │            │                                │        ├──────┤
    │            │                                │        │ CodeUri    │
    └──────┴────────────────┴────┴──────┘
    
    Waiting for service jaeger-demo to be deployed...
      Waiting for function jaeger-demo to be deployed...
        Waiting for packaging function jaeger-demo code...
        The function jaeger-demo has been packaged. A total of 635 files were compressed and the final size was 797.62 KB
      function jaeger-demo deploy success
    service jaeger-demo deploy success
  8. 结果验证。
    在函数计算控制台查看调用链,可以看到您刚创建的自定义Span与函数计算的系统Span连接起来。 user-defined-span

使用OpenTelemetry

本文以Python运行时为例,介绍如何通过Funcraft创建自定义Span,创建并部署函数的步骤。

说明 您可以使用Funcraft工具安装依赖包,并打包部署到函数计算,您也可以通过其他方式打包并部署。Funcraft的更多信息,请参见 功能概览
  1. 创建代码目录。
    mkdir opentelemetry-demo
  2. 进入代码目录。
    cd opentelemey-demo
  3. 初始化Python运行时模板。
    1. 触发初始化脚本。
      fun init
      系统返回以下信息。
      ? Select a template to init (Use arrow keys or type to search)
      > event-nodejs12
        event-nodejs10
        event-nodejs8
        event-nodejs6
        event-python3
        event-python2.7
        event-java8
        event-java11
        event-php7.2
        event-dotnetcore2.1
        http-trigger-nodejs12
        http-trigger-nodejs10
        http-trigger-nodejs8
        http-trigger-nodejs6
        http-trigger-python3
        http-trigger-python2.7
      (Move up and down to reveal more choices)
    2. 按键盘下键,选中 event-python3.0,然后按Enter键。
      ? Select a template to init (Use arrow keys or type to search)
        event-nodejs12
        event-nodejs10
        event-nodejs8
        event-nodejs6
      > event-python3
        event-python2.7
        event-java8
        event-php7.2
        event-dotnetcore2.1
        http-trigger-nodejs12
        http-trigger-nodejs10
        http-trigger-nodejs8
        http-trigger-nodejs6
        http-trigger-python3
        http-trigger-python2.7
        http-trigger-java8
        (Move up and down to reveal more choices)
      说明 由于 步骤4中安装的OpenTelemetry依赖不支持Python 2.X,因此需选择Python 3.0的模板。
  4. 安装OpenTelemetry依赖。
    1. 安装opentelemetry-api。
      fun install --runtime python3 --package-type pip opentelemetry-api
      返回结果示例如下。
      skip pulling image aliyunfc/runtime-python3.6:build-1.9.9...
      Task => PipTask
           => PYTHONUSERBASE=/code/.fun/python pip install --user --upgrade opentelemetry-api
      
      
          ╭───────────────────╮
         │                                        │
         │   Update available 3.6.20 → 3.6.21    │
         │   Run npm i @alicloud/fun to update    │
         │                                        │
         ╰────────────────────╯
    2. 安装opentelemetry-sdk。
      fun install --runtime python3 --package-type pip opentelemetry-sdk
      返回结果示例如下。
      skip pulling image aliyunfc/runtime-python3.6:build-1.9.9...
      Task => PipTask
           => PYTHONUSERBASE=/code/.fun/python pip install --user --upgrade opentelemetry-sdk
    3. 安装opentelemetry-exporter-jaeger。
      fun install --runtime python3 --package-type pip opentelemetry-exporter-jaeger
      返回结果示例如下。
      skip pulling image aliyunfc/runtime-python3.6:build-1.9.9...
      Task => PipTask
           => PYTHONUSERBASE=/code/.fun/python pip install --user --upgrade opentelemetry-exporter-jaeger
  5. 编写函数代码。
    vim index.py
    代码内容如下。
    # -*- coding: utf-8 -*-
    import logging
    import time
    from opentelemetry import trace
    from opentelemetry.exporter import jaeger
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
    from opentelemetry.trace import set_span_in_context
    
    trace.set_tracer_provider(TracerProvider())
    tracer = trace.get_tracer(__name__)
    
    
    def handler(event, context):
        logger = logging.getLogger()
        logger.info("start to init tracer")
        init_tracer(context.tracing.jaeger_endpoint)
        jaeger_span_context = context.tracing.span_context
        fc_invocation_span = get_fc_span(jaeger_span_context)
        with fc_invocation_span as parent:
            print('Hello world!')
        context = set_span_in_context(parent)
        child = tracer.start_span("child", context=context)
        # print(child)
        time.sleep(0.1)
        child.end()
        return 'hello world'
    
    
    def init_tracer(endpoint):
        jaeger_exporter = jaeger.JaegerSpanExporter(
            service_name='opentelemetry-service',
            collector_endpoint=endpoint,
            transport_format='thrift'  # optional
        )
    
        # Create a BatchExportSpanProcessor and add the exporter to it
        span_processor = BatchExportSpanProcessor(jaeger_exporter)
    
        # add to the tracer
        trace.get_tracer_provider().add_span_processor(span_processor)
    
    
    def get_fc_span(jaeger_span_context):
        jaeger_span_context_arr = jaeger_span_context.split(":")
        tid = int(jaeger_span_context_arr[0], 16)
        sid = int(jaeger_span_context_arr[1], 16)
        fid = int(jaeger_span_context_arr[3], 16)
    
        span_context = trace.SpanContext(
            trace_id=tid,
            span_id=sid,
            is_remote=True,
            trace_flags=trace.TraceFlags(fid),
            trace_state=[],
        )
        return trace.DefaultSpan(span_context)
  6. 更新template.yml
    ROSTemplateFormatVersion: '2015-09-01'
    Transform: 'Aliyun::Serverless-2018-04-03'
    Resources:
      opentelemetry-demo:
        Type: 'Aliyun::Serverless::Service'
        Properties:
          Description: 'helloworld'
          TracingConfig: Enable
        opentelemetry-demo:
          Type: 'Aliyun::Serverless::Function'
          Properties:
            Handler: index.handler
            Runtime: python3
            CodeUri: './'
  7. 部署到函数计算。
    fun deploy -y
    返回结果示例如下。
    using template: template.yml
    using region: cn-hangzhou
    using accountId: ***********3743
    using accessKeyId: ***********mKCr
    using timeout: 10
    
    Collecting your services information, in order to caculate devlopment changes...
    
    Resources Changes(Beta version! Only FC resources changes will be displayed):
    
    ┌──────────┬───────────────┬────┬───────┐
    │ Resource           │ ResourceType                 │ Action │ Property     │
    ├──────────┼───────────────┼────┼───────┤
    │                    │                              │        │ Description  │
    │ opentelemetry-demo │ Aliyun::Serverless::Service  │ Add    ├───────┤
    │                    │                              │        │ TracingConfig│
    ├──────────┼───────────────┼────┼───────┤
    │                    │                              │        │ Handler      │
    │                    │                              │        ├───────┤
    │ opentelemetry-demo │ Aliyun::Serverless::Function │ Add    │ Runtime      │
    │                    │                              │        ├───────┤
    │                    │                              │        │ CodeUri      │
    └──────────┴───────────────┴────┴───────┘
    
    Waiting for service opentelemetry-demo to be deployed...
      Waiting for function opentelemetry-demo to be deployed...
        Waiting for packaging function opentelemetry-demo code...
        The function opentelemetry-demo has been packaged. A total of 529 files were compressed and the final size was 5.12 MB
      function opentelemetry-demo deploy success
    service opentelemetry-demo deploy success
  8. 结果验证。
    在函数计算控制台查看调用链,可以看到您刚创建的自定义Span与函数计算的系统Span连接起来。 user-defined-span-opentelemetry