Skip to content

Commit b195cf6

Browse files
mingjourneyhappyhuman
authored andcommitted
Integrate OpenTelemetry (OTel) support: 1. define utils under the /telemetry folder
otel-utils-7
1 parent 620bd06 commit b195cf6

File tree

8 files changed

+1104
-0
lines changed

8 files changed

+1104
-0
lines changed

core/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,22 @@
4343
},
4444
"peerDependencies": {
4545
"@google-cloud/storage": "^7.17.1",
46+
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
47+
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
4648
"@google/genai": "1.14.0",
4749
"@modelcontextprotocol/sdk": "1.17.5",
4850
"@opentelemetry/api": "1.9.0",
51+
"@opentelemetry/api-logs": "^0.205.0",
52+
"@opentelemetry/exporter-logs-otlp-http": "^0.205.0",
53+
"@opentelemetry/exporter-metrics-otlp-http": "^0.205.0",
54+
"@opentelemetry/exporter-trace-otlp-http": "^0.205.0",
55+
"@opentelemetry/resource-detector-gcp": "^0.40.0",
56+
"@opentelemetry/resources": "^2.1.0",
57+
"@opentelemetry/sdk-logs": "^0.205.0",
58+
"@opentelemetry/sdk-metrics": "^2.1.0",
59+
"@opentelemetry/sdk-trace-base": "^2.1.0",
60+
"@opentelemetry/sdk-trace-node": "^2.1.0",
61+
"google-auth-library": "^10.3.0",
4962
"zod": "3.25.76"
5063
}
5164
}

core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ export * from './tools/mcp/mcp_session_manager.js';
99
export * from './tools/mcp/mcp_tool.js';
1010
export * from './tools/mcp/mcp_toolset.js';
1111
export * from './artifacts/gcs_artifact_service.js';
12+
export * from './telemetry/setup.js';
13+
export * from './telemetry/google_cloud.js';

core/src/telemetry/google_cloud.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {GoogleAuth} from 'google-auth-library';
8+
import {PeriodicExportingMetricReader} from '@opentelemetry/sdk-metrics';
9+
import {detectResources, Resource} from '@opentelemetry/resources';
10+
import {gcpDetector} from '@opentelemetry/resource-detector-gcp';
11+
import {TraceExporter} from '@google-cloud/opentelemetry-cloud-trace-exporter';
12+
import {BatchSpanProcessor} from '@opentelemetry/sdk-trace-base';
13+
import {MetricExporter} from '@google-cloud/opentelemetry-cloud-monitoring-exporter';
14+
15+
import {logger} from '../utils/logger.js';
16+
17+
import {OtelExportersConfig, OTelHooks} from './setup.js';
18+
19+
const GCP_PROJECT_ERROR_MESSAGE =
20+
'Cannot determine GCP Project. OTel GCP Exporters cannot be set up. ' +
21+
'Please make sure to log into correct GCP Project.';
22+
23+
async function getGcpProjectId(): Promise<string | undefined> {
24+
try {
25+
const auth = new GoogleAuth();
26+
const projectId = await auth.getProjectId();
27+
return projectId || undefined;
28+
} catch (error) {
29+
return undefined;
30+
}
31+
}
32+
33+
export async function getGcpExporters(config: OtelExportersConfig = {}): Promise<OTelHooks> {
34+
const {
35+
enableTracing = false,
36+
enableMetrics = false,
37+
// enableCloudLogging = false,
38+
} = config;
39+
40+
const projectId = await getGcpProjectId();
41+
if (!projectId) {
42+
logger.warn(GCP_PROJECT_ERROR_MESSAGE);
43+
return {};
44+
}
45+
46+
return {
47+
spanProcessors: enableTracing ? [
48+
new BatchSpanProcessor(new TraceExporter({ projectId })),
49+
] : [],
50+
metricReaders: enableMetrics ? [
51+
new PeriodicExportingMetricReader({
52+
exporter: new MetricExporter({ projectId }),
53+
exportIntervalMillis: 5000,
54+
}),
55+
] : [],
56+
logRecordProcessors: [],
57+
};
58+
}
59+
60+
export function getGcpResource(): Resource {
61+
return detectResources({ detectors: [gcpDetector] });
62+
}

core/src/telemetry/setup.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {trace, metrics} from '@opentelemetry/api';
8+
import {logs} from '@opentelemetry/api-logs';
9+
import {LoggerProvider, LogRecordProcessor, BatchLogRecordProcessor} from '@opentelemetry/sdk-logs';
10+
import {MetricReader, MeterProvider, PeriodicExportingMetricReader} from '@opentelemetry/sdk-metrics';
11+
import {detectResources, Resource} from '@opentelemetry/resources';
12+
import {SpanProcessor, BatchSpanProcessor} from '@opentelemetry/sdk-trace-base';
13+
import {NodeTracerProvider} from '@opentelemetry/sdk-trace-node';
14+
import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-http';
15+
import {OTLPMetricExporter} from '@opentelemetry/exporter-metrics-otlp-http';
16+
import {OTLPLogExporter} from '@opentelemetry/exporter-logs-otlp-http';
17+
18+
export interface OtelExportersConfig {
19+
enableTracing?: boolean;
20+
enableMetrics?: boolean;
21+
enableLogging?: boolean;
22+
}
23+
24+
/**
25+
* Configuration hooks for OpenTelemetry setup.
26+
*
27+
* This interface defines the structure for configuring OpenTelemetry
28+
* components including span processors, metric readers, and log record processors.
29+
*/
30+
export interface OTelHooks {
31+
spanProcessors?: SpanProcessor[];
32+
metricReaders?: MetricReader[];
33+
logRecordProcessors?: LogRecordProcessor[];
34+
}
35+
36+
/**
37+
* Sets up OTel providers if hooks for a given telemetry type were passed.
38+
*
39+
* Additionally adds generic OTLP exporters based on following env variables:
40+
* OTEL_EXPORTER_OTLP_ENDPOINT
41+
* OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
42+
* OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
43+
* OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
44+
* See https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
45+
* for how they are used.
46+
*
47+
* If a provider for a specific telemetry type was already globally set -
48+
* this function will not override it or register more exporters.
49+
*
50+
* @experimental (Experimental, subject to change)
51+
*
52+
* @param otelHooksToSetup per-telemetry-type processors and readers to be added
53+
* to OTel providers. If no hooks for a specific telemetry type are passed -
54+
* provider will not be set.
55+
* @param otelResource OTel resource to use in providers.
56+
* If empty - default OTel resource detection will be used.
57+
*/
58+
export function maybeSetOtelProviders(
59+
otelHooksToSetup: OTelHooks[] = [],
60+
otelResource?: Resource
61+
): void {
62+
const resource = otelResource || getOtelResource();
63+
const allHooks = [...otelHooksToSetup, getOtelExporters()];
64+
const spanProcessors = allHooks.flatMap(hooks => hooks.spanProcessors || []);
65+
const metricReaders = allHooks.flatMap(hooks => hooks.metricReaders || []);
66+
const logRecordProcessors = allHooks.flatMap(hooks => hooks.logRecordProcessors || []);
67+
68+
if (spanProcessors.length > 0) {
69+
const tracerProvider = new NodeTracerProvider({
70+
resource,
71+
spanProcessors
72+
});
73+
tracerProvider.register();
74+
trace.setGlobalTracerProvider(tracerProvider);
75+
}
76+
77+
if (metricReaders.length > 0) {
78+
const meterProvider = new MeterProvider({
79+
readers: metricReaders,
80+
resource,
81+
});
82+
metrics.setGlobalMeterProvider(meterProvider);
83+
}
84+
85+
if (logRecordProcessors.length > 0) {
86+
const loggerProvider = new LoggerProvider({
87+
resource,
88+
processors: logRecordProcessors,
89+
});
90+
// logs is experimental, reference to https://open-telemetry.github.io/opentelemetry-js/modules/_opentelemetry_api-logs.html#alpha-software---use-at-your-own-risk
91+
logs.setGlobalLoggerProvider(loggerProvider);
92+
}
93+
}
94+
95+
/**
96+
* Gets the OTel resource with environment variable detection.
97+
*
98+
* The resource detection populates resource labels from
99+
* environment variables like OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES.
100+
*
101+
* @returns A Resource object with detected attributes
102+
*/
103+
function getOtelResource(): Resource {
104+
return detectResources({
105+
detectors: [],
106+
});
107+
}
108+
109+
/**
110+
* Gets OTel exporters configuration based on environment variables.
111+
*
112+
* @returns OtelExportersConfig with flags based on environment variables
113+
*/
114+
function getOtelExportersConfig(): OtelExportersConfig {
115+
return {
116+
enableTracing: !!(process.env.OTEL_EXPORTER_OTLP_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT),
117+
enableMetrics: !!(process.env.OTEL_EXPORTER_OTLP_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT),
118+
enableLogging: !!(process.env.OTEL_EXPORTER_OTLP_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT),
119+
};
120+
}
121+
122+
/**
123+
* Gets OTel exporters based on configuration.
124+
*
125+
* @param config Configuration for which exporters to enable
126+
* @returns OTelHooks containing configured exporters
127+
*/
128+
function getOtelExporters(config = getOtelExportersConfig()): OTelHooks {
129+
const { enableTracing, enableMetrics, enableLogging } = config;
130+
return {
131+
spanProcessors: enableTracing ? [new BatchSpanProcessor(new OTLPTraceExporter())] : [],
132+
metricReaders: enableMetrics ? [new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter() })] : [],
133+
logRecordProcessors: enableLogging ? [new BatchLogRecordProcessor(new OTLPLogExporter())] : [],
134+
};
135+
}

0 commit comments

Comments
 (0)