Skip to content

Commit

Permalink
Merge pull request #421 from solarwinds/NH-88600
Browse files Browse the repository at this point in the history
Use HTTP exporters for OTLP
  • Loading branch information
raphael-theriault-swi committed Aug 13, 2024
2 parents f7f1e12 + 4fbedf8 commit cadbcd6
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 101 deletions.
3 changes: 3 additions & 0 deletions .yarn/versions/9578c31f.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
releases:
"@solarwinds-apm/sampling": patch
solarwinds-apm: minor
1 change: 0 additions & 1 deletion packages/sampling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"@opentelemetry/api": "^1.3.0",
"@opentelemetry/sdk-metrics": "~1.25.0",
"@solarwinds-apm/eslint-config": "workspace:^",
"@solarwinds-apm/lazy": "workspace:^",
"@solarwinds-apm/rollup-config": "workspace:^",
"@solarwinds-apm/test": "workspace:^",
"@types/node": "^16.13.0",
Expand Down
50 changes: 23 additions & 27 deletions packages/sampling/src/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,28 @@ limitations under the License.
*/

import { metrics, ValueType } from "@opentelemetry/api"
import { lazy } from "@solarwinds-apm/lazy"

export const counters = lazy(() => {
const meter = metrics.getMeter("sw.apm.sampling.metrics")
const meter = metrics.getMeter("sw.apm.sampling.metrics")

return {
requestCount: meter.createCounter("trace.service.request_count", {
valueType: ValueType.INT,
}),
sampleCount: meter.createCounter("trace.service.samplecount", {
valueType: ValueType.INT,
}),
traceCount: meter.createCounter("trace.service.tracecount", {
valueType: ValueType.INT,
}),
throughTraceCount: meter.createCounter(
"trace.service.through_trace_count",
{ valueType: ValueType.INT },
),
triggeredTraceCount: meter.createCounter(
"trace.service.triggered_trace_count",
{ valueType: ValueType.INT },
),
tokenBucketExhaustionCount: meter.createCounter(
"trace.service.tokenbucket_exhaustion_count",
{ valueType: ValueType.INT },
),
}
})
export const counters = {
requestCount: meter.createCounter("trace.service.request_count", {
valueType: ValueType.INT,
}),
sampleCount: meter.createCounter("trace.service.samplecount", {
valueType: ValueType.INT,
}),
traceCount: meter.createCounter("trace.service.tracecount", {
valueType: ValueType.INT,
}),
throughTraceCount: meter.createCounter("trace.service.through_trace_count", {
valueType: ValueType.INT,
}),
triggeredTraceCount: meter.createCounter(
"trace.service.triggered_trace_count",
{ valueType: ValueType.INT },
),
tokenBucketExhaustionCount: meter.createCounter(
"trace.service.tokenbucket_exhaustion_count",
{ valueType: ValueType.INT },
),
}
4 changes: 3 additions & 1 deletion packages/solarwinds-apm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@
"@grpc/grpc-js": "^1.10.6",
"@opentelemetry/api-logs": "~0.52.0",
"@opentelemetry/core": "~1.25.0",
"@opentelemetry/exporter-logs-otlp-grpc": "~0.52.0",
"@opentelemetry/exporter-logs-otlp-proto": "~0.52.0",
"@opentelemetry/exporter-metrics-otlp-proto": "~0.52.0",
"@opentelemetry/exporter-trace-otlp-proto": "~0.52.0",
"@opentelemetry/instrumentation": "~0.52.0",
"@opentelemetry/resources": "~1.25.0",
"@opentelemetry/sdk-logs": "~0.52.0",
Expand Down
47 changes: 27 additions & 20 deletions packages/solarwinds-apm/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ import { z, ZodError, ZodIssueCode } from "zod"

import aoCert from "./appoptics.crt.js"

const ENDPOINTS = {
traces: "/v1/traces",
metrics: "/v1/metrics",
logs: "/v1/logs",
}

const boolean = z.union([
z.boolean(),
z
Expand Down Expand Up @@ -116,7 +122,9 @@ const transactionSettings = z.array(
.transform((s) => ({
tracing: s.tracing,
matcher:
"matcher" in s ? s.matcher : (ident: string) => s.regex.test(ident),
"matcher" in s
? s.matcher.bind(s)
: (ident: string) => s.regex.test(ident),
})),
)

Expand Down Expand Up @@ -172,7 +180,7 @@ const schema = z.object({
.default({}),
})

export interface Config extends Partial<z.input<typeof schema>> {
export interface Config extends z.input<typeof schema> {
instrumentations?: Instrumentations
metrics?: Metrics
}
Expand All @@ -185,7 +193,7 @@ export interface ExtendedSwConfiguration extends SwConfiguration {
tracesEndpoint?: string
metricsEndpoint?: string
logsEndpoint?: string
authorization?: string
headers: Record<string, string>
}
dev: z.infer<typeof schema>["dev"]

Expand Down Expand Up @@ -246,27 +254,26 @@ export function readConfig():
otlp: {
tracesEndpoint:
otelEnv.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ??
otelEnv.OTEL_EXPORTER_OTLP_ENDPOINT ??
raw.collector?.replace(
/^apm\.collector\./,
"https://otel.collector.",
),
otelEnv.OTEL_EXPORTER_OTLP_ENDPOINT?.concat(ENDPOINTS.traces) ??
raw.collector
?.replace(/^apm\.collector\./, "https://otel.collector.")
.concat(ENDPOINTS.traces),
metricsEndpoint:
otelEnv.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT ??
otelEnv.OTEL_EXPORTER_OTLP_ENDPOINT ??
raw.collector?.replace(
/^apm\.collector\./,
"https://otel.collector.",
),
otelEnv.OTEL_EXPORTER_OTLP_ENDPOINT?.concat(ENDPOINTS.metrics) ??
raw.collector
?.replace(/^apm\.collector\./, "https://otel.collector.")
.concat(ENDPOINTS.metrics),
logsEndpoint:
otelEnv.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ??
otelEnv.OTEL_EXPORTER_OTLP_ENDPOINT ??
raw.collector?.replace(
/^apm\.collector\./,
"https://otel.collector.",
),
authorization:
raw.serviceKey?.token && `Bearer ${raw.serviceKey.token}`,
otelEnv.OTEL_EXPORTER_OTLP_ENDPOINT?.concat(ENDPOINTS.logs) ??
raw.collector
?.replace(/^apm\.collector\./, "https://otel.collector.")
.concat(ENDPOINTS.logs),

headers: raw.serviceKey?.token
? { authorization: `Bearer ${raw.serviceKey.token}` }
: {},
},
}

Expand Down
28 changes: 28 additions & 0 deletions packages/solarwinds-apm/src/exporters/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2023-2024 SolarWinds Worldwide, LLC.
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
http://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 { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto"

import { type ExtendedSwConfiguration } from "../config.js"

export class LogExporter extends OTLPLogExporter {
constructor(config: ExtendedSwConfiguration) {
super({
url: config.otlp.logsEndpoint,
headers: config.otlp.headers,
})
}
}
60 changes: 60 additions & 0 deletions packages/solarwinds-apm/src/exporters/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2023-2024 SolarWinds Worldwide, LLC.
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
http://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 { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto"
import {
Aggregation,
AggregationTemporality,
ExponentialHistogramAggregation,
InstrumentType,
} from "@opentelemetry/sdk-metrics"

import { type ExtendedSwConfiguration } from "../config.js"

export class MetricExporter extends OTLPMetricExporter {
constructor(config: ExtendedSwConfiguration) {
super({
url: config.otlp.metricsEndpoint,
headers: config.otlp.headers,
})
}

override selectAggregation(instrumentType: InstrumentType): Aggregation {
switch (instrumentType) {
case InstrumentType.HISTOGRAM: {
return new ExponentialHistogramAggregation(undefined, true)
}
default: {
return Aggregation.Default()
}
}
}

override selectAggregationTemporality(
instrumentType: InstrumentType,
): AggregationTemporality {
switch (instrumentType) {
case InstrumentType.COUNTER:
case InstrumentType.OBSERVABLE_COUNTER:
case InstrumentType.HISTOGRAM: {
return AggregationTemporality.DELTA
}
default: {
return super.selectAggregationTemporality(instrumentType)
}
}
}
}
28 changes: 28 additions & 0 deletions packages/solarwinds-apm/src/exporters/traces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2023-2024 SolarWinds Worldwide, LLC.
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
http://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 { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"

import { type ExtendedSwConfiguration } from "../config.js"

export class TraceExporter extends OTLPTraceExporter {
constructor(config: ExtendedSwConfiguration) {
super({
url: config.otlp.tracesEndpoint,
headers: config.otlp.headers,
})
}
}
41 changes: 6 additions & 35 deletions packages/solarwinds-apm/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import type { Metadata } from "@grpc/grpc-js"
import {
diag,
type DiagLogFunction,
Expand All @@ -25,7 +24,6 @@ import {
} from "@opentelemetry/api"
import { logs } from "@opentelemetry/api-logs"
import { CompositePropagator, W3CBaggagePropagator } from "@opentelemetry/core"
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc"
import {
type Instrumentation,
registerInstrumentations,
Expand Down Expand Up @@ -298,11 +296,8 @@ async function spanProcessors(
}

if (config.dev.otlpTraces) {
const { SwOtlpExporter } = await import("@solarwinds-apm/sdk/otlp-exporter")
const exporter = new SwOtlpExporter(config, {
url: config.otlp.tracesEndpoint,
metadata: await grpcMetadata(config),
})
const { TraceExporter } = await import("./exporters/traces.js")
const exporter = new TraceExporter(config)

const responseTimeProcessor = new sdk.SwResponseTimeProcessor(config)
const transactionNameProcessor = new sdk.SwTransactionNameProcessor()
Expand Down Expand Up @@ -339,13 +334,8 @@ async function metricReaders(
}

if (config.dev.otlpMetrics) {
const { SwOtlpMetricsExporter } = await import(
"@solarwinds-apm/sdk/otlp-metrics-exporter"
)
const exporter = new SwOtlpMetricsExporter({
url: config.otlp.tracesEndpoint,
metadata: await grpcMetadata(config),
})
const { MetricExporter } = await import("./exporters/metrics.js")
const exporter = new MetricExporter(config)
readers.push(
new PeriodicExportingMetricReader({
exporter,
Expand All @@ -360,27 +350,8 @@ async function metricReaders(
async function logRecordProcessors(
config: ExtendedSwConfiguration,
): Promise<LogRecordProcessor[]> {
return [
new BatchLogRecordProcessor(
new OTLPLogExporter({
url: config.otlp.logsEndpoint,
metadata: await grpcMetadata(config),
}),
),
]
}

export async function grpcMetadata(
config: ExtendedSwConfiguration,
): Promise<Metadata | undefined> {
if (!config.otlp.authorization) return

const { Metadata } = await import("@grpc/grpc-js")
const metadata = new Metadata()

metadata.set("authorization", config.otlp.authorization)

return metadata
const { LogExporter } = await import("./exporters/logs.js")
return [new BatchLogRecordProcessor(new LogExporter(config))]
}

// https://github.com/boostorg/log/blob/boost-1.82.0/include/boost/log/trivial.hpp#L42-L50
Expand Down
4 changes: 2 additions & 2 deletions packages/solarwinds-apm/src/sampling/grpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
import { type SwConfiguration } from "@solarwinds-apm/sdk"

import { Backoff } from "../backoff.js"
import { CoreSampler } from "./core.js"
import { Sampler } from "./sampler.js"

const CLIENT_VERSION = "2"

Expand All @@ -58,7 +58,7 @@ const TRIGGER_STRICT_BUCKET_RATE = "TriggerStrictBucketRate"
const SIGNATURE_KEY = "SignatureKey"

/** Sampler that retrieves settings from the SWO collector directly via gRPC */
export class GrpcSampler extends CoreSampler {
export class GrpcSampler extends Sampler {
readonly #key: string
readonly #address: URL
readonly #hostname = hostname()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { HEADERS_STORAGE } from "../propagation/headers.js"
* response headers setting. The only piece of logic left to implement
* by specific sampler is remote setting retrieval.
*/
export abstract class CoreSampler extends OboeSampler {
export abstract class Sampler extends OboeSampler {
readonly #tracingMode: TracingMode | undefined
readonly #triggerMode: boolean
readonly #transactionSettings: SwConfiguration["transactionSettings"]
Expand Down
Loading

0 comments on commit cadbcd6

Please sign in to comment.