Skip to content

Integration Spanner Extension

Aryeh Citron edited this page Apr 27, 2026 · 12 revisions

Track Google Cloud Spanner operations in your test diagrams using the TestTrackingDiagrams.Extensions.Spanner NuGet package. This extension supports two interception strategies: ADO.NET wrapping (for users of SpannerConnection) and gRPC client wrapping (for users of SpannerClient).

Using a shared library or abstraction layer? If your code doesn't use the Spanner SDK directly — e.g. it goes through a shared data-access library, wrapper, or custom abstraction — this extension won't be able to intercept the underlying calls. See Tracking Custom Dependencies for alternative approaches including RequestResponseLogger.LogPair(), TrackingProxy<T>, and MessageTracker.

Installation

dotnet add package TestTrackingDiagrams.Extensions.Spanner

How It Works

Google Cloud Spanner has two .NET client libraries:

  1. ADO.NET (Google.Cloud.Spanner.Data) — SpannerConnection, SpannerCommand, standard DbCommand.ExecuteReader() etc.
  2. Low-level gRPC (Google.Cloud.Spanner.V1) — SpannerClient, direct RPC calls like ExecuteSql, Read, Commit.

This extension provides wrappers for both:

  • TrackingSpannerConnection wraps DbConnection and intercepts all SQL commands
  • SpannerTracker provides direct LogRequest/LogResponse pairing for gRPC-style usage

Both use SpannerOperationClassifier to classify operations and SpannerTracker to log to RequestResponseLogger with RequestResponseMetaType.Event and DependencyCategory: "Database".


Supported Operations

Operation SQL Pattern gRPC Method
Query SELECT ... ExecuteSql, ExecuteStreamingSql
Read Read
StreamingRead StreamingRead
Insert INSERT INTO ...
Update UPDATE ...
Delete DELETE FROM ...
InsertOrUpdate Mutation-based
Replace Mutation-based
Commit Commit
Rollback Rollback
BeginTransaction BeginTransaction
BatchDml ExecuteBatchDml
PartitionQuery PartitionQuery
PartitionRead PartitionRead
Ddl CREATE TABLE, ALTER TABLE, DROP TABLE
CreateSession CreateSession, BatchCreateSessions
DeleteSession DeleteSession

Verbosity Levels

SpannerTrackingVerbosity.Summarised

  • Label: Short keyword (e.g. SELECT, INSERT, Commit)
  • Content: Omitted
  • URI: spanner:///TableName or spanner:///unknown

SpannerTrackingVerbosity.Detailed

  • Label: Operation with table name (e.g. SELECT FROM Users, INSERT INTO Orders)
  • Content: SQL text (if LogSqlText = true)
  • URI: spanner:///TableName

SpannerTrackingVerbosity.Raw

  • Label: Full SQL text
  • Content: SQL text + parameters (if LogParameters = true)
  • URI: spanner:///databaseId/TableName

Setup

Option A: ADO.NET Wrapping (SpannerConnection)

Wrap your SpannerConnection (or any DbConnection) with WithTestTracking():

using TestTrackingDiagrams.Extensions.Spanner;

var connection = new SpannerConnection(connectionString);
var trackingConnection = connection.WithTestTracking(new SpannerTrackingOptions
{
    ServiceName = "Spanner",
    CallingServiceName = "OrdersApi",
    CurrentTestInfoFetcher = () => (TestContext.Current!.Test.TestDisplayName, TestContext.Current.Test.UniqueID)
});

// Use trackingConnection in place of connection
using var cmd = trackingConnection.CreateCommand();
cmd.CommandText = "SELECT * FROM Users WHERE Id = @id";
// ...

The tracking connection creates TrackingSpannerCommand wrappers that intercept ExecuteReader, ExecuteNonQuery, and ExecuteScalar (both sync and async).

Option B: Direct Tracker Usage (gRPC / manual)

For low-level SpannerClient usage or manual logging:

using TestTrackingDiagrams.Extensions.Spanner;

var tracker = new SpannerTracker(new SpannerTrackingOptions
{
    ServiceName = "Spanner",
    CallingServiceName = "OrdersApi",
    CurrentTestInfoFetcher = () => (testName, testId)
});

// Classify and log
var op = SpannerOperationClassifier.ClassifyGrpc("ExecuteSql", "Users", databaseId);
var (reqId, traceId) = tracker.LogRequest(op, "SELECT * FROM Users");

// ... execute operation ...

tracker.LogResponse(op, reqId, traceId, "5 rows returned");

Option C: DI Registration

services.AddSpannerTestTracking(options =>
{
    options.ServiceName = "Spanner";
    options.CallingServiceName = "OrdersApi";
    options.CurrentTestInfoFetcher = () => (TestContext.Current!.Test.TestDisplayName, TestContext.Current.Test.UniqueID);
});

Then inject SpannerTracker where needed.


Configuration

SpannerTrackingOptions

Property Type Default Description
ServiceName string "Spanner" Display name in diagrams for the Spanner service
CallingServiceName string "Caller" Calling service name in diagrams
Verbosity SpannerTrackingVerbosity Detailed Verbosity level (Raw, Detailed, Summarised)
CurrentTestInfoFetcher Func<(string Name, string Id)>? null Required: provides test context for log correlation
CurrentStepTypeFetcher Func<string?>? null Optional — returns the current BDD step type
LogSqlText bool true Include SQL text in logged content (Detailed mode)
LogParameters bool false Include command parameters in logged content (Raw mode)
ExcludedOperations HashSet<SpannerOperation> [] Operations to skip (e.g. CreateSession, DeleteSession)
SetupVerbosity SpannerTrackingVerbosity? null Verbosity override for the Setup phase. See Phase-Aware Tracking
ActionVerbosity SpannerTrackingVerbosity? null Verbosity override for the Action phase. See Phase-Aware Tracking
TrackDuringSetup bool true When false, tracking is suppressed during Setup
TrackDuringAction bool true When false, tracking is suppressed during Action

Spanner vs Dapper Extension

If you already use the Integration Dapper Extension, it can also intercept SpannerConnection queries (since SpannerConnection is a DbConnection). The key difference:

Spanner Extension Dapper Extension
Classification Spanner-specific (Read, StreamingRead, Mutations, DDL, Partitioned ops) Generic SQL (SELECT, INSERT, UPDATE, DELETE)
gRPC support Yes (SpannerClient wrapping + classifier) No
Dependency category Database SQL
URI scheme spanner:/// sql:///

Use the Spanner extension when you want Spanner-specific operation visibility. Use the Dapper extension if you only need basic SQL classification.


ITrackingComponent

SpannerTracker and TrackingSpannerConnection both implement ITrackingComponent and auto-register:

  • ComponentName: "SpannerTracker ({ServiceName})"
  • WasInvoked: true after first operation
  • InvocationCount: Total operations logged

See Also

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally