Skip to content

Maintenance: replace EnvironmentVariablesService class with helper functions in Tracer #4137

Open
@dreamorosi

Description

@dreamorosi

Summary

In #3945 we added new zero-dependency functional utilities to read and parse environment variables. These new functions supersede the old class based environment service approach. We should refactor the Tracer package to use the new function based approach instead of the custom EnvironmentVariablesService class.

Why is this needed?

The rationale for the introduction of these utilities as per #3945 is as follows:

[M]ost of the advantage to customers will come in the form of smaller utilities over time, since we'll be able to use these helpers across other Powertools for AWS utilities and replace the existing EnvironmentVariablesService class-based model which is extremely verbose and requires props drilling if you want to access env variables deep into an inheritance stack.

This change will help us to eventually remove the EnvironmentVariablesService entirely from the code base. The Tracer package currently has its own extended version of the service that adds tracer-specific environment variable handling.

Which area does this relate to?

Tracer

Solution

Currently the Tracer package has its own EnvironmentVariablesService class that extends the common one:

import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons';

class EnvironmentVariablesService
  extends CommonEnvironmentVariablesService
  implements ConfigServiceInterface
{
  private awsExecutionEnv = 'AWS_EXECUTION_ENV';
  private samLocalVariable = 'AWS_SAM_LOCAL';
  private tracerCaptureErrorVariable = 'POWERTOOLS_TRACER_CAPTURE_ERROR';
  private tracerCaptureHTTPsRequestsVariable = 'POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS';
  private tracerCaptureResponseVariable = 'POWERTOOLS_TRACER_CAPTURE_RESPONSE';
  private tracingEnabledVariable = 'POWERTOOLS_TRACE_ENABLED';

  public getAwsExecutionEnv(): string {
    return this.get(this.awsExecutionEnv);
  }

  public getCaptureHTTPsRequests(): string {
    return this.get(this.tracerCaptureHTTPsRequestsVariable);
  }

  public getSamLocal(): string {
    return this.get(this.samLocalVariable);
  }

  public getTracingCaptureError(): string {
    return this.get(this.tracerCaptureErrorVariable);
  }

  public getTracingCaptureResponse(): string {
    return this.get(this.tracerCaptureResponseVariable);
  }

  public getTracingEnabled(): string {
    return this.get(this.tracingEnabledVariable);
  }
}

const environmentVariablesService = new EnvironmentVariablesService();

This service is used throughout the Tracer class and ProviderService for reading environment variables multiple times.

Proposed refactor using new functional utilities:

Replace the service with a private #envConfig object that is populated once during the setOptions method:

import { 
  getStringFromEnv, 
  getBooleanFromEnv, 
  isDevMode, 
  getServiceName,
  getXRayTraceIdFromEnv
} from '@aws-lambda-powertools/commons/utils/env';

class Tracer {
  // Replace envVarsService with #envConfig using hash notation
  readonly #envConfig: {
    awsExecutionEnv: string;
    samLocal: string;
    captureError: string;
    captureHTTPsRequests: string;
    captureResponse: string;
    tracingEnabled: string;
    serviceName: string;
    xrayTraceId: string;
  };

  // In setOptions method, replace envVarsService assignment
  private setOptions(options: TracerOptions): Tracer {
    const { enabled, serviceName, captureHTTPsRequests, customConfigService } = options;

    this.#setEnvConfig(); // Replace this.envVarsService = environmentVariablesService;
    this.setCustomConfigService(customConfigService);
    this.setTracingEnabled(enabled);
    this.setCaptureResponse();
    this.setCaptureError();
    this.setServiceName(serviceName);
    this.setCaptureHTTPsRequests(captureHTTPsRequests);

    return this;
  }

  // Replace envVarsService initialization
  private #setEnvConfig(): void {
    this.#envConfig = {
      awsExecutionEnv: getStringFromEnv({
        key: 'AWS_EXECUTION_ENV',
        defaultValue: '',
      }),
      samLocal: getStringFromEnv({
        key: 'AWS_SAM_LOCAL',
        defaultValue: '',
      }),
      captureError: getStringFromEnv({
        key: 'POWERTOOLS_TRACER_CAPTURE_ERROR',
        defaultValue: '',
      }),
      captureHTTPsRequests: getStringFromEnv({
        key: 'POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS',
        defaultValue: '',
      }),
      captureResponse: getStringFromEnv({
        key: 'POWERTOOLS_TRACER_CAPTURE_RESPONSE',
        defaultValue: '',
      }),
      tracingEnabled: getStringFromEnv({
        key: 'POWERTOOLS_TRACE_ENABLED',
        defaultValue: '',
      }),
      serviceName: getServiceName(),
      xrayTraceId: getXRayTraceIdFromEnv() || '',
    };
  }

  // Update methods to use #envConfig instead of service calls
  private isAmplifyCli(): boolean {
    return this.#envConfig.awsExecutionEnv === 'AWS_Lambda_amplify-mock';
  }

  private isLambdaExecutionEnv(): boolean {
    return this.#envConfig.awsExecutionEnv !== '';
  }

  private isLambdaSamCli(): boolean {
    return this.#envConfig.samLocal !== '';
  }

  private setCaptureError(): void {
    if (this.captureError !== undefined) {
      return;
    }

    if (this.#envConfig.captureError.toLowerCase() === 'false') {
      this.captureError = false;
      return;
    }

    this.captureError = true;
  }

  private setCaptureHTTPsRequests(captureHTTPsRequests?: boolean): void {
    if (captureHTTPsRequests !== undefined) {
      this.captureHTTPsRequests = captureHTTPsRequests;
      return;
    }

    if (this.#envConfig.captureHTTPsRequests.toLowerCase() === 'false') {
      this.captureHTTPsRequests = false;
      return;
    }

    this.captureHTTPsRequests = true;
  }

  private setCaptureResponse(): void {
    if (this.captureResponse !== undefined) {
      return;
    }

    if (this.#envConfig.captureResponse.toLowerCase() === 'false') {
      this.captureResponse = false;
      return;
    }

    this.captureResponse = true;
  }

  private setServiceName(serviceName?: string): void {
    if (serviceName !== undefined && this.isValidServiceName(serviceName)) {
      this.serviceName = serviceName;
      return;
    }

    if (this.#envConfig.serviceName !== undefined && this.isValidServiceName(this.#envConfig.serviceName)) {
      this.serviceName = this.#envConfig.serviceName;
      return;
    }

    this.serviceName = this.defaultServiceName;
  }

  private setTracingEnabled(enabled?: boolean): void {
    if (enabled !== undefined) {
      this.tracingEnabled = enabled;
      return;
    }

    if (this.#envConfig.tracingEnabled.toLowerCase() === 'false') {
      this.tracingEnabled = false;
      return;
    }

    this.tracingEnabled = true;
  }
}

For ProviderService, replace the import and usage:

// Replace import
import { getXRayTraceIdFromEnv } from '@aws-lambda-powertools/commons/utils/env';

// Replace usage in ProviderService
request.addHeader(
  'X-Amzn-Trace-Id',
  `Root=${getXRayTraceIdFromEnv()};Parent=${subsegment.id};Sampled=${subsegment.notTraced ? '0' : '1'}`
);

Files to be modified:

  1. Remove packages/tracer/src/config/EnvironmentVariablesService.ts entirely
  2. Update packages/tracer/src/Tracer.ts to use the #envConfig approach
  3. Update packages/tracer/src/provider/ProviderService.ts to use functional utilities
  4. Remove the envVarsService property and related methods (getEnvVarsService())

Benefits of this approach:

  • Environment variables are read only once during initialization (better performance)
  • No repeated environment variable access throughout the object lifecycle
  • Cleaner, more functional approach
  • Eliminates the verbose class-based service pattern
  • Makes environment configuration explicit and cached
  • Consistent with existing setOptions pattern

This refactor would eliminate approximately 75 lines of class-based code and improve performance by avoiding repeated environment variable reads.

Acknowledgment

Future readers

Please react with 👍 and your use case to help us understand customer demand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmedThe scope is clear, ready for implementationgood-first-issueSomething that is suitable for those who want to start contributinghelp-wantedWe would really appreciate some support from community for this onetracerThis item relates to the Tracer Utility

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions