Skip to content

Commit 1ddb62e

Browse files
authored
feat: Enable tracing via OpenTelemetry. (#2218)
* feat: Enable tracing via OpenTelemetry. * Better comment for the tracerProvider setting. * Address feedback. * Add an event for logical termination and add events to tests. * Address API naming feedback. * Remove the trace event that's currently not in Firestore prod.
1 parent ba8ce46 commit 1ddb62e

File tree

9 files changed

+225
-81
lines changed

9 files changed

+225
-81
lines changed

dev/src/index.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -792,8 +792,7 @@ export class Firestore implements firestore.Firestore {
792792
}
793793

794794
private newTraceUtilInstance(settings: firestore.Settings): TraceUtil {
795-
// Take the tracing option from the settings.
796-
let createEnabledInstance = settings.openTelemetryOptions?.enableTracing;
795+
let createEnabledInstance = true;
797796

798797
// The environment variable can override options to enable/disable telemetry collection.
799798
if ('FIRESTORE_ENABLE_TRACING' in process.env) {

dev/src/reference/query-util.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export class QueryUtil<
180180
const tag = requestTag();
181181
const startTime = Date.now();
182182
const isExplain = explainOptions !== undefined;
183+
const methodName = 'runQuery';
183184

184185
let numDocumentsReceived = 0;
185186
let lastReceivedDocument: QueryDocumentSnapshot<
@@ -245,6 +246,11 @@ export class QueryUtil<
245246

246247
if (proto.done) {
247248
logger('QueryUtil._stream', tag, 'Trigger Logical Termination.');
249+
this._firestore._traceUtil
250+
.currentSpan()
251+
.addEvent(
252+
`Firestore.${methodName}: Received RunQueryResponse.Done.`
253+
);
248254
backendStream.unpipe(stream);
249255
backendStream.resume();
250256
backendStream.end();
@@ -265,7 +271,6 @@ export class QueryUtil<
265271
let streamActive: Deferred<boolean>;
266272
do {
267273
streamActive = new Deferred<boolean>();
268-
const methodName = 'runQuery';
269274

270275
this._firestore._traceUtil
271276
.currentSpan()

dev/src/telemetry/disabled-trace-util.ts

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
import {Attributes, TraceUtil} from './trace-util';
1717
import {Span} from './span';
1818

19+
/**
20+
* @private
21+
* @internal
22+
*/
1923
export class DisabledTraceUtil implements TraceUtil {
2024
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2125
startSpan(name: string): Span {

dev/src/telemetry/enabled-trace-util.ts

+23-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
trace,
2323
Tracer,
2424
Span as OpenTelemetrySpan,
25+
TracerProvider,
2526
} from '@opentelemetry/api';
2627

2728
import {Span} from './span';
@@ -33,22 +34,40 @@ import {DEFAULT_DATABASE_ID} from '../path';
3334
import {DEFAULT_MAX_IDLE_CHANNELS} from '../index';
3435
const serviceConfig = interfaces['google.firestore.v1.Firestore'];
3536

37+
/**
38+
* @private
39+
* @internal
40+
*/
3641
export class EnabledTraceUtil implements TraceUtil {
3742
private tracer: Tracer;
3843
private settingsAttributes: Attributes;
3944

45+
// Visible for testing
46+
tracerProvider: TracerProvider;
47+
4048
constructor(settings: Settings) {
41-
let tracerProvider = settings.openTelemetryOptions?.tracerProvider;
49+
let provider: TracerProvider | undefined =
50+
settings.openTelemetry?.tracerProvider;
4251

4352
// If a TracerProvider has not been given to us, we try to use the global one.
44-
if (!tracerProvider) {
53+
if (!provider) {
4554
const {trace} = require('@opentelemetry/api');
46-
tracerProvider = trace.getTracerProvider();
55+
provider = trace.getTracerProvider();
4756
}
4857

58+
// At this point provider is guaranteed to be defined because
59+
// `trace.getTracerProvider()` does not return null or undefined.
60+
this.tracerProvider = provider!;
61+
4962
const libVersion = require('../../../package.json').version;
5063
const libName = require('../../../package.json').name;
51-
this.tracer = tracerProvider.getTracer(libName, libVersion);
64+
try {
65+
this.tracer = this.tracerProvider.getTracer(libName, libVersion);
66+
} catch (e) {
67+
throw new Error(
68+
"The object provided for 'tracerProvider' does not conform to the TracerProvider interface."
69+
);
70+
}
5271

5372
this.settingsAttributes = {};
5473
this.settingsAttributes['otel.scope.name'] = libName;

dev/src/telemetry/span.ts

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
import {Span as OpenTelemetrySpan} from '@opentelemetry/api';
1818
import {Attributes} from './trace-util';
1919

20+
/**
21+
* @private
22+
* @internal
23+
*/
2024
export class Span {
2125
constructor(private span?: OpenTelemetrySpan) {}
2226

dev/src/telemetry/trace-util.ts

+13
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,18 @@
1616

1717
import {Span} from './span';
1818

19+
/**
20+
* @private
21+
* @internal
22+
*/
1923
export interface Attributes {
2024
[attributeKey: string]: AttributeValue | undefined;
2125
}
26+
27+
/**
28+
* @private
29+
* @internal
30+
*/
2231
export declare type AttributeValue =
2332
| string
2433
| number
@@ -67,6 +76,10 @@ export const ATTRIBUTE_KEY_TRANSACTION_TYPE = 'transaction_type';
6776
export const ATTRIBUTE_KEY_ATTEMPTS_ALLOWED = 'attempts_allowed';
6877
export const ATTRIBUTE_KEY_ATTEMPTS_REMAINING = 'attempts_remaining';
6978

79+
/**
80+
* @private
81+
* @internal
82+
*/
7083
export interface TraceUtil {
7184
startActiveSpan<F extends (span: Span) => unknown>(
7285
name: string,

dev/system-test/tracing.ts

+48-11
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
Context as OpenTelemetryContext,
3131
} from '@opentelemetry/api';
3232
import {TraceExporter} from '@google-cloud/opentelemetry-cloud-trace-exporter';
33-
import {Settings} from '@google-cloud/firestore';
33+
import {FirestoreOpenTelemetryOptions, Settings} from '@google-cloud/firestore';
3434
import {
3535
AlwaysOnSampler,
3636
BatchSpanProcessor,
@@ -96,13 +96,6 @@ setLogFunction((msg: string) => {
9696
console.log(`LOG: ${msg}`);
9797
});
9898

99-
// TODO(tracing): This should be moved to firestore.d.ts when we want to
100-
// release the feature.
101-
export interface FirestoreOpenTelemetryOptions {
102-
enableTracing?: boolean;
103-
tracerProvider?: any;
104-
}
105-
10699
interface TestConfig {
107100
// In-Memory tests check trace correctness by inspecting traces in memory by
108101
// utilizing InMemorySpanExporter. These tests have `e2e` set to `false`.
@@ -192,7 +185,6 @@ describe('Tracing Tests', () => {
192185
tracerProvider: TracerProvider
193186
): FirestoreOpenTelemetryOptions {
194187
const options: FirestoreOpenTelemetryOptions = {
195-
enableTracing: true,
196188
tracerProvider: undefined,
197189
};
198190

@@ -285,7 +277,7 @@ describe('Tracing Tests', () => {
285277

286278
const settings: Settings = {
287279
preferRest: testConfig.preferRest,
288-
openTelemetryOptions: getOpenTelemetryOptions(tracerProvider),
280+
openTelemetry: getOpenTelemetryOptions(tracerProvider),
289281
};
290282

291283
// Named-database tests use an environment variable to specify the database ID. Add it to the settings.
@@ -660,7 +652,7 @@ describe('Tracing Tests', () => {
660652

661653
// Expect that the span exists first.
662654
const span = getSpanByName(spanName);
663-
expect(span).to.not.be.null;
655+
expect(span, `Could not find the span named ${spanName}`).to.not.be.null;
664656

665657
// Assert that the expected attributes are present in the span attributes.
666658
// Note that the span attributes may be a superset of the attributes passed
@@ -672,6 +664,29 @@ describe('Tracing Tests', () => {
672664
}
673665
}
674666

667+
// Ensures that the given span exists and has the given attributes.
668+
function expectSpanHasEvents(spanName: string, eventNames: string[]): void {
669+
// The Cloud Trace API does not return span attributes and events.
670+
if (testConfig.e2e) {
671+
return;
672+
}
673+
674+
// Expect that the span exists first.
675+
const span = getSpanByName(spanName);
676+
expect(span, `Could not find the span named ${spanName}`).to.not.be.null;
677+
678+
// Assert that the expected attributes are present in the span attributes.
679+
// Note that the span attributes may be a superset of the attributes passed
680+
// to this function.
681+
if (span?.events) {
682+
const numEvents = eventNames.length;
683+
expect(numEvents).to.equal(span.events.length);
684+
for (let i = 0; i < numEvents; ++i) {
685+
expect(span.events[i].name).to.equal(eventNames[i]);
686+
}
687+
}
688+
}
689+
675690
describe(IN_MEMORY_TEST_SUITE_TITLE, () => {
676691
describe(NON_GLOBAL_OTEL_TEST_SUITE_TITLE, () => {
677692
describe(GRPC_TEST_SUITE_TITLE, () => {
@@ -753,6 +768,11 @@ describe('Tracing Tests', () => {
753768
SPAN_NAME_DOC_REF_GET,
754769
SPAN_NAME_BATCH_GET_DOCUMENTS
755770
);
771+
expectSpanHasEvents(SPAN_NAME_BATCH_GET_DOCUMENTS, [
772+
'Firestore.batchGetDocuments: Start',
773+
'Firestore.batchGetDocuments: First response received',
774+
'Firestore.batchGetDocuments: Completed',
775+
]);
756776
});
757777

758778
it('document reference create()', async () => {
@@ -820,6 +840,11 @@ describe('Tracing Tests', () => {
820840
);
821841
await waitForCompletedSpans(2);
822842
expectSpanHierarchy(SPAN_NAME_TEST_ROOT, SPAN_NAME_AGGREGATION_QUERY_GET);
843+
expectSpanHasEvents(SPAN_NAME_AGGREGATION_QUERY_GET, [
844+
'Firestore.runAggregationQuery: Start',
845+
'Firestore.runAggregationQuery: First response received',
846+
'Firestore.runAggregationQuery: Completed',
847+
]);
823848
});
824849

825850
it('collection reference add()', async () => {
@@ -852,6 +877,12 @@ describe('Tracing Tests', () => {
852877
);
853878
await waitForCompletedSpans(2);
854879
expectSpanHierarchy(SPAN_NAME_TEST_ROOT, SPAN_NAME_QUERY_GET);
880+
expectSpanHasEvents(SPAN_NAME_QUERY_GET, [
881+
'RunQuery',
882+
'Firestore.runQuery: Start',
883+
'Firestore.runQuery: First response received',
884+
'Firestore.runQuery: Completed',
885+
]);
855886
});
856887

857888
it('firestore getAll()', async () => {
@@ -862,6 +893,11 @@ describe('Tracing Tests', () => {
862893
);
863894
await waitForCompletedSpans(2);
864895
expectSpanHierarchy(SPAN_NAME_TEST_ROOT, SPAN_NAME_BATCH_GET_DOCUMENTS);
896+
expectSpanHasEvents(SPAN_NAME_BATCH_GET_DOCUMENTS, [
897+
'Firestore.batchGetDocuments: Start',
898+
'Firestore.batchGetDocuments: First response received',
899+
'Firestore.batchGetDocuments: Completed',
900+
]);
865901
});
866902

867903
it('transaction', async () => {
@@ -920,6 +956,7 @@ describe('Tracing Tests', () => {
920956
await runFirestoreOperationInRootSpan(async () => {
921957
const query = firestore.collectionGroup('foo');
922958
let numPartitions = 0;
959+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
923960
for await (const partition of query.getPartitions(3)) {
924961
numPartitions++;
925962
}

0 commit comments

Comments
 (0)