Skip to content

Maintenance: replace EnvironmentVariablesService class with helper functions in Metrics #4136

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 Metrics 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 Metrics package currently has its own extended version of the service that adds metrics-specific environment variable handling.

Which area does this relate to?

Metrics

Solution

Currently the Metrics 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 readonly namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE';
  private readonly functionNameVariable = 'POWERTOOLS_METRICS_FUNCTION_NAME';
  private readonly disabledVariable = 'POWERTOOLS_METRICS_DISABLED';

  public getNamespace(): string {
    return this.get(this.namespaceVariable);
  }

  public getFunctionName(): string {
    return this.get(this.functionNameVariable);
  }

  public getMetricsDisabled(): boolean {
    const value = this.get(this.disabledVariable);
    if (this.isValueFalse(value)) return false;
    if (this.isValueTrue(value)) return true;
    if (this.isDevMode()) return true;
    return false;
  }
}

This service is used throughout the Metrics class in several places 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 
} from '@aws-lambda-powertools/commons/utils/env';

class Metrics {
  // Replace envVarsService with #envConfig using hash notation
  readonly #envConfig: {
    namespace: string;
    functionName: string;
    serviceName: string;
    disabled: boolean;
    devMode: boolean;
  };

  // In setOptions method, replace setEnvVarsService() call
  private setOptions(options: MetricsOptions): void {
    const {
      customConfigService,
      namespace,
      serviceName,
      singleMetric,
      defaultDimensions,
      functionName,
    } = options;

    this.#setEnvConfig(); // Replace this.setEnvVarsService();
    this.setConsole();
    this.setCustomConfigService(customConfigService);
    this.setDisabled();
    this.setNamespace(namespace);
    this.setService(serviceName);
    this.setDefaultDimensions(defaultDimensions);
    this.setFunctionNameForColdStartMetric(functionName);
    this.isSingleMetric = singleMetric || false;

    return this;
  }

  // Replace setEnvVarsService() with setEnvConfig()
  private setEnvConfig(): void {
    this.#envConfig = {
      namespace: getStringFromEnv({
        key: 'POWERTOOLS_METRICS_NAMESPACE',
        defaultValue: '',
      }),
      functionName: getStringFromEnv({
        key: 'POWERTOOLS_METRICS_FUNCTION_NAME',
        defaultValue: '',
      }),
      serviceName: getServiceName(),
      disabled: this.#getMetricsDisabledFromEnv(),
      devMode: isDevMode(),
    };
  }

  private #getMetricsDisabledFromEnv(): boolean {
    const disabledValue = getStringFromEnv({
      key: 'POWERTOOLS_METRICS_DISABLED',
      defaultValue: '',
    });
    
    if (disabledValue.toLowerCase() === 'false') return false;
    if (disabledValue.toLowerCase() === 'true') return true;
    return isDevMode();
  }

  // Update methods to use #envConfig instead of service calls
  private setNamespace(namespace: string | undefined): void {
    this.namespace = (namespace ||
      this.getCustomConfigService()?.getNamespace() ||
      this.#envConfig.namespace) as string;
  }

  protected setFunctionNameForColdStartMetric(functionName?: string): void {
    const value = functionName?.trim() ?? this.#envConfig.functionName.trim();
    if (value && value.length > 0) {
      this.functionName = value;
    }
  }

  private setService(service: string | undefined): void {
    const targetService =
      ((service ||
        this.getCustomConfigService()?.getServiceName() ||
        this.#envConfig.serviceName) as string) ||
      this.defaultServiceName;
    // ...
  }

  private setDisabled(): void {
    this.disabled = this.#envConfig.disabled;
  }

  private setConsole(): void {
    if (!this.#envConfig.devMode) {
      this.console = new Console({
        stdout: process.stdout,
        stderr: process.stderr,
      });
    } else {
      this.console = console;
    }
  }
}

Files to be modified:

  1. Remove packages/metrics/src/config/EnvironmentVariablesService.ts entirely
  2. Update packages/metrics/src/Metrics.ts to use the #envConfig approach
  3. Remove the envVarsService property and related methods (getEnvVarsService(), setEnvVarsService())

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 50 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 onemetricsThis item relates to the Metrics Utility

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions