Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions collector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,40 @@ from an S3 object using a CloudFormation template:

Loading configuration from S3 will require that the IAM role attached to your function includes read access to the relevant bucket.

### Inline configuration via Base64

You can supply the collector configuration inline as a Base64-encoded string using `OPENTELEMETRY_COLLECTOR_CONFIG_CONTENT`. This is useful when you want to avoid deploying a config file or referencing an external URI:

```
aws lambda update-function-configuration --function-name Function \
--environment Variables={OPENTELEMETRY_COLLECTOR_CONFIG_CONTENT=$(base64 < collector.yaml)}
```

Or in a CloudFormation template:

```yaml
Function:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
OPENTELEMETRY_COLLECTOR_CONFIG_CONTENT: <output of `base64 < collector.yaml`>
```

Use the standard Base64 alphabet (as produced by `base64` on Linux/macOS or `openssl base64`). URL-safe Base64 is not supported.

`OPENTELEMETRY_COLLECTOR_CONFIG_URI` takes precedence if both variables are set.

## Environment Variables

The following environment variables can be used to configure the OpenTelemetry Collector Lambda extension:

| Variable Name | Value | Description |
| ------------------------------------ | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENTELEMETRY_COLLECTOR_CONFIG_URI` | URI (e.g., `/var/task/collector.yaml`, `http://...`, `s3://...`) | Specifies the location of the OpenTelemetry Collector configuration file. This can be a path within the function's deployment package, an HTTP URI, or an S3 URI. If loading from S3, the function's IAM role needs read access to the specified S3 object. |
| `OPENTELEMETRY_EXTENSION_LOG_LEVEL` | `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal` (Default: `info`) | Controls the logging level of the OpenTelemetry Lambda extension itself. |
| Variable Name | Value | Description |
| --------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENTELEMETRY_COLLECTOR_CONFIG_URI` | URI (e.g., `/var/task/collector.yaml`, `http://...`, `s3://...`) | Specifies the location of the OpenTelemetry Collector configuration file. This can be a path within the function's deployment package, an HTTP URI, or an S3 URI. If loading from S3, the function's IAM role needs read access to the specified S3 object. |
| `OPENTELEMETRY_COLLECTOR_CONFIG_CONTENT`| Base64-encoded YAML | Supplies the collector configuration inline. The value must be standard Base64-encoded YAML. Ignored if `OPENTELEMETRY_COLLECTOR_CONFIG_URI` is also set (a warning is logged). If decoding fails, an error is logged and the extension falls back to the URI or default path. |
| `OPENTELEMETRY_EXTENSION_LOG_LEVEL` | `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal` (Default: `info`) | Controls the logging level of the OpenTelemetry Lambda extension itself. |

## Auto-Configuration

Expand Down
33 changes: 27 additions & 6 deletions collector/internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ package collector

import (
"context"
"encoding/base64"
"fmt"
"os"
"strings"

"github.com/open-telemetry/opentelemetry-collector-contrib/confmap/provider/s3provider"
"github.com/open-telemetry/opentelemetry-collector-contrib/confmap/provider/secretsmanagerprovider"
Expand Down Expand Up @@ -49,23 +51,42 @@ type Collector struct {
coreFunc func(zapcore.LevelEnabler) zapcore.Core
}

const (
envCollectorConfigURI = "OPENTELEMETRY_COLLECTOR_CONFIG_URI"
envCollectorConfigContent = "OPENTELEMETRY_COLLECTOR_CONFIG_CONTENT"
envCollectorConfigFile = "OPENTELEMETRY_COLLECTOR_CONFIG_FILE" // deprecated
)

func getConfig(logger *zap.Logger) string {
val, ex := os.LookupEnv("OPENTELEMETRY_COLLECTOR_CONFIG_URI")
if ex {
if val, ex := os.LookupEnv(envCollectorConfigURI); ex {
if _, also := os.LookupEnv(envCollectorConfigContent); also {
logger.Warn("Both " + envCollectorConfigURI + " and " + envCollectorConfigContent + " are set; using " + envCollectorConfigURI)
}
logger.Info("Using config URI from environment variable", zap.String("uri", val))
return val
}

if raw, ex := os.LookupEnv(envCollectorConfigContent); ex {
if trimmed := strings.TrimSpace(raw); trimmed != "" {
decoded, err := base64.StdEncoding.DecodeString(trimmed)
if err != nil {
logger.Error("Failed to decode "+envCollectorConfigContent+" as Base64; ignoring", zap.Error(err))
} else {
logger.Info("Using inline config from " + envCollectorConfigContent)
return "yaml:" + string(decoded)
}
}
}

// The name of the environment variable was changed
// This is the old name, kept for backwards compatibility
oldVal, oldEx := os.LookupEnv("OPENTELEMETRY_COLLECTOR_CONFIG_FILE")
if oldEx {
if oldVal, oldEx := os.LookupEnv(envCollectorConfigFile); oldEx {
logger.Info("Using config URI from deprecated environment variable", zap.String("uri", oldVal))
logger.Warn("The OPENTELEMETRY_COLLECTOR_CONFIG_FILE environment variable is deprecated. Please use OPENTELEMETRY_COLLECTOR_CONFIG_URI instead.")
logger.Warn("The " + envCollectorConfigFile + " environment variable is deprecated. Please use " + envCollectorConfigURI + " instead.")
return oldVal
}

// If neither environment variable is set, use the default
// If no environment variable is set, use the default
defaultVal := "/opt/collector-config/config.yaml"
logger.Info("Using default config URI", zap.String("uri", defaultVal))
return defaultVal
Expand Down
64 changes: 64 additions & 0 deletions collector/internal/collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package collector

import (
"context"
"encoding/base64"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -100,6 +101,69 @@ func TestCollectorLogLevelDoesNotSuppressExtensionLogs(t *testing.T) {
"extension logs should be controlled by the extension logger, not collector config")
}

func TestCollectorConfigContentStartsCollector(t *testing.T) {
yamlContent := `
receivers:
nop:
exporters:
nop:
service:
telemetry:
logs:
level: info
pipelines:
traces:
receivers: [nop]
exporters: [nop]
`
t.Setenv(envCollectorConfigContent, base64.StdEncoding.EncodeToString([]byte(yamlContent)))

core, logs := observer.New(zapcore.InfoLevel)
col := NewCollector(zap.New(core), testFactories(t), "test")

ctx := context.Background()
err := col.Start(ctx)
require.NoError(t, err)

err = col.Stop()
require.NoError(t, err)

assert.NotEmpty(t, logs.FilterMessage("Using inline config from "+envCollectorConfigContent).All())
}

func TestCollectorConfigURITakesPrecedenceOverContent(t *testing.T) {
t.Setenv(envCollectorConfigURI, "file:testdata/config-info-level.yaml")
t.Setenv(envCollectorConfigContent, base64.StdEncoding.EncodeToString([]byte("irrelevant: true")))

core, logs := observer.New(zapcore.WarnLevel)
col := NewCollector(zap.New(core), testFactories(t), "test")

ctx := context.Background()
err := col.Start(ctx)
require.NoError(t, err)

err = col.Stop()
require.NoError(t, err)

warnLogs := logs.FilterMessageSnippet(envCollectorConfigContent + " are set").All()
assert.NotEmpty(t, warnLogs, "expected a warning about both env vars being set")
}

func TestCollectorConfigContentInvalidBase64FallsBack(t *testing.T) {
t.Setenv(envCollectorConfigContent, "not-valid-base64!@#$")

core, logs := observer.New(zapcore.ErrorLevel)
col := NewCollector(zap.New(core), testFactories(t), "test")

ctx := context.Background()
// Collector will fall back to the default path which won't exist — expect start error
err := col.Start(ctx)
assert.Error(t, err, "expected collector to fail when falling back to missing default config")

errorLogs := logs.FilterMessageSnippet("Failed to decode").All()
assert.NotEmpty(t, errorLogs, "expected an error log about invalid Base64")
}

func testFactories(t *testing.T) otelcol.Factories {
receivers, err := otelcol.MakeFactoryMap(receivertest.NewNopFactory())
require.NoError(t, err)
Expand Down