Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use HTTP exporters for OTLP #421

Merged
merged 4 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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