Skip to content

Commit

Permalink
refactor: remove grpc trace exporter dependency on grpc-exporter-base
Browse files Browse the repository at this point in the history
  • Loading branch information
dyladan committed Jul 18, 2022
1 parent 747c404 commit d035aeb
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 48 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "experimental/packages/otlp-proto-exporter-base/protos"]
path = experimental/packages/otlp-proto-exporter-base/protos
url = https://github.com/open-telemetry/opentelemetry-proto.git
[submodule "experimental/packages/exporter-trace-otlp-grpc/protos"]
path = experimental/packages/exporter-trace-otlp-grpc/protos
url = https://github.com/open-telemetry/opentelemetry-proto
3 changes: 0 additions & 3 deletions experimental/packages/exporter-trace-otlp-grpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"devDependencies": {
"@babel/core": "7.16.0",
"@opentelemetry/api": "^1.0.0",
"@opentelemetry/otlp-exporter-base": "0.30.0",
"@types/mocha": "8.2.3",
"@types/node": "14.17.33",
"@types/sinon": "10.0.6",
Expand All @@ -70,9 +69,7 @@
"@grpc/grpc-js": "^1.5.9",
"@grpc/proto-loader": "^0.6.9",
"@opentelemetry/core": "1.4.0",
"@opentelemetry/otlp-grpc-exporter-base": "0.30.0",
"@opentelemetry/otlp-transformer": "0.30.0",
"@opentelemetry/resources": "1.4.0",
"@opentelemetry/sdk-trace-base": "1.4.0"
}
}
1 change: 1 addition & 0 deletions experimental/packages/exporter-trace-otlp-grpc/protos
Submodule protos added at c5c8b2
Original file line number Diff line number Diff line change
Expand Up @@ -14,58 +14,82 @@
* limitations under the License.
*/

import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
import { baggageUtils, getEnv } from '@opentelemetry/core';
import { Metadata } from '@grpc/grpc-js';
import {
OTLPGRPCExporterConfigNode,
OTLPGRPCExporterNodeBase,
ServiceClientType,
validateAndNormalizeUrl,
DEFAULT_COLLECTOR_URL
} from '@opentelemetry/otlp-grpc-exporter-base';
import { createExportTraceServiceRequest, IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer';
import * as grpc from '@grpc/grpc-js';
import { loadSync } from '@grpc/proto-loader';
import { ExportResult, ExportResultCode, getEnv } from '@opentelemetry/core';
import { createExportTraceServiceRequest } from '@opentelemetry/otlp-transformer';
import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
import * as path from "path";
import type { OTLPGRPCTraceExporterConfig, TraceServiceClient } from './types';
import { getConnectionOptions } from './util';

/**
* OTLP Trace Exporter for Node
*/
export class OTLPTraceExporter
extends OTLPGRPCExporterNodeBase<ReadableSpan,
IExportTraceServiceRequest>
implements SpanExporter {
export class OTLPTraceExporter implements SpanExporter {
private _metadata: grpc.Metadata;
private _serviceClient: TraceServiceClient;
public url: string;
public compression: grpc.compressionAlgorithms;

constructor(config: OTLPGRPCExporterConfigNode = {}) {
super(config);
const headers = baggageUtils.parseKeyPairsIntoRecord(getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS);
this.metadata ||= new Metadata();
for (const [k, v] of Object.entries(headers)) {
this.metadata.set(k, v);
}
}
constructor(config: OTLPGRPCTraceExporterConfig = {}) {
const { host, credentials, metadata, compression } = getConnectionOptions(config, getEnv());
this.url = host;
this.compression = compression
this._metadata = metadata;

convert(spans: ReadableSpan[]): IExportTraceServiceRequest {
return createExportTraceServiceRequest(spans);
}
const packageDefinition = loadSync("opentelemetry/proto/collector/trace/v1/trace_service.proto", {
keepCase: false,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [
// In webpack environments, the bundle may be at the same level as the protos/ directory
// path.resolve(__dirname, 'protos'),
// When running typescript directly in tests or with ts-node, the protos/ directory is one level above the src/ directory
path.resolve(__dirname, '..', 'protos'),
// When running the compiled js, the protos directory is two levels above the build/src/ directory
// path.resolve(__dirname, '..', '..', 'protos'),
],
});

getDefaultUrl(config: OTLPGRPCExporterConfigNode) {
return validateAndNormalizeUrl(this.getUrlFromConfig(config));
// any required here because
const packageObject: any = grpc.loadPackageDefinition(packageDefinition);
const channelOptions: grpc.ChannelOptions = {
'grpc.default_compression_algorithm': this.compression,
};
console.log(host, credentials, channelOptions)
this._serviceClient = new packageObject.opentelemetry.proto.collector.trace.v1.TraceService(host, credentials, channelOptions);
}

getServiceClientType() {
return ServiceClientType.SPANS;
}
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
// TODO
const deadline = Date.now() + 1000;

getServiceProtoPath(): string {
return 'opentelemetry/proto/collector/trace/v1/trace_service.proto';
const req = createExportTraceServiceRequest(spans);
console.log(req)
this._serviceClient.export(
req,
this._metadata,
{ deadline },
(...args: unknown[]) => {
console.log(args)
if (args[0]) {
resultCallback({
code: ExportResultCode.FAILED,
error: args[0] as any,
});
} else {
resultCallback({
code: ExportResultCode.SUCCESS,
});
}
},
);
}

getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string {
if (typeof config.url === 'string') {
return config.url;
}

return getEnv().OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
getEnv().OTEL_EXPORTER_OTLP_ENDPOINT ||
DEFAULT_COLLECTOR_URL;
shutdown(): Promise<void> {
throw new Error('Method not implemented.');
}
}
52 changes: 52 additions & 0 deletions experimental/packages/exporter-trace-otlp-grpc/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type * as grpc from '@grpc/grpc-js';

export type ConnectionOptions = {
host: string;
metadata: grpc.Metadata;
credentials: grpc.ChannelCredentials;
compression: grpc.compressionAlgorithms;
}

export type OTLPGRPCTraceExporterConfig = {
credentials?: grpc.ChannelCredentials;
metadata?: grpc.Metadata;
compression?: CompressionAlgorithm;

headers?: Partial<Record<string, unknown>>;
hostname?: string;
url?: string;
concurrencyLimit?: number;
/** Maximum time the OTLP exporter will wait for each batch export.
* The default value is 10000ms. */
timeoutMillis?: number;
}

export enum CompressionAlgorithm {
NONE = 'none',
GZIP = 'gzip'
}

export interface TraceServiceClient extends grpc.Client {
export: (
request: any,
metadata: grpc.Metadata,
options: grpc.CallOptions,
callback: Function
) => {};
}
123 changes: 123 additions & 0 deletions experimental/packages/exporter-trace-otlp-grpc/src/util/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as grpc from "@grpc/grpc-js";
import { diag } from "@opentelemetry/api";
import { baggageUtils, ENVIRONMENT } from "@opentelemetry/core";
import { CompressionAlgorithm, ConnectionOptions, OTLPGRPCTraceExporterConfig } from "../types";
import { getCredentials } from "./security";

const DEFAULT_COLLECTOR_URL = 'http://localhost:4317'

export function getConnectionOptions(config: OTLPGRPCTraceExporterConfig, env: Required<ENVIRONMENT>): ConnectionOptions {
const metadata = getMetadata(config, env);
const url = getUrl(config, env);
const host = getHost(url);
const compression = configureCompression(config, env);

if (url == null) {
return {
metadata,
credentials: config.credentials ?? grpc.credentials.createInsecure(),
host,
compression,
}
}

const credentials = config.credentials ?? getCredentials(url, env);

return {
metadata,
credentials,
host,
compression,
}
}

export function configureCompression(config: OTLPGRPCTraceExporterConfig, env: Required<ENVIRONMENT>): grpc.compressionAlgorithms {
switch (config.compression) {
case CompressionAlgorithm.GZIP:
return grpc.compressionAlgorithms.gzip
case CompressionAlgorithm.NONE:
return grpc.compressionAlgorithms.identity
}

const definedCompression = env.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION || env.OTEL_EXPORTER_OTLP_COMPRESSION;
return definedCompression === 'gzip' ? grpc.compressionAlgorithms.gzip : grpc.compressionAlgorithms.identity;
}

function getMetadata(config: OTLPGRPCTraceExporterConfig, env: Required<ENVIRONMENT>) {
const metadata = config.metadata ?? new grpc.Metadata();
const metadataMap = metadata.getMap();

const envHeaders = baggageUtils.parseKeyPairsIntoRecord(env.OTEL_EXPORTER_OTLP_HEADERS);
const envTraceHeaders = baggageUtils.parseKeyPairsIntoRecord(env.OTEL_EXPORTER_OTLP_TRACES_HEADERS);
const headers = config.headers ?? {};

console.log('env', env.OTEL_EXPORTER_OTLP_TRACES_HEADERS)
console.log('config', config.headers)
console.log('env headers', envHeaders)

for (const [k, v] of Object.entries(headers)) {
if (metadataMap[k] == null) {
metadata.set(k, String(v));
}
}

for (const [k, v] of Object.entries(envTraceHeaders)) {
if (metadataMap[k] == null) {
metadata.set(k, v);
}
}

for (const [k, v] of Object.entries(envHeaders)) {
if (metadataMap[k] == null) {
metadata.set(k, v);
}
}

return metadata;
}

function getUrl(config: OTLPGRPCTraceExporterConfig, env: Required<ENVIRONMENT>): string {
if (typeof config.url === 'string') {
return config.url;
}

return env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
env.OTEL_EXPORTER_OTLP_ENDPOINT ||
DEFAULT_COLLECTOR_URL;
}

function getHost(url: string): string {
const hasProtocol = url.match(/^([\w]{1,8}):\/\//);
if (!hasProtocol) {
url = `https://${url}`;
}
const target = new URL(url);
if (target.pathname && target.pathname !== '/') {
diag.warn(
'URL path should not be set when using grpc, the path part of the URL will be ignored.'
);
}
if (target.protocol !== '' && !target.protocol?.match(/^(http)s?:$/)) {
diag.warn(
'URL protocol should be http(s)://. Using http://.'
);
}
return target.host;
}

Loading

0 comments on commit d035aeb

Please sign in to comment.