# Serverless

> Instrument your serverless functions with OpenTelemetry JavaScript

---

This guide shows how to get started with tracing serverless functions using
OpenTelemetry instrumentation libraries.

> [!NOTE]
>
> The OpenTelemetry documentation assume that the compiled application is run as
> [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules).

## AWS Lambda

> [!NOTE]
>
> You can also automatically instrument your AWS Lambda functions by using the
> [community provided Lambda layers](/docs/platforms/faas/lambda-auto-instrument/).

The following show how to use Lambda wrappers with OpenTelemetry to instrument
AWS Lambda functions manually and send traces to a configured backend.

If you are interested in a plug and play user experience, see
[OpenTelemetry Lambda Layers](https://github.com/open-telemetry/opentelemetry-lambda).

### Dependencies

First, create an empty `package.json`:

```sh
npm init -y
```

Then install the required dependencies:

```sh
npm install \
  @opentelemetry/api \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/instrumentation \
  @opentelemetry/sdk-trace-base \
  @opentelemetry/sdk-trace-node
```

### AWS Lambda wrapper code

This file contains all the OpenTelemetry logic, which enables tracing. Save the
following code as `lambda-wrapper.js`.

```javascript
/* lambda-wrapper.js */

const api = require('@opentelemetry/api');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const {
  OTLPTraceExporter,
} = require('@opentelemetry/exporter-trace-otlp-http');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const {
  getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');

api.diag.setLogger(new api.DiagConsoleLogger(), api.DiagLogLevel.ALL);

const spanProcessor = new BatchSpanProcessor(
  new OTLPTraceExporter({
    url: '<backend_url>',
  }),
);

const provider = new NodeTracerProvider({
  spanProcessors: [spanProcessor],
});

provider.register();

registerInstrumentations({
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-aws-lambda': {
        disableAwsContextPropagation: true,
      },
    }),
  ],
});
```

Replace `<backend_url>` with the URL of your favorite backend to export all
traces to it. If you don't have one setup already, you can check out
[Jaeger](https://www.jaegertracing.io/) or [Zipkin](https://zipkin.io/).

Note that `disableAwsContextPropagation` is set to true. The reason for this is
that the Lambda instrumentation tries to use the X-Ray context headers by
default, unless active tracing is enabled for this function, this results in a
non-sampled context, which creates a `NonRecordingSpan`.

More details can be found in the instrumentation
[documentation](https://www.npmjs.com/package/@opentelemetry/instrumentation-aws-lambda).

### AWS Lambda function handler

Now that you have a Lambda wrapper, create a simple handler that serves as a
Lambda function. Save the following code as `handler.js`.

```javascript
/* handler.js */

'use strict';

const https = require('https');

function getRequest() {
  const url = 'https://opentelemetry.io/';

  return new Promise((resolve, reject) => {
    const req = https.get(url, (res) => {
      resolve(res.statusCode);
    });

    req.on('error', (err) => {
      reject(new Error(err));
    });
  });
}

exports.handler = async (event) => {
  try {
    const result = await getRequest();
    return {
      statusCode: result,
    };
  } catch (error) {
    return {
      statusCode: 400,
      body: error.message,
    };
  }
};
```

### Deployment

There are multiple ways of deploying your Lambda function:

- [AWS Console](https://aws.amazon.com/console/)
- [AWS CLI](https://aws.amazon.com/cli/)
- [Serverless Framework](https://github.com/serverless/serverless)
- [Terraform](https://github.com/hashicorp/terraform)

Here we will be using Serverless Framework, more details can be found in the
[Setting Up Serverless Framework guide](https://www.serverless.com/framework/docs/getting-started).

Create a file called `serverless.yml`:

```yaml
service: lambda-otel-native
frameworkVersion: '3'
provider:
  name: aws
  runtime: nodejs14.x
  region: '<your-region>'
  environment:
    NODE_OPTIONS: --require lambda-wrapper
functions:
  lambda-otel-test:
    handler: handler.hello
```

For OpenTelemetry to work properly, `lambda-wrapper.js` must be included before
any other file: the `NODE_OPTIONS` setting ensures this.

Note if you are not using Serverless Framework to deploy your Lambda function,
you must manually add this environment variable using the AWS Console UI.

Finally, run the following command to deploy the project to AWS:

```shell
serverless deploy
```

You can now invoke the newly deployed Lambda function by using the AWS Console
UI. You should expect to see spans related to the invocation of the Lambda
function.

### Visiting the backend

You should now be able to view traces produced by OpenTelemetry from your Lambda
function in the backend!

## GCP function

The following shows how to instrument
[HTTP triggered function](https://docs.cloud.google.com/run/docs/write-functions)
using the Google Cloud Platform (GCP) UI.

### Creating function

Login to GCP and create or select a project where your function should be
placed. In the side menu go to _Serverless_ and select _Cloud Functions_. Next,
click on _Create Function_, and select
[2nd generation](https://cloud.google.com/blog/products/serverless/cloud-functions-2nd-generation-now-generally-available)
for your environment, provide a function name and select your region.

### Setup environment variable for otelwrapper

If closed, open the _Runtime, build, connections and security settings_ menu and
scroll down and add the environment variable `NODE_OPTIONS` with the following
value:

```shell
--require ./otelwrapper.js
```

### Select runtime

On the next screen (_Code_), select Node.js version 16 for your runtime.

### Create OTel wrapper

Create a new file called `otelwrapper.js`, that will be used to instrument your
service. Please make sure that you provide a `SERVICE_NAME` and that you set the
`<address for your backend>`.

```javascript
/* otelwrapper.js */

const { resourceFromAttributes } = require('@opentelemetry/resources');
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
const api = require('@opentelemetry/api');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const {
  OTLPTraceExporter,
} = require('@opentelemetry/exporter-trace-otlp-http');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const {
  getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');

api.diag.setLogger(new api.DiagConsoleLogger(), api.DiagLogLevel.ALL);

const collectorOptions = {
  url: '<address for your backend>',
};

const provider = new NodeTracerProvider({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: '<your function name>',
  }),
  spanProcessors: [
    new BatchSpanProcessor(new OTLPTraceExporter(collectorOptions)),
  ],
});

provider.register();

registerInstrumentations({
  instrumentations: [getNodeAutoInstrumentations()],
});
```

### Add package dependencies

Add the following to your `package.json`:

```json
{
  "dependencies": {
    "@google-cloud/functions-framework": "^3.0.0",
    "@opentelemetry/api": "^1.9.0",
    "@opentelemetry/auto-instrumentations-node": "^0.56.1",
    "@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
    "@opentelemetry/instrumentation": "^0.200.0",
    "@opentelemetry/sdk-trace-base": "^2.0.0",
    "@opentelemetry/sdk-trace-node": "^2.0.0",
    "@opentelemetry/resources": "^2.0.0",
    "@opentelemetry/semantic-conventions": "^2.0.0"
  }
}
```

### Add HTTP call to function

The following code makes a call to the OpenTelemetry web site to demonstrate an
outbound call.

```javascript
/* index.js */
const functions = require('@google-cloud/functions-framework');
const https = require('https');

functions.http('helloHttp', (req, res) => {
  let url = 'https://opentelemetry.io/';
  https
    .get(url, (response) => {
      res.send(`Response ${response.body}!`);
    })
    .on('error', (e) => {
      res.send(`Error ${e}!`);
    });
});
```

### Backend

If you run OTel collector in GCP VM you are likely to need to
[create VPC access connector](https://cloud.google.com/vpc/docs/configure-serverless-vpc-access)
to be able to send traces.

### Deploy

Select Deploy in UI and await deployment to be ready.

### Testing

You can test the function using cloud shell from test tab.
