Skip to content

Commit

Permalink
Merge pull request #550 from myrotvorets/logs-metrics
Browse files Browse the repository at this point in the history
Add support for metrics and logs; remove legacy code
  • Loading branch information
myrotvorets-team authored Oct 2, 2023
2 parents a70991a + d2a07de commit b412be2
Show file tree
Hide file tree
Showing 12 changed files with 692 additions and 30 deletions.
65 changes: 65 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,68 @@ jobs:
with:
node-version: ${{ matrix.node.version }}
registry-url: https://npm.pkg.github.com

lint:
name: Check code style
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '[ci skip]') || github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
packages: read
env:
NO_UPDATE_NOTIFIER: 'true'
NPM_CONFIG_FUND: '0'
NPM_CONFIG_AUDIT: '0'
SUPPRESS_SUPPORT: '1'
steps:
- name: Check out the code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0

- name: Set up Node.js environment
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 'lts/*'
registry-url: https://npm.pkg.github.com
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Run postinstall scripts
run: npm rebuild && npm run prepare --if-present

- name: Check code style
run: npm run lint

typecheck:
name: Check types
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '[ci skip]') || github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
packages: read
env:
NO_UPDATE_NOTIFIER: 'true'
NPM_CONFIG_FUND: '0'
NPM_CONFIG_AUDIT: '0'
SUPPRESS_SUPPORT: '1'
steps:
- name: Check out the code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0

- name: Set up Node.js environment
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 'lts/*'
registry-url: https://npm.pkg.github.com
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check code style
run: npm run typecheck
34 changes: 20 additions & 14 deletions lib/index.mts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { type Meter, metrics } from '@opentelemetry/api';
import { type Logger, logs } from '@opentelemetry/api-logs';
import { processDetectorSync } from '@opentelemetry/resources';
import { NodeSDK, type NodeSDKConfiguration } from '@opentelemetry/sdk-node';
import { BatchSpanProcessor, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import {
dockerDetector,
k8sDetector,
osDetector,
packageJsonDetector,
} from '@myrotvorets/opentelemetry-resource-detectors';
import { MetricsConfigurator } from './metrics.mjs';
import { LogsConfigurator } from './logs.mjs';

export type Config = { serviceName: string } & Omit<Partial<NodeSDKConfiguration>, 'serviceName'>;

Expand All @@ -19,18 +21,6 @@ export class OpenTelemetryConfigurator {
public constructor(config: Config) {
this._config = config;

if (!this._config.traceExporter && !this._config.spanProcessor) {
this._config.traceExporter = process.env.OTEL_EXPORTER_ZIPKIN_ENDPOINT ? new ZipkinExporter() : undefined;
if (this._config.traceExporter) {
/* c8 disable start */
this._config.spanProcessor =
process.env.NODE_ENV === 'production'
? new BatchSpanProcessor(this._config.traceExporter)
: new SimpleSpanProcessor(this._config.traceExporter);
/* c8 disable end */
}
}

if (!this._config.resourceDetectors?.length && false !== this._config.autoDetectResources) {
this._config.resourceDetectors = [
osDetector,
Expand All @@ -41,6 +31,14 @@ export class OpenTelemetryConfigurator {
];
}

if (!this._config.metricReader) {
this._config.metricReader = new MetricsConfigurator().reader;
}

if (!this._config.logRecordProcessor) {
this._config.logRecordProcessor = new LogsConfigurator().processor;
}

this._sdk = new NodeSDK(this._config);
}

Expand Down Expand Up @@ -76,4 +74,12 @@ export class OpenTelemetryConfigurator {
public get config(): Readonly<Config> {
return this._config;
}

public meter(): Meter {
return metrics.getMeter(this._config.serviceName);
}

public logger(): Logger {
return logs.getLogger(this._config.serviceName);
}
}
87 changes: 87 additions & 0 deletions lib/logs.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { diag } from '@opentelemetry/api';
import { getEnv, getEnvWithoutDefaults } from '@opentelemetry/core';
import { OTLPLogExporter as OTLPGrpcLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { OTLPLogExporter as OTLPHttpLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { OTLPLogsExporter as OTLPProtoLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
import { type LogRecordExporter, type LogRecordProcessor, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { filterBlanksAndNulls } from './utils.mjs';

export class LogsConfigurator {
private readonly _processor: LogRecordProcessor | undefined;

public constructor() {
const env = process.env;
let exporters = filterBlanksAndNulls(Array.from(new Set((env.OTEL_LOGS_EXPORTER ?? '').split(','))));

if (exporters.length === 0) {
exporters = ['otlp'];
} else if (exporters[0] === 'none' && exporters.length === 1) {
diag.info('Logs exporting is disabled.');
}

for (const value of exporters) {
const processor = LogsConfigurator.tryCreateLogProcessor(value);

if (processor) {
this._processor = processor;
break;
}
}
}

public get processor(): LogRecordProcessor | undefined {
return this._processor;
}

/**
* @see https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#exporter-selection
*/
protected static getOtlpProtocol(): string {
const parsedEnvValues = getEnvWithoutDefaults();
const env = getEnv();

return (
parsedEnvValues.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ??
parsedEnvValues.OTEL_EXPORTER_OTLP_PROTOCOL ??
env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL // It will have a default value, no need to fall back to OTEL_EXPORTER_OTLP_PROTOCOL
);
}

/**
* @see https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#exporter-selection
*/
protected static configureOtlp(): LogRecordExporter | undefined {
const protocol = LogsConfigurator.getOtlpProtocol();

switch (protocol) {
case 'grpc':
return new OTLPGrpcLogExporter();

case 'http/json':
return new OTLPHttpLogExporter();

case 'http/protobuf':
return new OTLPProtoLogExporter();

default:
diag.warn(`Unsupported OTLP logs protocol: ${protocol}.`);
return undefined;
}
}

protected static tryCreateLogProcessor(name: string): LogRecordProcessor | undefined {
switch (name) {
case 'otlp': {
const exporter = LogsConfigurator.configureOtlp();
return exporter ? new SimpleLogRecordProcessor(exporter) : undefined;
}

case 'none':
return undefined;

default:
diag.warn(`Unsupported logs exporter: ${name}.`);
return undefined;
}
}
}
91 changes: 91 additions & 0 deletions lib/metrics.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { diag } from '@opentelemetry/api';
import { getEnv, getEnvWithoutDefaults } from '@opentelemetry/core';
import { OTLPMetricExporter as OTLPGrpcMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { OTLPMetricExporter as OTLPHttpMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { OTLPMetricExporter as OTLPProtoMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { type MetricReader, PeriodicExportingMetricReader, type PushMetricExporter } from '@opentelemetry/sdk-metrics';
import { filterBlanksAndNulls } from './utils.mjs';

export class MetricsConfigurator {
private readonly _reader: MetricReader | undefined;

public constructor() {
const env = process.env;
let exporters = filterBlanksAndNulls(Array.from(new Set((env.OTEL_METRICS_EXPORTER ?? '').split(','))));

if (exporters.length === 0) {
exporters = ['otlp'];
} else if (exporters[0] === 'none' && exporters.length === 1) {
diag.info('Metrics exporting is disabled.');
}

for (const value of exporters) {
const reader = MetricsConfigurator.tryCreateReader(value);

if (reader) {
this._reader = reader;
break;
}
}
}

public get reader(): MetricReader | undefined {
return this._reader;
}

/**
* @see https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#exporter-selection
*/
protected static getOtlpProtocol(): string {
const parsedEnvValues = getEnvWithoutDefaults();
const env = getEnv();

return (
parsedEnvValues.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL ??
parsedEnvValues.OTEL_EXPORTER_OTLP_PROTOCOL ??
env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL // It will have a default value, no need to fall back to OTEL_EXPORTER_OTLP_PROTOCOL
);
}

/**
* @see https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#exporter-selection
*/
protected static configureOtlp(): PushMetricExporter | undefined {
const protocol = MetricsConfigurator.getOtlpProtocol();

switch (protocol) {
case 'grpc':
return new OTLPGrpcMetricExporter();

case 'http/json':
return new OTLPHttpMetricExporter();

case 'http/protobuf':
return new OTLPProtoMetricExporter();

default:
diag.warn(`Unsupported OTLP metrics protocol: ${protocol}.`);
return undefined;
}
}

protected static tryCreateReader(name: string): MetricReader | undefined {
switch (name) {
case 'otlp': {
const exporter = MetricsConfigurator.configureOtlp();
return exporter ? new PeriodicExportingMetricReader({ exporter }) : undefined;
}

case 'prometheus':
return new PrometheusExporter();

case 'none':
return undefined;

default:
diag.warn(`Unsupported metrics exporter: ${name}.`);
return undefined;
}
}
}
3 changes: 3 additions & 0 deletions lib/utils.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function filterBlanksAndNulls(list: string[]): string[] {
return list.map((item) => item.trim()).filter((s) => s !== 'null' && s !== '');
}
Loading

0 comments on commit b412be2

Please sign in to comment.