# Instrumentação Lambda com Serverless Framework e Elven Plugin

***

### Sumário

* Visão geral
* Pré-requisitos
* Instalação
* Quick Start
* Configuração do plugin
* Referência de opções
* Controle por função
* Variáveis de ambiente injetadas
* Layers aplicadas
* Exemplos completos
* Boas práticas
* Troubleshooting
* FAQ

***

### Visão geral

O **`elven-instrumentation-serverless-plugin`** automatiza toda a instrumentação das suas funções Lambda. Com uma única configuração no `serverless.yml`, o plugin:

1. **Adiciona as layers** de OpenTelemetry e Elven Log Extension em cada função
2. **Injeta todas as variáveis de ambiente** necessárias para traces e logs
3. **Detecta a arquitetura** (x86\_64/arm64) e aplica as layers corretas
4. **Gera nomes de serviço** padronizados automaticamente (`{service}_{stage}_{function}`)

```
┌──────────────────────────────────────────────────────────────┐
│                   serverless.yml                             │
│                                                              │
│  plugins:                                                    │
│    - elven-instrumentation-serverless-plugin                 │
│                                                              │
│  custom:                                                     │
│    elvenLayerPlugin:                                         │
│      tenant: "meu-tenant"        ──────┐                     │
│      token: "meu-token"                │                     │
│                                        ▼                     │
│                              ┌─────────────────┐             │
│                              │  Plugin injeta:  │             │
│                              │  - 2 layers      │             │
│                              │  - 14+ env vars  │             │
│                              └────────┬────────┘             │
│                                       │                      │
│  functions:                           ▼                      │
│    api:      ← layers + envs injetados automaticamente       │
│    worker:   ← layers + envs injetados automaticamente       │
│    cron:     ← layers + envs injetados automaticamente       │
└──────────────────────────────────────────────────────────────┘
```

| Componente                    | O que faz                                                           |
| ----------------------------- | ------------------------------------------------------------------- |
| **Plugin Serverless**         | Injeta layers e variáveis de ambiente automaticamente no deploy     |
| **Layer OpenTelemetry**       | Instrumenta o runtime Node.js — gera traces e spans automaticamente |
| **Layer Elven Log Extension** | Captura logs da função via Lambda Logs API e envia para Loki        |

***

### Pré-requisitos

* **Serverless Framework** v3 ou v4 instalado
* **Node.js 18+**
* **Coletor OpenTelemetry (OTLP)** implantado na sua infraestrutura (a Elven realiza o deploy do collector no seu ambiente — não é um endpoint compartilhado)
* Credenciais da Elven Observability:
  * **Tenant ID**
  * **API Token**
  * **Endpoint do seu Collector OTLP**

***

### Instalação

```bash
npm install --save-dev elven-instrumentation-serverless-plugin
```

Adicione o plugin no `serverless.yml`:

```yaml
plugins:
  - elven-instrumentation-serverless-plugin
```

Verificar instalação:

```bash
npx serverless print 2>&1 | grep -i elven
```

***

### Quick Start

#### 1. Configure o plugin no `serverless.yml`

```yaml
service: minha-api

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1

plugins:
  - elven-instrumentation-serverless-plugin

custom:
  elvenLayerPlugin:
    tenant: "seu-tenant-id"
    token: "seu-api-token"

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
```

#### 2. Deploy normalmente

```bash
npx serverless deploy
```

O plugin automaticamente:

* Adiciona as layers de OTel e Log Extension na função
* Injeta as variáveis de ambiente com nome de serviço `minha-api_dev_hello`
* Configura o endpoint OTLP e Loki da Elven

**Pronto!** Ao invocar a função, traces e logs já aparecem na Elven Observability.

#### Output do plugin no deploy

```
✅ [Elven] Instrumented function: minha-api_dev_hello (x86_64)
🏗️  [Elven] Architecture: x86_64
📍 [Elven] Region: us-east-1
📦 [Elven] Default layers applied:
      - arn:aws:lambda:us-east-1:184161586896:layer:opentelemetry-nodejs-0_20_0:1
      - arn:aws:lambda:us-east-1:911167927290:layer:elven-lambda-log-extension:8
```

***

### Configuração do plugin

Todas as opções ficam dentro de `custom.elvenLayerPlugin` no `serverless.yml`:

```yaml
custom:
  elvenLayerPlugin:
    # === Obrigatórios ===
    tenant: "seu-tenant-id"
    token: "seu-api-token"

    # === Opcionais ===
    region: "us-east-1"                                    # default: us-east-1
    collector_endpoint: "https://otel-collector.minha-infra.com:4318"  # URL do seu collector
    logs_endpoint: "https://loki.elvenobservability.com"   # default: endpoint Elven
    architecture: "x86_64"                                 # default: x86_64
    log_version: "8"                                       # versão da layer de logs (x86_64)
    log_version_arm64: "7"                                 # versão da layer de logs (arm64)
    enabled_instrumentations: "http,express,pg"            # default: todas
    disabled_instrumentations: ""                          # default: nenhuma
    layers:                                                # override completo das layers
      - "arn:aws:lambda:us-east-1:..."
      - "arn:aws:lambda:us-east-1:..."
```

***

### Referência de opções

#### Opções obrigatórias

| Opção    | Descrição                        | Exemplo         |
| -------- | -------------------------------- | --------------- |
| `tenant` | Tenant ID na Elven Observability | `"meu-tenant"`  |
| `token`  | API Token de autenticação        | `"eyJhbGci..."` |

#### Opções opcionais

| Opção                       | Default                               | Descrição                                                                   |
| --------------------------- | ------------------------------------- | --------------------------------------------------------------------------- |
| `region`                    | `us-east-1`                           | Região AWS das layers                                                       |
| `collector_endpoint`        | — (**obrigatório**)                   | URL do seu coletor OTLP (ex: `https://otel-collector.minha-infra.com:4318`) |
| `logs_endpoint`             | `https://loki.elvenobservability.com` | URL **base** do Loki (sem `/loki/api/v1/push`)                              |
| `architecture`              | `x86_64`                              | Arquitetura padrão: `x86_64` ou `arm64`                                     |
| `log_version`               | `8`                                   | Versão da layer de logs para x86\_64                                        |
| `log_version_arm64`         | `7`                                   | Versão da layer de logs para arm64                                          |
| `enabled_instrumentations`  | todas                                 | Instrumentações OTel habilitadas (separadas por vírgula)                    |
| `disabled_instrumentations` | nenhuma                               | Instrumentações OTel a desabilitar                                          |
| `layers`                    | auto                                  | Override completo da lista de layers (substitui as layers padrão)           |

#### Ordem de resolução da arquitetura

A arquitetura é resolvida nesta ordem de precedência:

1. `custom.elvenLayerPlugin.architecture`
2. Variável de ambiente `LAMBDA_ARCHITECTURE`
3. `provider.architecture` do Serverless
4. Default: `x86_64`

Para funções individuais, `function.architecture` tem prioridade sobre o valor global.

***

### Controle por função

#### Desabilitar instrumentação em uma função específica

Adicione `disableElven: true` na função:

```yaml
functions:
  api:
    handler: handler.api
    # instrumentada normalmente

  internal:
    handler: handler.internal
    disableElven: true
    # NÃO será instrumentada

  worker:
    handler: handler.worker
    # instrumentada normalmente
```

Output no deploy:

```
✅ [Elven] Instrumented function: minha-api_dev_api (x86_64)
⚠️  [Elven] Skipping instrumentation for function: internal
✅ [Elven] Instrumented function: minha-api_dev_worker (x86_64)
```

#### Sobrescrever variáveis por função

O plugin **não sobrescreve** variáveis de ambiente já definidas na função. Isso permite customizar por função:

```yaml
functions:
  api:
    handler: handler.api
    # Usa configuração padrão do plugin

  heavy-worker:
    handler: handler.worker
    environment:
      OTEL_TRACES_SAMPLER: "traceidratio"
      OTEL_TRACES_SAMPLER_ARG: "0.1"
      # Sampling de 10% — demais variáveis são injetadas pelo plugin
```

#### Funções com arquiteturas diferentes

O plugin detecta a arquitetura por função e aplica as layers corretas:

```yaml
provider:
  architecture: x86_64  # padrão para todas

functions:
  api:
    handler: handler.api
    # usa x86_64 (do provider)

  ml-inference:
    handler: handler.inference
    architecture: arm64
    # usa arm64 — layers arm64 aplicadas automaticamente
```

***

### Variáveis de ambiente injetadas

O plugin injeta as seguintes variáveis em cada função:

#### OpenTelemetry (Traces)

| Variável                              | Valor                              | Descrição                       |
| ------------------------------------- | ---------------------------------- | ------------------------------- |
| `AWS_LAMBDA_EXEC_WRAPPER`             | `/opt/otel-handler`                | Ativa o wrapper OTel no runtime |
| `OTEL_SERVICE_NAME`                   | `{service}_{stage}_{function}`     | Nome do serviço nos traces      |
| `OTEL_TRACES_SAMPLER`                 | `always_on`                        | Estratégia de sampling          |
| `OTEL_LAMBDA_TRACE_MODE`              | `capture`                          | Modo de captura de traces       |
| `OTEL_PROPAGATORS`                    | `tracecontext,baggage,xray`        | Propagadores de contexto        |
| `OTEL_RESOURCE_ATTRIBUTES`            | `service.name=...,environment=...` | Atributos de recurso            |
| `OTEL_EXPORTER_OTLP_ENDPOINT`         | `collector_endpoint`               | Endpoint OTLP                   |
| `OTEL_NODE_ENABLED_INSTRUMENTATIONS`  | lista de instrumentações           | Instrumentações habilitadas     |
| `OTEL_NODE_DISABLED_INSTRUMENTATIONS` | *(condicional)*                    | Instrumentações desabilitadas   |

#### Elven Log Extension (Logs)

| Variável          | Valor           | Descrição              |
| ----------------- | --------------- | ---------------------- |
| `LOKI_URL`        | `logs_endpoint` | URL **base** do Loki   |
| `LOKI_TENANT_ID`  | `tenant`        | Tenant ID para o Loki  |
| `LOKI_AUTH_TOKEN` | `token`         | Token de autenticação  |
| `DEBUG`           | `false`         | Debug da log extension |

> **Nota sobre `LOKI_URL`:** O plugin envia apenas a URL base (ex: `https://loki.elvenobservability.com`). A log extension appenda `/loki/api/v1/push` automaticamente.

#### Naming convention automática

O `OTEL_SERVICE_NAME` segue o padrão:

```
{serviceName}_{stage}_{functionName}
```

Exemplo: serviço `ecommerce`, stage `prod`, função `checkout` → `ecommerce_prod_checkout`

***

### Layers aplicadas

O plugin adiciona automaticamente duas layers:

#### x86\_64

| Layer                 | ARN                                                                               |
| --------------------- | --------------------------------------------------------------------------------- |
| OpenTelemetry Node.js | `arn:aws:lambda:{region}:184161586896:layer:opentelemetry-nodejs-0_20_0:1`        |
| Elven Log Extension   | `arn:aws:lambda:{region}:911167927290:layer:elven-lambda-log-extension:{version}` |

#### arm64

| Layer                 | ARN                                                                                     |
| --------------------- | --------------------------------------------------------------------------------------- |
| OpenTelemetry Node.js | `arn:aws:lambda:{region}:184161586896:layer:opentelemetry-nodejs-0_20_0:1`              |
| Elven Log Extension   | `arn:aws:lambda:{region}:911167927290:layer:elven-lambda-log-extension-arm64:{version}` |

#### Override de layers

Para usar layers customizadas (ex: versão diferente ou layer própria):

```yaml
custom:
  elvenLayerPlugin:
    tenant: "meu-tenant"
    token: "meu-token"
    layers:
      - "arn:aws:lambda:us-east-1:184161586896:layer:opentelemetry-nodejs-0_20_0:1"
      - "arn:aws:lambda:us-east-1:911167927290:layer:elven-lambda-log-extension:8"
```

> Ao definir `layers`, a seleção automática por arquitetura é desabilitada. Certifique-se de usar as layers corretas para a arquitetura das suas funções.

***

### Exemplos completos

#### Exemplo 1 — API REST simples

```yaml
service: users-api

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  stage: ${opt:stage, 'dev'}

plugins:
  - elven-instrumentation-serverless-plugin

custom:
  elvenLayerPlugin:
    tenant: "meu-tenant"
    token: "meu-api-token"

functions:
  getUsers:
    handler: src/handlers/users.list
    events:
      - http:
          path: /users
          method: get

  getUser:
    handler: src/handlers/users.get
    events:
      - http:
          path: /users/{id}
          method: get

  createUser:
    handler: src/handlers/users.create
    events:
      - http:
          path: /users
          method: post
```

#### Exemplo 2 — Microserviço com múltiplas arquiteturas

```yaml
service: ml-pipeline

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  architecture: x86_64

plugins:
  - elven-instrumentation-serverless-plugin

custom:
  elvenLayerPlugin:
    tenant: "meu-tenant"
    token: "meu-api-token"
    region: "us-east-1"

functions:
  api:
    handler: src/api.handler
    events:
      - http:
          path: /predict
          method: post

  inference:
    handler: src/inference.handler
    architecture: arm64
    memorySize: 1024
    timeout: 30
    events:
      - sqs:
          arn: !GetAtt InferenceQueue.Arn

  data-ingestion:
    handler: src/ingestion.handler
    timeout: 300
    events:
      - schedule: rate(5 minutes)
```

#### Exemplo 3 — Instrumentações seletivas com funções excluídas

```yaml
service: ecommerce

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1

plugins:
  - elven-instrumentation-serverless-plugin

custom:
  elvenLayerPlugin:
    tenant: "meu-tenant"
    token: "meu-api-token"
    enabled_instrumentations: "http,express,pg,redis,undici"

functions:
  checkout:
    handler: src/checkout.handler
    events:
      - http:
          path: /checkout
          method: post

  payments:
    handler: src/payments.handler
    events:
      - sqs:
          arn: !GetAtt PaymentsQueue.Arn

  # Função interna — sem necessidade de observabilidade
  migrations:
    handler: src/migrations.handler
    disableElven: true
    events:
      - schedule: rate(1 day)
```

#### Exemplo 4 — Usando variáveis do Serverless para credenciais

Para não hardcodar credenciais no `serverless.yml`:

```yaml
custom:
  elvenLayerPlugin:
    tenant: ${env:ELVEN_TENANT}
    token: ${env:ELVEN_TOKEN}
    region: ${self:provider.region}
```

Deploy passando as variáveis:

```bash
ELVEN_TENANT=meu-tenant ELVEN_TOKEN=meu-token npx serverless deploy
```

Ou usando SSM Parameter Store:

```yaml
custom:
  elvenLayerPlugin:
    tenant: ${ssm:/elven/tenant}
    token: ${ssm:/elven/token}
```

#### Exemplo 5 — Customizar sampling por função

```yaml
custom:
  elvenLayerPlugin:
    tenant: "meu-tenant"
    token: "meu-token"

functions:
  # Função com alto volume → sampling de 10%
  high-traffic-api:
    handler: src/api.handler
    environment:
      OTEL_TRACES_SAMPLER: "traceidratio"
      OTEL_TRACES_SAMPLER_ARG: "0.1"
    events:
      - http:
          path: /search
          method: get

  # Função crítica → capturar tudo (default do plugin)
  checkout:
    handler: src/checkout.handler
    events:
      - http:
          path: /checkout
          method: post
```

***

### Boas práticas

#### Credenciais seguras

Nunca hardcode credenciais no `serverless.yml`. Use uma das opções:

```yaml
# Opção 1: Variáveis de ambiente
tenant: ${env:ELVEN_TENANT}
token: ${env:ELVEN_TOKEN}

# Opção 2: SSM Parameter Store
tenant: ${ssm:/elven/tenant}
token: ${ssm:/elven/token~true}  # ~true para decrypt SecureString

# Opção 3: Secrets Manager
tenant: ${ssm:/aws/reference/secretsmanager/elven-tenant}
```

#### Instrumentações seletivas

Habilite apenas as instrumentações que o projeto realmente usa. Menos instrumentações = cold start mais rápido:

```yaml
custom:
  elvenLayerPlugin:
    enabled_instrumentations: "http,pg,redis,undici"
```

Instrumentações disponíveis: `http`, `express`, `graphql`, `grpc`, `hapi`, `ioredis`, `koa`, `mongodb`, `mysql`, `net`, `pg`, `redis`, `memcached`, `mongoose`, `amqplib`, `bunyan`, `cassandra-driver`, `connect`, `kafkajs`, `knex`, `mysql2`, `nestjs-core`, `pino`, `restify`, `socket.io`, `undici`, `winston`

#### Timeout da função

As layers adicionam \~200-400ms de overhead no cold start. Certifique-se de que o timeout acomoda isso:

```yaml
provider:
  timeout: 10  # mínimo recomendado
```

Para funções com múltiplas instrumentações, considere 30 segundos.

#### Memória

Mais memória = mais CPU = cold start mais rápido. Para funções instrumentadas, recomendamos pelo menos **256 MB**:

```yaml
provider:
  memorySize: 256
```

#### Stages

O plugin usa o stage do Serverless para compor o nome do serviço e o atributo `environment`:

```bash
# Deploy em staging → nomes: ecommerce_staging_checkout
npx serverless deploy --stage staging

# Deploy em production → nomes: ecommerce_production_checkout
npx serverless deploy --stage production
```

***

### Troubleshooting

#### O deploy falha com erro de layer

**Sintoma:** Erro `Layer version arn:aws:lambda:... does not exist`.

**Causas possíveis:**

1. **Região incorreta** — A layer não está disponível na região configurada. Verifique `region` no plugin
2. **Versão inexistente** — Verifique se `log_version` / `log_version_arm64` estão corretos

```bash
# Verificar se a layer existe na região
aws lambda get-layer-version \
  --layer-name opentelemetry-nodejs-0_20_0 \
  --version-number 1 \
  --region us-east-1 \
  --query "LayerVersionArn"
```

#### A função falha ao iniciar (timeout ou crash)

**Sintoma:** `Runtime.ExitError` ou timeout no cold start.

**Verificações:**

1. **Timeout muito baixo** — Aumente para pelo menos 10 segundos
2. **Layer incompatível com a arquitetura** — Verifique se a função arm64 está recebendo a layer arm64
3. **Wrapper incorreto** — O plugin seta `AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler`. Se a layer OTel não estiver presente, isso causa crash

```bash
# Verificar configuração da função após deploy
npx serverless info --verbose
```

#### Traces não aparecem na Elven

**Sintoma:** Função executa normalmente, mas nenhum trace aparece.

**Verificações:**

1. Confirme que `collector_endpoint` está correto
2. Confirme que o `token` é válido
3. Se sobrescreveu `OTEL_TRACES_SAMPLER` na função, verifique o valor

```bash
# Ver as variáveis de ambiente da função
aws lambda get-function-configuration \
  --function-name minha-api-dev-hello \
  --query "Environment.Variables" \
  --output table
```

#### Logs não aparecem no Loki

**Sintoma:** Traces funcionam, mas logs não aparecem.

**Verificações:**

1. **`logs_endpoint` deve ser a URL base** (sem `/loki/api/v1/push`). A extension adiciona o path automaticamente
   * Correto: `https://loki.elvenobservability.com`
   * Errado: `https://loki.elvenobservability.com/loki/api/v1/push`
2. Confirme que `tenant` e `token` estão corretos
3. Habilite debug temporariamente na função:

```yaml
functions:
  minha-funcao:
    handler: handler.handler
    environment:
      DEBUG: "true"
```

Depois invoque e verifique os logs no CloudWatch.

#### Plugin não aparece no output do deploy

**Sintoma:** Nenhuma mensagem `[Elven]` no deploy.

**Verificações:**

1. O plugin está na seção `plugins` do `serverless.yml`
2. O pacote está instalado: `npm ls elven-instrumentation-serverless-plugin`
3. A seção `custom.elvenLayerPlugin` existe (mesmo que vazia, o plugin precisa ser listado em `plugins`)

#### Variáveis de ambiente não são injetadas

**Sintoma:** A função não tem as variáveis esperadas.

**Causa:** O plugin **não sobrescreve** variáveis já definidas na função. Se você definiu `OTEL_SERVICE_NAME` manualmente na função, o plugin não vai substituir.

**Verificação:**

```bash
npx serverless print --path functions.minhaFuncao.environment
```

***

### FAQ

**Preciso configurar algo em cada função?** Não. O plugin instrumenta **todas** as funções automaticamente. A única configuração necessária é o bloco `custom.elvenLayerPlugin` com `tenant` e `token`.

**Posso desabilitar em funções específicas?** Sim. Adicione `disableElven: true` na definição da função.

**Funciona com Serverless Framework v4?** Sim. O plugin é compatível com v3 e v4 do Serverless Framework.

**O plugin funciona com outros runtimes além de Node.js?** As layers padrão do plugin são para **Node.js**. Para outros runtimes (Python, Java, Ruby), use a opção `layers` para especificar as layers corretas. Consulte a documentação de instrumentação Lambda manual para os ARNs de cada runtime.

**O plugin sobrescreve minhas variáveis de ambiente?** Não. O plugin **só injeta** variáveis que **não existem** na função. Se você definir uma variável manualmente, ela tem prioridade.

**O plugin sobrescreve minhas layers?** Não. O plugin **adiciona** as layers sem remover as existentes. Não há duplicação — se a layer já existe, não é adicionada novamente.

**Como atualizar a versão das layers?** Altere `log_version` e `log_version_arm64` no `custom.elvenLayerPlugin`. Para a layer OTel, use a opção `layers` com os ARNs atualizados.

**Posso usar com monorepo / múltiplos `serverless.yml`?** Sim. Cada `serverless.yml` é independente. Use a mesma configuração `elvenLayerPlugin` em cada um, preferencialmente via variáveis de ambiente ou SSM para evitar duplicação.

**Qual o impacto no tamanho do deployment?** As layers **não** contam no limite de 50 MB do pacote de deploy (ZIP). Elas contam no limite total de 250 MB (código + layers descomprimidos).

**Posso usar junto com outros plugins do Serverless?** Sim. O plugin roda no hook `before:package:initialize` e não interfere com outros plugins. A ordem no `plugins` array geralmente não importa.

**Como vejo quais variáveis o plugin injetou?** Use `npx serverless print` para ver a configuração completa resolvida, ou verifique direto na AWS:

```bash
aws lambda get-function-configuration \
  --function-name {service}-{stage}-{function} \
  --query "Environment.Variables"
```
