Skip to content

Commit

Permalink
Test propagation of spans to injected not global TracerProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
odeke-em committed Sep 30, 2024
1 parent 0240b49 commit d0a338d
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 79 deletions.
2 changes: 1 addition & 1 deletion observability-test/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ describe('Database', () => {
database = new Database(INSTANCE, NAME, POOL_OPTIONS);
database.parent = INSTANCE;
database.databaseRole = 'parent_role';
database.observabilityConfig = {
database.observabilityOptions_ = {
tracerProvider: provider,
enableExtendedTracing: false,
};
Expand Down
151 changes: 148 additions & 3 deletions observability-test/spanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as assert from 'assert';
import {grpc} from 'google-gax';
import {google} from '../protos/protos';
import {Database, Spanner} from '../src';
import {Database, Instance, Spanner} from '../src';
import {MutationSet} from '../src/transaction';
import protobuf = google.spanner.v1;
import * as mock from '../test/mockserver/mockspanner';
Expand All @@ -35,6 +35,8 @@ const {
AsyncHooksContextManager,
} = require('@opentelemetry/context-async-hooks');

const {ObservabilityOptions} = require('../src/instrument');

/** A simple result set for SELECT 1. */
function createSelect1ResultSet(): protobuf.ResultSet {
const fields = [
Expand All @@ -60,7 +62,9 @@ interface setupResults {
spannerMock: mock.MockSpanner;
}

async function setup(): Promise<setupResults> {
async function setup(
observabilityOptions?: typeof ObservabilityOptions
): Promise<setupResults> {
const server = new grpc.Server();

const spannerMock = mock.createMockSpanner(server);
Expand Down Expand Up @@ -97,6 +101,7 @@ async function setup(): Promise<setupResults> {
servicePath: 'localhost',
port,
sslCreds: grpc.credentials.createInsecure(),
observabilityOptions: observabilityOptions,
});

return Promise.resolve({
Expand Down Expand Up @@ -149,7 +154,7 @@ describe('EndToEnd', () => {

const instance = spanner.instance('instance');
database = instance.database('database');
database.observabilityConfig = {
database.observabilityOptions_ = {
tracerProvider: provider,
enableExtendedTracing: false,
};
Expand Down Expand Up @@ -440,3 +445,143 @@ describe('EndToEnd', () => {
});
});
});

describe('ObservabilityOptions injection and propagation', async () => {
const globalTraceExporter = new InMemorySpanExporter();
const globalTracerProvider = new NodeTracerProvider({
sampler: new AlwaysOnSampler(),
exporter: globalTraceExporter,
});
globalTracerProvider.addSpanProcessor(
new SimpleSpanProcessor(globalTraceExporter)
);
globalTracerProvider.register();

const injectedTraceExporter = new InMemorySpanExporter();
const injectedTracerProvider = new NodeTracerProvider({
sampler: new AlwaysOnSampler(),
exporter: injectedTraceExporter,
});
injectedTracerProvider.addSpanProcessor(
new SimpleSpanProcessor(injectedTraceExporter)
);

const observabilityOptions: typeof ObservabilityOptions = {
tracerProvider: injectedTracerProvider,
enableExtendedTracing: true,
};

const setupResult = await setup(observabilityOptions);
const spanner = setupResult.spanner;
const server = setupResult.server;
const spannerMock = setupResult.spannerMock;

after(async () => {
globalTraceExporter.reset();
injectedTraceExporter.reset();
await globalTracerProvider.shutdown();
await injectedTracerProvider.shutdown();
spannerMock.resetRequests();
spanner.close();
server.tryShutdown(() => {});
});

it('Passed into Spanner, Instance and Database', done => {
// Ensure that the same observability configuration is set on the Spanner client.
assert.deepStrictEqual(spanner.observabilityOptions_, observabilityOptions);

// Acquire a handle to the Instance through spanner.instance.
const instanceByHandle = spanner.instance('instance');
assert.deepStrictEqual(
instanceByHandle.observabilityOptions_,
observabilityOptions
);

// Create the Instance by means of a constructor directly.
const instanceByConstructor = new Instance(spanner, 'myInstance');
assert.deepStrictEqual(
instanceByConstructor.observabilityOptions_,
observabilityOptions
);

// Acquire a handle to the Database through instance.database.
const databaseByHandle = instanceByHandle.database('database');
assert.deepStrictEqual(
databaseByHandle.observabilityOptions_,
observabilityOptions
);

// Create the Database by means of a constructor directly.
const databaseByConstructor = new Database(
instanceByConstructor,
'myDatabase'
);
assert.deepStrictEqual(
databaseByConstructor.observabilityOptions_,
observabilityOptions
);

done();
});

it('Propagates spans to the injected not global TracerProvider', done => {
const instance = spanner.instance('instance');
const database = instance.database('database');

database.run('SELECT 1', (err, rows) => {
assert.ifError(err);

injectedTraceExporter.forceFlush();
globalTraceExporter.forceFlush();
const spansFromInjected = injectedTraceExporter.getFinishedSpans();
const spansFromGlobal = globalTraceExporter.getFinishedSpans();

assert.strictEqual(
spansFromGlobal.length,
0,
'Expecting no spans from the global exporter'
);
assert.strictEqual(
spansFromInjected.length > 0,
true,
'Expecting spans from the injected exporter'
);

spansFromInjected.sort((spanA, spanB) => {
spanA.startTime < spanB.startTime;
});
const actualSpanNames: string[] = [];
const actualEventNames: string[] = [];
spansFromInjected.forEach(span => {
actualSpanNames.push(span.name);
span.events.forEach(event => {
actualEventNames.push(event.name);
});
});

const expectedSpanNames = [
'CloudSpanner.Database.runStream',
'CloudSpanner.Database.run',
];
assert.deepStrictEqual(
actualSpanNames,
expectedSpanNames,
`span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}`
);

const expectedEventNames = [
'Acquiring session',
'Waiting for a session to become available',
'Acquired session',
'Using Session',
];
assert.deepStrictEqual(
actualEventNames,
expectedEventNames,
`Unexpected events:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}`
);

done();
});
});
});
21 changes: 11 additions & 10 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ class Database extends common.GrpcServiceObject {

const sessions = (resp!.session || []).map(metadata => {
const session = this.session(metadata.name!);
session.observabilityConfig = this.observabilityConfig;
session.observabilityOptions_ = this.observabilityOptions_;
session.metadata = metadata;
return session;
});
Expand Down Expand Up @@ -738,6 +738,7 @@ class Database extends common.GrpcServiceObject {
const id = identifier.transaction;
const transaction = new BatchTransaction(session, options);
transaction.id = id;
transaction.observabilityOptions_ = this.observabilityOptions_;
transaction.readTimestamp = identifier.timestamp as PreciseDate;
return transaction;
}
Expand Down Expand Up @@ -827,7 +828,7 @@ class Database extends common.GrpcServiceObject {
? (optionsOrCallback as TimestampBounds)
: {};

const q = {opts: this.observabilityConfig};
const q = {opts: this.observabilityOptions_};
return startTrace('Database.createBatchTransaction', q, span => {
this.pool_.getSession((err, session) => {
if (err) {
Expand Down Expand Up @@ -1874,7 +1875,7 @@ class Database extends common.GrpcServiceObject {
delete (gaxOpts as GetSessionsOptions).pageToken;
}

const q = {opts: this.observabilityConfig};
const q = {opts: this.observabilityOptions_};
return startTrace('Database.getSessions', q, span => {
this.request<
google.spanner.v1.ISession,
Expand All @@ -1896,7 +1897,7 @@ class Database extends common.GrpcServiceObject {
sessionInstances = sessions.map(metadata => {
const session = self.session(metadata.name!);
session.metadata = metadata;
session.observabilityConfig = this.observabilityConfig;
session.observabilityOptions_ = this.observabilityOptions_;
return session;
});
}
Expand Down Expand Up @@ -2057,7 +2058,7 @@ class Database extends common.GrpcServiceObject {
? (optionsOrCallback as TimestampBounds)
: {};

const q = {opts: this.observabilityConfig};
const q = {opts: this.observabilityOptions_};
return startTrace('Database.getSnapshot', q, span => {
this.pool_.getSession((err, session) => {
if (err) {
Expand Down Expand Up @@ -2158,7 +2159,7 @@ class Database extends common.GrpcServiceObject {
? (optionsOrCallback as GetTransactionOptions)
: {};

const q = {opts: this.observabilityConfig};
const q = {opts: this.observabilityOptions_};
return startTrace('Database.getTransaction', q, span => {
this.pool_.getSession((err, session, transaction) => {
if (options.requestOptions) {
Expand Down Expand Up @@ -2785,7 +2786,7 @@ class Database extends common.GrpcServiceObject {
? (optionsOrCallback as TimestampBounds)
: {};

const q = {sql: query, opts: this.observabilityConfig};
const q = {sql: query, opts: this.observabilityOptions_};
return startTrace('Database.run', q, span => {
this.runStream(query, options)
.on('error', err => {
Expand Down Expand Up @@ -3006,7 +3007,7 @@ class Database extends common.GrpcServiceObject {
options?: TimestampBounds
): PartialResultStream {
const proxyStream: Transform = through.obj();
const q = {sql: query, opts: this.observabilityConfig};
const q = {sql: query, opts: this.observabilityOptions_};
return startTrace('Database.runStream', q, span => {
this.pool_.getSession((err, session) => {
if (err) {
Expand Down Expand Up @@ -3184,7 +3185,7 @@ class Database extends common.GrpcServiceObject {
? (optionsOrRunFn as RunTransactionOptions)
: {};

const q = {opts: this.observabilityConfig};
const q = {opts: this.observabilityOptions_};
startTrace('Database.runTransaction', q, span => {
this.pool_.getSession((err, session?, transaction?) => {
if (err) {
Expand Down Expand Up @@ -3577,7 +3578,7 @@ class Database extends common.GrpcServiceObject {
? (optionsOrCallback as CallOptions)
: {};

const q = {opts: this.observabilityConfig};
const q = {opts: this.observabilityOptions_};
return startTrace('Database.writeAtLeastOnce', q, span => {
this.pool_.getSession((err, session?, transaction?) => {
if (err && isSessionNotFoundError(err as grpc.ServiceError)) {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2057,3 +2057,4 @@ import IInstanceConfig = instanceAdmin.spanner.admin.instance.v1.IInstanceConfig
export {v1, protos};
export default {Spanner};
export {Float32, Float, Int, Struct, Numeric, PGNumeric, SpannerDate};
export {ObservabilityOptions};
66 changes: 1 addition & 65 deletions test/spanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5015,7 +5015,7 @@ describe('Spanner with mock server', () => {
const opts: typeof ObservabilityOptions = {tracerProvider: provider};
startTrace('aSpan', {opts: opts}, span => {
const database = newTestDatabase();
database.observabilityConfig = opts;
database.observabilityOptions_ = opts;

async function runIt() {
const query = {
Expand Down Expand Up @@ -5054,70 +5054,6 @@ describe('Spanner with mock server', () => {
);
});
});

it('Should use passed ObservabilityOptions in Spanner, Instance and Database', () => {
const exporter = new InMemorySpanExporter();
const provider = new NodeTracerProvider({
sampler: new AlwaysOnSampler(),
exporter: exporter,
});
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));

const observabilityOptions: typeof ObservabilityOptions = {
tracerProvider: provider,
enableExtendedTracing: true,
};
const spanner = new Spanner({
servicePath: 'localhost',
port,
sslCreds: grpc.credentials.createInsecure(),
observabilityOptions: observabilityOptions,
});

// Ensure that the same observability configuration is set on the Spanner client.
assert.deepStrictEqual(spanner.observabilityOptions_, observabilityOptions);

// Acquire a handle to the Instance through spanner.instance.
const instanceByHandle = spanner.instance('instance');
assert.deepStrictEqual(
instanceByHandle.observabilityOptions_,
observabilityOptions
);

// Create the Instance by means of a constructor directly.
const instanceByConstructor = new Instance(spanner, 'myInstance');
assert.deepStrictEqual(
instanceByConstructor.observabilityOptions_,
observabilityOptions
);

// Acquire a handle to the Database through instance.database.
const databaseByHandle = instanceByHandle.database('database');
assert.deepStrictEqual(
databaseByHandle.observabilityOptions_,
observabilityOptions
);

// Create the Database by means of a constructor directly.
const databaseByConstructor = new Database(
instanceByConstructor,
'myDatabase'
);
assert.deepStrictEqual(
databaseByConstructor.observabilityOptions_,
observabilityOptions
);

spanner.close();

/*
* TODO: Once we've merged in end-to-end Database tests,
* we shall add another test in which we:
* Register a global exporter
* Inject a different exporter via Spanner
* Verify that injected exporter is used and not global.
*/
});
});

function executeSimpleUpdate(
Expand Down

0 comments on commit d0a338d

Please sign in to comment.