Skip to content

Instantly share code, notes, and snippets.

@sgtsquiggs
Last active October 1, 2025 19:51
Show Gist options
  • Save sgtsquiggs/60ef8945b3793dd64e8586d13ffdae2e to your computer and use it in GitHub Desktop.
Save sgtsquiggs/60ef8945b3793dd64e8586d13ffdae2e to your computer and use it in GitHub Desktop.
DataDog tracing interceptor for @connectrpc/connect
import type { Span } from 'dd-trace';
import { type DescMethod, type DescService } from '@bufbuild/protobuf';
import {
Code, ConnectError, createContextKey, type Interceptor,
} from '@connectrpc/connect';
import ddtracer from 'dd-trace';
import * as opentracing from 'opentracing';
const getMethodMetadata = (service: DescService, method: DescMethod) => {
const tags = {
kind: '',
name: '',
package: '',
path: `/${service.typeName}/${method.name}`,
service: '',
};
switch (method.methodKind) {
case 'bidi_streaming':
tags.kind = 'bidi';
break;
case 'client_streaming':
tags.kind = 'clientStream';
break;
case 'server_streaming':
tags.kind = 'serverStream';
break;
case 'unary':
tags.kind = 'unary';
break;
default:
break;
}
const serviceParts = service.typeName.split('.');
tags.name = method.name;
tags.service = serviceParts.pop() || '';
tags.package = serviceParts.join('.');
return tags;
};
const addCode = (span: Span, code: Code | string) => {
if (typeof code === 'string') {
span.setTag('grpc.status.code', code);
return;
}
const codeName = Code[code];
span.setTag('grpc.status.code', codeName.charAt(0).toUpperCase() + codeName.slice(1));
};
const metadataFilter = (key: string) => {
if (['content-type', 'grpc-accept-encoding', 'te'].includes(key)) {
return false;
}
if (key.startsWith('x-envoy')) {
return false;
}
return true;
};
const addMetadataTags = (span: Span, metadata: Headers, type: string) => {
if (typeof metadata.forEach !== 'function') return;
metadata.forEach((value, key) => {
if (metadataFilter(key)) {
span.setTag(`grpc.${type}.metadata.${key}`, value);
}
});
};
const inject = (span: Span, metadata: Headers) => {
if (typeof metadata.set !== 'function') return;
const carrier: Record<string, string> = {};
ddtracer.inject(span, opentracing.FORMAT_TEXT_MAP, carrier);
Object.keys(carrier).forEach((key) => {
metadata.set(key, carrier[key]);
});
};
export const SpanContextKey = createContextKey<Span | undefined>(undefined);
export const tracer: Interceptor = (next) => async (req) =>
ddtracer.trace('grpc.client', async (span) => {
try {
const method = getMethodMetadata(req.service, req.method);
span.addTags({
component: 'grpc',
'grpc.method.kind': method.kind,
'grpc.method.name': method.name,
'grpc.method.package': method.package,
'grpc.method.path': method.path,
'grpc.method.service': method.service,
'grpc.status.code': 0,
'resource.name': method.path,
'span.kind': 'client',
'span.type': 'http',
});
if (method.service && method.package) {
span.setTag('rpc.service', `${method.package}.${method.service}`);
}
addMetadataTags(span, req.header, 'request');
inject(span, req.header);
req.contextValues.set(SpanContextKey, span);
const res = await next(req);
addCode(span, 'OK');
addMetadataTags(span, res.header, 'response');
return res;
} catch (err) {
if (err instanceof ConnectError) {
addCode(span, err.code);
span.setTag('error', err);
addMetadataTags(span, err.metadata, 'response');
}
throw err;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment