Skip to content

Conversation

@RaphaelManke
Copy link

Summary

Automatically extract the AWS account ID from the Lambda Extensions API registration response and inject it as the cloud.account.id attribute into all telemetry (traces, logs, metrics) via a confmap converter.

This implementation uses the OpenTelemetry Collector's converter pattern to mutate the loaded configuration, ensuring the account ID is automatically available without requiring configuration or environment variables.

Changes

  • extensionapi/client.go: Added AccountId field to RegisterResponse and request the accountId feature via Lambda-Extension-Accept-Feature header
  • accountidprocessor/converter.go: New confmap converter that automatically injects a resource processor for cloud.account.id attribute into all pipelines
  • accountidprocessor/converter_test.go: Comprehensive tests for converter behavior (empty account ID, no pipelines, multiple pipelines, leading zeros)
  • extensionapi/client_test.go: Tests for JSON unmarshaling with leading zero preservation
  • lambdacomponents/default.go: Updated to accept accountID and return converter factories
  • collector.go: Updated to accept and register custom converters
  • manager.go: Updated to pass account ID through the initialization flow

Benefits

  • Account ID automatically available in all telemetry without configuration
  • No environment variables needed, uses AWS Lambda API response
  • Follows OpenTelemetry Collector patterns
  • Static injection at startup with no runtime overhead
  • Comprehensive test coverage for edge cases (leading zeros, multiple pipelines)

… registration

Automatically extract the AWS account ID from the Lambda Extensions API
registration response and inject it as the 'cloud.account.id' attribute
into all telemetry (traces, logs, metrics) via a confmap converter.

Changes:
- Add AccountId field to RegisterResponse struct
- Request Lambda-Extension-Accept-Feature header with 'accountId' value
- Create accountidprocessor converter to inject cloud.account.id attribute
- Update lambdacomponents.Components to accept accountID and return converters
- Update collector.NewCollector to accept and register custom converters
- Update manager to pass account ID through the initialization flow

Includes:
- Comprehensive tests for JSON unmarshaling with leading zero preservation
- Tests for converter behavior across different pipeline configurations
- Tests for account ID handling in the extension API client

Benefits:
- Account ID automatically available in all telemetry without configuration
- No environment variables needed, uses AWS Lambda API response
- Follows OpenTelemetry Collector converter pattern
- Static injection at startup, no runtime overhead
@RaphaelManke RaphaelManke requested a review from a team as a code owner October 24, 2025 12:24
@wpessers wpessers added the go Pull requests that update Go code label Oct 28, 2025
return &converter{accountID: accountID}
}

func (c converter) Convert(_ context.Context, conf *confmap.Conf) error {
Copy link
Member

@maxday maxday Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can tweak this function a bit:

  • use `(c* converter) in the definition
  • simplify a bit the for loop
  • remove the usage of sprintf

This will give something like:

func (c *converter) Convert(_ context.Context, conf *confmap.Conf) error {
	if c.accountID == "" {
		return nil
	}

	svc, ok := conf.Get(serviceKey).(map[string]interface{})
	if !ok {
		return nil
	}

	pipes, ok := svc[pipelinesKey].(map[string]interface{})
	if !ok {
		return nil
	}

	for _, v := range pipes {
		p, ok := v.(map[string]interface{})
		if !ok {
			continue
		}

		raw, _ := p[processorsKey]
		procs, _ := raw.([]interface{})
		p[processorsKey] = append([]interface{}{resourceProc}, procs...)
	}

	resourceProcessor := map[string]interface{}{
		resourceProc: map[string]interface{}{
			"attributes": []map[string]interface{}{
				{
					"key":    accountIDAttrKey,
					"value":  c.accountID,
					"action": "insert",
				},
			},
		},
	}

	return conf.Merge(confmap.NewFromStringMap(map[string]interface{}{
		"processors": resourceProcessor,
	}))
}

WDYT?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! I tried the in-place mutation approach, but it doesn't work because confmap.Conf.Get() and ToStringMap() both return deep copies, not references. I checked the decoupleafterbatchconverter in this repo and it uses the same :: path notation with conf.Merge() pattern.

I've adopted your other suggestions:

  • Pointer receiver (c *converter)
  • Cleaner variable names and simplified type assertions
  • Using semconv constant (go.opentelemetry.io/otel/semconv since the collector one is deprecated)
  • Removed fmt.Sprintf (using string concatenation instead)
  • Added idempotency check (from @wpessers' suggestion)

- Use pointer receiver (c *converter) instead of value receiver
- Use semconv.CloudAccountIDKey from go.opentelemetry.io/otel/semconv
- Replace fmt.Sprintf with string concatenation
- Add idempotency check to skip if processor already prepended
- Use 'any' instead of 'interface{}' for modern Go idioms
- Fix constant alignment in client.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants