Skip to content
Open
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
67 changes: 38 additions & 29 deletions src/api/resolvers/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
// import prisInstru from '@prisma/instrumentation';
import { Resource } from '@opentelemetry/resources';
import { configPrivate } from '$config/private';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { trace } from '@opentelemetry/api';
import type { Plugin } from 'graphql-yoga';

import {
SimpleSpanProcessor,
BatchSpanProcessor,
Expand Down Expand Up @@ -37,48 +35,59 @@ class PrettyConsoleSpanExporter implements SpanExporter {
}
}

const headers: Record<string, string> = {};
// Create fallback console exporter
const consoleExporter = new PrettyConsoleSpanExporter();
const fallbackProcessor = new SimpleSpanProcessor(consoleExporter);

if (configPrivate.OTEL_AUTHORIZATION_HEADER) {
headers.authorization = configPrivate.OTEL_AUTHORIZATION_HEADER;
}
let activeProcessor: SpanProcessor | undefined;

const exporter = configPrivate.OTEL_ENDPOINT_URL
? new OTLPTraceExporter({
try {
if (configPrivate.OTEL_ENDPOINT_URL) {
const otlpExporter = new OTLPTraceExporter({
url: configPrivate.OTEL_ENDPOINT_URL,
headers
})
: undefined;
headers: configPrivate.OTEL_AUTHORIZATION_HEADER
? { authorization: configPrivate.OTEL_AUTHORIZATION_HEADER }
: {},
timeoutMillis: 5000, // 5 second timeout
concurrencyLimit: 10 // Limit concurrent exports
});

const processors: SpanProcessor[] = [];
if (exporter) {
if (configPrivate.NODE_ENV !== 'production') {
processors.push(new SimpleSpanProcessor(exporter));
// Configure processor based on environment
activeProcessor =
configPrivate.NODE_ENV === 'production'
? new BatchSpanProcessor(otlpExporter, {
maxQueueSize: 2000,
scheduledDelayMillis: 5000,
exportTimeoutMillis: 5000
})
: new SimpleSpanProcessor(otlpExporter);
} else {
processors.push(new BatchSpanProcessor(exporter));
console.info('No OTEL exporter configured, using console logging fallback');
activeProcessor = fallbackProcessor;
}
} else {
console.info('No OTEL exporter configured, using console.');
const consoleExporter = new PrettyConsoleSpanExporter();
processors.push(new SimpleSpanProcessor(consoleExporter));
} catch (error) {
console.warn('Failed to initialize OTLP exporter, using console logging fallback:', error);
activeProcessor = fallbackProcessor;
}

const provider = new NodeTracerProvider({
spanProcessors: processors,
resource: new Resource({
[ATTR_SERVICE_NAME]: configPrivate.OTEL_SERVICE_NAME,
[ATTR_SERVICE_VERSION]:
configPrivate.OTEL_SERVICE_VERSION ?? configPublic.PUBLIC_VERSION ?? 'unknown'
[ATTR_SERVICE_VERSION]: configPrivate.OTEL_SERVICE_VERSION ?? configPublic.PUBLIC_VERSION ?? 'unknown'
})
});

// Always add fallback processor first
provider.addSpanProcessor(fallbackProcessor);

// Add OTLP processor if available and different from fallback
if (activeProcessor && activeProcessor !== fallbackProcessor) {
provider.addSpanProcessor(activeProcessor);
}
Comment on lines +80 to +86
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Always-on console fallback can leak PII and flood logs.

PrettyConsoleSpanExporter logs user email and GraphQL args. Because the fallback processor is added unconditionally, this will emit in production even when OTLP is healthy, which is a privacy/compliance and log-volume risk. Consider gating the console fallback to non-production or an explicit flag, or redact sensitive fields.

🔧 Suggested guard to limit console fallback in production
-// Always add fallback processor first
-provider.addSpanProcessor(fallbackProcessor);
+// Add console fallback only when OTLP isn't active (or outside production)
+const enableConsoleFallback =
+  !activeProcessor || activeProcessor === fallbackProcessor || configPrivate.NODE_ENV !== 'production';
+if (enableConsoleFallback) {
+  provider.addSpanProcessor(fallbackProcessor);
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Always add fallback processor first
provider.addSpanProcessor(fallbackProcessor);
// Add OTLP processor if available and different from fallback
if (activeProcessor && activeProcessor !== fallbackProcessor) {
provider.addSpanProcessor(activeProcessor);
}
// Add console fallback only when OTLP isn't active (or outside production)
const enableConsoleFallback =
!activeProcessor || activeProcessor === fallbackProcessor || configPrivate.NODE_ENV !== 'production';
if (enableConsoleFallback) {
provider.addSpanProcessor(fallbackProcessor);
}
// Add OTLP processor if available and different from fallback
if (activeProcessor && activeProcessor !== fallbackProcessor) {
provider.addSpanProcessor(activeProcessor);
}
🤖 Prompt for AI Agents
In `@src/api/resolvers/tracer.ts` around lines 80 - 86, The console fallback
(fallbackProcessor / PrettyConsoleSpanExporter) is always added via
provider.addSpanProcessor which can leak PII and flood production logs; change
the logic in tracer setup to only register the fallbackProcessor when running in
non-production or when an explicit config flag is enabled (e.g.,
process.env.NODE_ENV !== 'production' || config.enableConsoleExporter) and/or
apply a sanitization/redaction step to span attributes/GraphQL args before they
reach PrettyConsoleSpanExporter; update the conditional around
provider.addSpanProcessor(fallbackProcessor) and ensure activeProcessor logic
(activeProcessor !== fallbackProcessor) still applies.


registerInstrumentations({
tracerProvider: provider,
instrumentations: [
new HttpInstrumentation()
// new prisInstru.PrismaInstrumentation({
// middleware: true
// })
]
instrumentations: [new HttpInstrumentation()]
});

provider.register();
Expand Down
Loading