This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 75f2ae953 refactor(go/adbc): refactor logging instrumentation into
OTel tracing - part 2/3 (#4659)
75f2ae953 is described below
commit 75f2ae9532e297667d424f8b2332524c7c2a0afd
Author: Bruce Irschick <[email protected]>
AuthorDate: Mon Aug 24 16:44:01 2026 -0700
refactor(go/adbc): refactor logging instrumentation into OTel tracing -
part 2/3 (#4659)
This pull request refactors the FlightSQL ADBC driver to improve error
handling, tracing, and code clarity. The main focus is on replacing
logger-based tracing with OpenTelemetry (OTel) tracing, enhancing
context propagation, and ensuring errors are consistently handled and
returned. Several methods now include OTel tracing spans, and error
handling is standardized across the connection implementation.
**Tracing and Observability Improvements**
- Replaced logger-based tracing (`doGetWithLogger`) with OTel-based
tracing (`doGetWithTracer`) throughout the connection implementation,
ensuring all relevant methods now emit OTel spans for better
observability.
- Added OTel tracing spans to methods such as `PrepareDriverInfo`,
`GetObjectsCatalogs`, `GetObjectsDbSchemas`, `GetObjectsTables`, and
`GetTableSchema`, capturing start time, errors, and context for each
operation.
**Error Handling and API Consistency**
- Standardized error handling by consistently capturing and returning
errors, including context and metadata, across all public methods. This
includes propagating errors from context checks and response metadata.
- Updated function signatures to use named return values and to properly
set and return errors, improving clarity and reliability.
**Code Cleanup and Minor Fixes**
- Removed unused imports (such as `log/slog`) and the obsolete
`doGetWithLogger` function.
- Improved variable declarations and error assignments for better Go
idiomatic style and code readability.
**Test and Output Updates**
- Updated test assertions to match new trace output naming conventions,
reflecting the refactored tracing approach.
**Documentation and Comments**
- Added or updated comments to clarify the purpose of new and refactored
functions, particularly around tracing and error handling.
These changes collectively improve the maintainability, observability,
and robustness of the FlightSQL ADBC driver.Refactors logging
instrumentation into OTel tracing
---
- Integrate `otelgrpc` tracing
- updates for flightsql connection
- handle correct structured shutdown of database and tracing in correct
order (`tracingLifecycle`)
- ensures tracing provider not leaked on failed driver/database startup
Extends: https://github.com/apache/arrow-adbc/pull/4655
Is part 2 of 3 changes.
---------
Co-authored-by: Bruce Irschick (Bit Quill Technologies Inc)
<[email protected]>
---
go/adbc/driver/flightsql/flightsql_adbc_test.go | 4 +-
go/adbc/driver/flightsql/flightsql_connection.go | 279 +++++++++++------------
go/adbc/driver/flightsql/flightsql_database.go | 222 ++++++++++--------
go/adbc/driver/flightsql/flightsql_driver.go | 12 +-
go/adbc/driver/flightsql/flightsql_statement.go | 6 +-
go/adbc/driver/flightsql/flightsql_tracing.go | 41 ++++
go/adbc/driver/flightsql/logging.go | 115 ----------
go/adbc/driver/flightsql/tracing_test.go | 4 +-
go/adbc/go.mod | 1 +
go/adbc/go.sum | 2 +
10 files changed, 328 insertions(+), 358 deletions(-)
diff --git a/go/adbc/driver/flightsql/flightsql_adbc_test.go
b/go/adbc/driver/flightsql/flightsql_adbc_test.go
index 2be9496e9..79d318b10 100644
--- a/go/adbc/driver/flightsql/flightsql_adbc_test.go
+++ b/go/adbc/driver/flightsql/flightsql_adbc_test.go
@@ -368,8 +368,8 @@ func TestFlightSQLTracingProducesTraceFiles(t *testing.T) {
}
output := traceOutput.String()
- require.Contains(t, output, "FlightSQLDatabase.Open")
- require.Contains(t, output, "FlightSQLStatement.ExecuteQuery")
+ require.Contains(t, output, "FlightSQL.Database.Open")
+ require.Contains(t, output, "FlightSQL.Statement.ExecuteQuery")
}
// Run the test suite, but validating that a header set on the database is
ALWAYS passed
diff --git a/go/adbc/driver/flightsql/flightsql_connection.go
b/go/adbc/driver/flightsql/flightsql_connection.go
index 858b9f40d..ffacba5da 100644
--- a/go/adbc/driver/flightsql/flightsql_connection.go
+++ b/go/adbc/driver/flightsql/flightsql_connection.go
@@ -23,7 +23,6 @@ import (
"encoding/json"
"fmt"
"io"
- "log/slog"
"math"
"strings"
"time"
@@ -234,6 +233,8 @@ var adbcToFlightSQLInfo =
map[adbc.InfoCode]flightsql.SqlInfo{
adbc.InfoVendorSubstraitMaxVersion:
flightsql.SqlInfoFlightSqlServerSubstraitMaxVersion,
}
+// doGetWithTracer performs DoGet against an endpoint's locations, tracing each
+// attempt and joining all per-location failures into the returned error.
func doGetWithResponseMetadata(ctx context.Context, client *flightsql.Client,
ticket *flight.Ticket, opts ...grpc.CallOption) (*flight.Reader, error) {
var header, trailer metadata.MD
callOpts := append(append([]grpc.CallOption{}, opts...),
grpc.Header(&header), grpc.Trailer(&trailer))
@@ -381,95 +382,6 @@ func doGetWithTracer(ctx context.Context, cl
*flightsql.Client, endpoint *flight
return nil, err
}
-// doGetWithLogger performs DoGet against an endpoint's locations, logging each
-// attempt and joining all per-location failures into the returned error so the
-// caller can see every location that was tried. logger may be nil.
-func doGetWithLogger(ctx context.Context, cl *flightsql.Client, endpoint
*flight.FlightEndpoint, clientCache gcache.Cache, logger *slog.Logger, opts
...grpc.CallOption) (rdr *flight.Reader, err error) {
- log := safeLogger(logger)
- if len(endpoint.Location) == 0 {
- log.DebugContext(ctx, "FlightSQL doGet",
- "phase", "noLocations",
- )
- start := time.Now()
- rdr, err = cl.DoGet(ctx, endpoint.Ticket, opts...)
- log.DebugContext(ctx, "FlightSQL doGet",
- "phase", "defaultClientResult",
- "duration", time.Since(start),
- "err", err,
- )
- return rdr, err
- }
-
- var (
- cc interface{}
- hasFallback bool
- attemptErrors []string
- )
-
- for _, loc := range endpoint.Location {
- if loc.Uri == flight.LocationReuseConnection {
- hasFallback = true
- continue
- }
-
- start := time.Now()
- cc, err = clientCache.Get(loc.Uri)
- if err != nil {
- attemptErrors = append(attemptErrors,
fmt.Sprintf("clientCache.Get(%q): %s", loc.Uri, err.Error()))
- log.WarnContext(ctx, "FlightSQL doGet location attempt
failed",
- "phase", "clientCacheGet",
- "location", loc.Uri,
- "duration", time.Since(start),
- "err", err,
- )
- continue
- }
-
- conn := cc.(*flightsql.Client)
- rdr, err = conn.DoGet(ctx, endpoint.Ticket, opts...)
- if err != nil {
- attemptErrors = append(attemptErrors,
fmt.Sprintf("DoGet(%q): %s", loc.Uri, err.Error()))
- log.WarnContext(ctx, "FlightSQL doGet location attempt
failed",
- "phase", "doGet",
- "location", loc.Uri,
- "duration", time.Since(start),
- "err", err,
- )
- continue
- }
-
- log.DebugContext(ctx, "FlightSQL doGet succeeded",
- "location", loc.Uri,
- "duration", time.Since(start),
- )
- return
- }
-
- if hasFallback {
- start := time.Now()
- rdr, err = cl.DoGet(ctx, endpoint.Ticket, opts...)
- if err != nil {
- attemptErrors = append(attemptErrors,
fmt.Sprintf("DoGet(fallback to default client): %s", err.Error()))
- log.WarnContext(ctx, "FlightSQL doGet fallback to
default client failed",
- "duration", time.Since(start),
- "err", err,
- )
- return nil, fmt.Errorf("all DoGet attempts failed: %s;
final: %w", strings.Join(attemptErrors, "; "), err)
- }
- log.DebugContext(ctx, "FlightSQL doGet succeeded via default
client fallback",
- "duration", time.Since(start),
- )
- return rdr, nil
- }
-
- if err != nil && len(attemptErrors) > 1 {
- err = fmt.Errorf("all %d DoGet location(s) failed: %s; final:
%w",
- len(attemptErrors), strings.Join(attemptErrors, "; "),
err)
- }
-
- return nil, err
-}
-
func (c *connectionImpl) getSessionOptions(ctx context.Context)
(map[string]interface{}, error) {
ctx = metadata.NewOutgoingContext(ctx, c.hdrs)
var header, trailer metadata.MD
@@ -818,7 +730,17 @@ func (c *connectionImpl) SetOptionDouble(key string, value
float64) error {
return c.ConnectionImplBase.SetOptionDouble(key, value)
}
-func (c *connectionImpl) PrepareDriverInfo(ctx context.Context, infoCodes
[]adbc.InfoCode) error {
+func (c *connectionImpl) PrepareDriverInfo(ctx context.Context, infoCodes
[]adbc.InfoCode) (err error) {
+ startTime := time.Now()
+ const spanName = "FlightSQL.Connection.PrepareDriverInfo"
+ ctx, span := internal.StartSpan(ctx, spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
driverInfo := c.DriverInfo
if len(infoCodes) == 0 {
@@ -839,7 +761,8 @@ func (c *connectionImpl) PrepareDriverInfo(ctx
context.Context, infoCodes []adbc
ctx = metadata.NewOutgoingContext(ctx, c.hdrs)
var header, trailer metadata.MD
- info, err := c.cl.GetSqlInfo(ctx, translated, grpc.Header(&header),
grpc.Trailer(&trailer), c.timeouts)
+ var info *flight.FlightInfo
+ info, err = c.cl.GetSqlInfo(ctx, translated, grpc.Header(&header),
grpc.Trailer(&trailer), c.timeouts)
// Just return local driver info if GetSqlInfo hasn't been implemented
on the server
if grpcstatus.Code(err) == grpccodes.Unimplemented {
@@ -852,10 +775,12 @@ func (c *connectionImpl) PrepareDriverInfo(ctx
context.Context, infoCodes []adbc
// No error, go get the SqlInfo from the server
for i, endpoint := range info.Endpoint {
- var header, trailer metadata.MD
- rdr, err := doGetWithLogger(ctx, c.cl, endpoint, c.clientCache,
c.Logger, grpc.Header(&header), grpc.Trailer(&trailer), c.timeouts)
+ var responseMetadata *responseMetadataCollector
+ ctx, responseMetadata = withResponseMetadata(ctx)
+ var rdr *flight.Reader
+ rdr, err = doGetWithTracer(ctx, c.cl, endpoint, c.clientCache,
c, c.timeouts)
if err != nil {
- return adbcFromFlightStatusWithDetails(err, header,
trailer, "GetInfo(DoGet): endpoint %d: %s", i, endpoint.Location)
+ return adbcFromFlightStatusWithDetails(err,
responseMetadata.snapshot(), nil, "GetInfo(DoGet): endpoint %d: %s", i,
endpoint.Location)
}
for rdr.Next() {
@@ -895,14 +820,14 @@ func (c *connectionImpl) PrepareDriverInfo(ctx
context.Context, infoCodes []adbc
}
}
- if err :=
driverInfo.RegisterInfoCode(adbcInfoCode, v); err != nil {
+ if err =
driverInfo.RegisterInfoCode(adbcInfoCode, v); err != nil {
return err
}
}
}
- if err := checkContext(rdr.Err(), ctx); err != nil {
- return adbcFromFlightStatusWithDetails(err, header,
trailer, "GetInfo(DoGet): endpoint %d: %s", i, endpoint.Location)
+ if err = checkContext(rdr.Err(), ctx); err != nil {
+ return adbcFromFlightStatusWithDetails(err,
responseMetadata.snapshot(), nil, "GetInfo(DoGet): endpoint %d: %s", i,
endpoint.Location)
}
}
@@ -918,7 +843,7 @@ func (c *connectionImpl) readInfo(ctx context.Context,
expectedSchema *arrow.Sch
info: info,
clientCache: c.clientCache,
bufferSize: 5,
- logger: c.Logger,
+ tracing: c,
}, opts...)
if err != nil {
return nil, adbcFromFlightStatus(err, "DoGet")
@@ -934,7 +859,16 @@ func (c *connectionImpl) readInfo(ctx context.Context,
expectedSchema *arrow.Sch
return rdr, nil
}
-func (c *connectionImpl) GetObjectsCatalogs(ctx context.Context, catalog
*string) ([]string, error) {
+func (c *connectionImpl) GetObjectsCatalogs(ctx context.Context, catalog
*string) (catalogs []string, err error) {
+ const spanName = "FlightSQL.Connection.GetObjectsCatalogs"
+ startTime := time.Now()
+ ctx, span := internal.StartSpan(ctx, spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
var (
header, trailer metadata.MD
numCatalogs int64
@@ -952,13 +886,14 @@ func (c *connectionImpl) GetObjectsCatalogs(ctx
context.Context, catalog *string
header = metadata.MD{}
trailer = metadata.MD{}
- rdr, err := c.readInfo(ctx, schema_ref.Catalogs, info, c.timeouts,
grpc.Header(&header), grpc.Trailer(&trailer))
+ var rdr array.RecordReader
+ rdr, err = c.readInfo(ctx, schema_ref.Catalogs, info, c.timeouts,
grpc.Header(&header), grpc.Trailer(&trailer))
if err != nil {
return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetObjects(GetCatalogs)")
}
defer rdr.Release()
- catalogs := make([]string, 0, numCatalogs)
+ catalogs = make([]string, 0, numCatalogs)
for rdr.Next() {
arr := rdr.RecordBatch().Column(0).(*array.String)
for i := 0; i < arr.Len(); i++ {
@@ -968,7 +903,7 @@ func (c *connectionImpl) GetObjectsCatalogs(ctx
context.Context, catalog *string
}
}
- if err := checkContext(rdr.Err(), ctx); err != nil {
+ if err = checkContext(rdr.Err(), ctx); err != nil {
return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetObjects(GetCatalogs)")
}
@@ -977,6 +912,15 @@ func (c *connectionImpl) GetObjectsCatalogs(ctx
context.Context, catalog *string
// Helper function to build up a map of catalogs to DB schemas
func (c *connectionImpl) GetObjectsDbSchemas(ctx context.Context, depth
adbc.ObjectDepth, catalog *string, dbSchema *string) (result
map[string][]string, err error) {
+ const spanName = "FlightSQL.Connection.GetObjectsDbSchemas"
+ startTime := time.Now()
+ ctx, span := internal.StartSpan(ctx, spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
if depth == adbc.ObjectDepthCatalogs {
return
}
@@ -984,14 +928,16 @@ func (c *connectionImpl) GetObjectsDbSchemas(ctx
context.Context, depth adbc.Obj
result = make(map[string][]string)
var header, trailer metadata.MD
// Pre-populate the map of which schemas are in which catalogs
- info, err := c.cl.GetDBSchemas(ctx,
&flightsql.GetDBSchemasOpts{DbSchemaFilterPattern: dbSchema},
grpc.Header(&header), grpc.Trailer(&trailer), c.timeouts)
+ var info *flight.FlightInfo
+ info, err = c.cl.GetDBSchemas(ctx,
&flightsql.GetDBSchemasOpts{DbSchemaFilterPattern: dbSchema},
grpc.Header(&header), grpc.Trailer(&trailer), c.timeouts)
if err != nil {
return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetObjects(GetDBSchemas)")
}
header = metadata.MD{}
trailer = metadata.MD{}
- rdr, err := c.readInfo(ctx, schema_ref.DBSchemas, info, c.timeouts,
grpc.Header(&header), grpc.Trailer(&trailer))
+ var rdr array.RecordReader
+ rdr, err = c.readInfo(ctx, schema_ref.DBSchemas, info, c.timeouts,
grpc.Header(&header), grpc.Trailer(&trailer))
if err != nil {
return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetObjects(GetDBSchemas)")
}
@@ -1019,6 +965,16 @@ func (c *connectionImpl) GetObjectsDbSchemas(ctx
context.Context, depth adbc.Obj
}
func (c *connectionImpl) GetObjectsTables(ctx context.Context, depth
adbc.ObjectDepth, catalog *string, dbSchema *string, tableName *string,
columnName *string, tableType []string) (result internal.SchemaToTableInfo, err
error) {
+ const spanName = "FlightSQL.Connection.GetObjectsTables"
+ startTime := time.Now()
+ ctx, span := internal.StartSpan(ctx, spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
if depth == adbc.ObjectDepthCatalogs || depth ==
adbc.ObjectDepthDBSchemas {
return
}
@@ -1028,7 +984,8 @@ func (c *connectionImpl) GetObjectsTables(ctx
context.Context, depth adbc.Object
// Pre-populate the map of which schemas are in which catalogs
includeSchema := depth == adbc.ObjectDepthAll || depth ==
adbc.ObjectDepthColumns
var header, trailer metadata.MD
- info, err := c.cl.GetTables(ctx, &flightsql.GetTablesOpts{
+ var info *flight.FlightInfo
+ info, err = c.cl.GetTables(ctx, &flightsql.GetTablesOpts{
DbSchemaFilterPattern: dbSchema,
TableNameFilterPattern: tableName,
TableTypes: tableType,
@@ -1044,7 +1001,8 @@ func (c *connectionImpl) GetObjectsTables(ctx
context.Context, depth adbc.Object
}
header = metadata.MD{}
trailer = metadata.MD{}
- rdr, err := c.readInfo(ctx, expectedSchema, info, c.timeouts,
grpc.Header(&header), grpc.Trailer(&trailer))
+ var rdr array.RecordReader
+ rdr, err = c.readInfo(ctx, expectedSchema, info, c.timeouts,
grpc.Header(&header), grpc.Trailer(&trailer))
if err != nil {
return nil, adbcFromFlightStatus(err, "GetObjects(GetTables)")
}
@@ -1074,7 +1032,8 @@ func (c *connectionImpl) GetObjectsTables(ctx
context.Context, depth adbc.Object
var schema *arrow.Schema
if includeSchema {
- reader, err :=
ipc.NewReader(bytes.NewReader(rdr.RecordBatch().Column(4).(*array.Binary).Value(i)))
+ var reader *ipc.Reader
+ reader, err =
ipc.NewReader(bytes.NewReader(rdr.RecordBatch().Column(4).(*array.Binary).Value(i)))
if err != nil {
return nil, adbc.Error{
Msg: err.Error(),
@@ -1093,13 +1052,23 @@ func (c *connectionImpl) GetObjectsTables(ctx
context.Context, depth adbc.Object
}
}
- if err := checkContext(rdr.Err(), ctx); err != nil {
+ if err = checkContext(rdr.Err(), ctx); err != nil {
return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetObjects(GetTables)")
}
return
}
-func (c *connectionImpl) GetTableSchema(ctx context.Context, catalog *string,
dbSchema *string, tableName string) (*arrow.Schema, error) {
+func (c *connectionImpl) GetTableSchema(ctx context.Context, catalog *string,
dbSchema *string, tableName string) (schema *arrow.Schema, err error) {
+ const spanName = "FlightSQL.Connection.GetTableSchema"
+ startTime := time.Now()
+ ctx, span := internal.StartSpan(ctx, spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
opts := &flightsql.GetTablesOpts{
Catalog: catalog,
DbSchemaFilterPattern: dbSchema,
@@ -1109,20 +1078,22 @@ func (c *connectionImpl) GetTableSchema(ctx
context.Context, catalog *string, db
ctx = metadata.NewOutgoingContext(ctx, c.hdrs)
var header, trailer metadata.MD
- info, err := c.cl.GetTables(ctx, opts, c.timeouts,
grpc.Header(&header), grpc.Trailer(&trailer))
+ var info *flight.FlightInfo
+ info, err = c.cl.GetTables(ctx, opts, c.timeouts, grpc.Header(&header),
grpc.Trailer(&trailer))
if err != nil {
return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetTableSchema(GetTables)")
}
- header = metadata.MD{}
- trailer = metadata.MD{}
- rdr, err := doGetWithLogger(ctx, c.cl, info.Endpoint[0], c.clientCache,
c.Logger, c.timeouts, grpc.Header(&header), grpc.Trailer(&trailer))
+ ctx, responseMetadata := withResponseMetadata(ctx)
+ var rdr *flight.Reader
+ rdr, err = doGetWithTracer(ctx, c.cl, info.Endpoint[0], c.clientCache,
c, c.timeouts)
if err != nil {
- return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetTableSchema(DoGet)")
+ return nil, adbcFromFlightStatusWithDetails(err,
responseMetadata.snapshot(), nil, "GetTableSchema(DoGet)")
}
defer rdr.Release()
- rec, err := rdr.Read()
+ var rec arrow.RecordBatch
+ rec, err = rdr.Read()
if err != nil {
if err == io.EOF {
return nil, adbc.Error{
@@ -1130,7 +1101,7 @@ func (c *connectionImpl) GetTableSchema(ctx
context.Context, catalog *string, db
Code: adbc.StatusNotFound,
}
}
- return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetTableSchema(DoGet)")
+ return nil, adbcFromFlightStatusWithDetails(err,
responseMetadata.snapshot(), nil, "GetTableSchema(DoGet)")
}
numRows := rec.NumRows()
@@ -1178,22 +1149,34 @@ func (c *connectionImpl) GetTableSchema(ctx
context.Context, catalog *string, db
// Field Name | Field Type
// ----------------|--------------
// table_type | utf8 not null
-func (c *connectionImpl) GetTableTypes(ctx context.Context)
(array.RecordReader, error) {
+func (c *connectionImpl) GetTableTypes(ctx context.Context) (reader
array.RecordReader, err error) {
+ const spanName = "FlightSQL.Connection.GetTableTypes"
+ startTime := time.Now()
+ ctx, span := internal.StartSpan(ctx, spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
ctx = metadata.NewOutgoingContext(ctx, c.hdrs)
var header, trailer metadata.MD
- info, err := c.cl.GetTableTypes(ctx, c.timeouts, grpc.Header(&header),
grpc.Trailer(&trailer))
+ var info *flight.FlightInfo
+ info, err = c.cl.GetTableTypes(ctx, c.timeouts, grpc.Header(&header),
grpc.Trailer(&trailer))
if err != nil {
return nil, adbcFromFlightStatusWithDetails(err, header,
trailer, "GetTableTypes")
}
- return newRecordReader(ctx, recordReaderConfig{
+ reader, err = newRecordReader(ctx, recordReaderConfig{
alloc: c.db.Alloc,
cl: c.cl,
info: info,
clientCache: c.clientCache,
bufferSize: 5,
- logger: c.Logger,
+ tracing: c,
})
+ return reader, err
}
// Commit commits any pending transactions on this connection, it should
@@ -1344,7 +1327,17 @@ func (c *connectionImpl) prepareSubstrait(ctx
context.Context, plan flightsql.Su
}
// Close closes this connection and releases any associated resources.
-func (c *connectionImpl) Close() error {
+func (c *connectionImpl) Close() (err error) {
+ const spanName = "FlightSQL.Connection.Close"
+ startTime := time.Now()
+ ctx, span := internal.StartSpan(context.Background(), spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
if c.cl == nil {
return adbc.Error{
Msg: "[Flight SQL Connection] trying to close already
closed connection",
@@ -1352,27 +1345,23 @@ func (c *connectionImpl) Close() error {
}
}
- closeStart := time.Now()
// Snapshot fields before tearing down c.cl; log "closing" and
// "closed" separately so a hung CloseSession is still visible.
- logger := safeLogger(c.Logger)
connID := c.id
openedAt := c.openedAt
+ span.AddEvent("closing",
trace.WithAttributes(attribute.String("connection_id", connID)))
- logger.Info("FlightSQL connection closing",
- "connection_id", connID,
- )
-
- ctx := metadata.NewOutgoingContext(context.Background(), c.hdrs)
+ ctx = metadata.NewOutgoingContext(ctx, c.hdrs)
var header, trailer metadata.MD
- _, err := c.cl.CloseSession(ctx, &flight.CloseSessionRequest{},
grpc.Header(&header), grpc.Trailer(&trailer), c.timeouts)
+ _, err = c.cl.CloseSession(ctx, &flight.CloseSessionRequest{},
grpc.Header(&header), grpc.Trailer(&trailer), c.timeouts)
if err != nil {
grpcStatus := grpcstatus.Convert(err)
// Ignore unimplemented
if grpcStatus.Code() != grpccodes.Unimplemented {
// Ignore the error since server may not support it and
may not properly return UNIMPLEMENTED
//
TODO(https://github.com/apache/arrow-adbc/issues/1243): log a proper warning
- c.db.Logger.Debug("failed to close session", "error",
err.Error())
+ // Note: this does not set the status to error. It just
records the error as an event in the span.
+ span.RecordError(err)
}
}
@@ -1380,22 +1369,18 @@ func (c *connectionImpl) Close() error {
err = c.cl.Close()
c.cl = nil
- args := []any{
- "connection_id", connID,
- "close_duration", time.Since(closeStart),
+ args := []attribute.KeyValue{
+ attribute.String("connection_id", connID),
}
if !openedAt.IsZero() {
- args = append(args, "lifetime", time.Since(openedAt))
+ args = append(args, attribute.Float64("lifetime_s",
time.Since(openedAt).Seconds()))
}
if err != nil {
- args = append(args, "err", err)
- args = append(args, grpcStatusAttrs(err)...)
- logger.Info("FlightSQL connection closed with error", args...)
- } else {
- logger.Info("FlightSQL connection closed", args...)
+ args = append(args, grpcStatusKeyValues(err)...)
}
+ span.AddEvent("closed", trace.WithAttributes(args...))
- return adbcFromFlightStatus(err, "Close")
+ return adbcFromFlightStatus(err, spanName)
}
// ReadPartition constructs a statement for a partition of a query. The
@@ -1403,6 +1388,16 @@ func (c *connectionImpl) Close() error {
//
// A partition can be retrieved by using ExecutePartitions on a statement.
func (c *connectionImpl) ReadPartition(ctx context.Context,
serializedPartition []byte) (rdr array.RecordReader, err error) {
+ const spanName = "FlightSQL.Connection.ReadPartition"
+ startTime := time.Now()
+ ctx, span := internal.StartSpan(ctx, spanName, c)
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
var info flight.FlightInfo
if err := proto.Unmarshal(serializedPartition, &info); err != nil {
return nil, adbc.Error{
@@ -1420,7 +1415,7 @@ func (c *connectionImpl) ReadPartition(ctx
context.Context, serializedPartition
}
ctx = metadata.NewOutgoingContext(ctx, c.hdrs)
- rdr, err = doGetWithLogger(ctx, c.cl, info.Endpoint[0], c.clientCache,
c.Logger, c.timeouts)
+ rdr, err = doGetWithTracer(ctx, c.cl, info.Endpoint[0], c.clientCache,
c, c.timeouts)
if err != nil {
return nil, adbcFromFlightStatus(err, "ReadPartition(DoGet)")
}
diff --git a/go/adbc/driver/flightsql/flightsql_database.go
b/go/adbc/driver/flightsql/flightsql_database.go
index 0ead6422c..1a07c9f50 100644
--- a/go/adbc/driver/flightsql/flightsql_database.go
+++ b/go/adbc/driver/flightsql/flightsql_database.go
@@ -21,8 +21,8 @@ import (
"context"
"crypto/tls"
"crypto/x509"
+ "errors"
"fmt"
- "log/slog"
"net/url"
"strconv"
"strings"
@@ -36,6 +36,8 @@ import (
"github.com/apache/arrow-go/v18/arrow/flight"
"github.com/apache/arrow-go/v18/arrow/flight/flightsql"
"github.com/bluele/gcache"
+
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
+ "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@@ -369,33 +371,38 @@ func (d *databaseImpl) SetOptionDouble(key string, value
float64) error {
return d.DatabaseImplBase.SetOptionDouble(key, value)
}
-func (d *databaseImpl) Close() error {
- if d.Logger != nil {
- d.Logger.Info("FlightSQL database closed",
- "target", d.uri.String(),
- )
- }
- return d.DatabaseImplBase.Close()
+func (d *databaseImpl) Close() (err error) {
+ const spanName = "FlightSQL.Database.Close"
+ startTime := time.Now()
+ var span trace.Span
+ _, span = internal.StartSpan(context.Background(), spanName, d)
+
+ span.AddEvent("closing",
trace.WithAttributes(attribute.String("target", d.uri.String())))
+ flushErr := d.ForceFlushTracing(context.Background())
+ internal.NewEndSpanHelper(span).
+ WithError(flushErr).
+ WithStartTime(startTime).
+ EndSpan()
+ shutdownErr := d.DatabaseImplBase.Close()
+ return errors.Join(flushErr, shutdownErr)
}
-func getFlightClient(ctx context.Context, loc string, d *databaseImpl,
authMiddle *bearerAuthMiddleware, cookies flight.CookieMiddleware)
(*flightsql.Client, error) {
+func getFlightClient(ctx context.Context, loc string, d *databaseImpl,
authMiddle *bearerAuthMiddleware, cookies flight.CookieMiddleware, span
trace.Span) (client *flightsql.Client, err error) {
middleware := []flight.ClientMiddleware{
- {
- Unary: makeUnaryLoggingInterceptor(d.Logger),
- Stream: makeStreamLoggingInterceptor(d.Logger),
- },
flight.CreateClientMiddleware(authMiddle),
{
Unary: unaryTimeoutInterceptor,
Stream: streamTimeoutInterceptor,
},
+ {Stream: responseMetadataStreamInterceptor},
}
if d.enableCookies {
middleware = append(middleware,
flight.CreateClientMiddleware(cookies))
}
- uri, err := url.Parse(loc)
+ var uri *url.URL
+ uri, err = url.Parse(loc)
if err != nil {
return nil, adbc.Error{Msg: fmt.Sprintf("Invalid URI '%s': %s",
loc, err), Code: adbc.StatusInvalidArgument}
}
@@ -436,15 +443,24 @@ func getFlightClient(ctx context.Context, loc string, d
*databaseImpl, authMiddl
dv, _ := d.DriverInfo.GetInfoForInfoCode(adbc.InfoDriverVersion)
driverVersion := dv.(string)
- dialOpts := append(d.dialOpts.opts,
grpc.WithConnectParams(d.timeout.connectParams()),
grpc.WithTransportCredentials(creds), grpc.WithUserAgent("ADBC Flight SQL
Driver "+driverVersion))
+ dialOpts := append(d.dialOpts.opts,
+ grpc.WithStatsHandler(otelgrpc.NewClientHandler(
+ otelgrpc.WithTracerProvider(d.GetTracerProvider()),
+ )),
+ grpc.WithConnectParams(d.timeout.connectParams()),
+ grpc.WithTransportCredentials(creds),
+ grpc.WithUserAgent("ADBC Flight SQL Driver "+driverVersion),
+ )
dialOpts = append(dialOpts, d.userDialOpts...)
if d.oauthToken != nil {
dialOpts = append(dialOpts,
grpc.WithPerRPCCredentials(d.oauthToken))
}
- d.Logger.DebugContext(ctx, "new client", "location", loc)
- cl, err := flightsql.NewClient(target, nil, middleware, dialOpts...)
+ span.AddEvent("flight.client.connecting", trace.WithAttributes(
+ attribute.String("flight.location", loc),
+ ))
+ client, err = flightsql.NewClient(target, nil, middleware, dialOpts...)
if err != nil {
return nil, adbc.Error{
Msg: err.Error(),
@@ -452,34 +468,35 @@ func getFlightClient(ctx context.Context, loc string, d
*databaseImpl, authMiddl
}
}
- cl.Alloc = d.Alloc
+ client.Alloc = d.Alloc
// Authorization header is already set, continue
if len(authMiddle.hdrs.Get("authorization")) > 0 {
- d.Logger.DebugContext(ctx, "reusing auth token", "location",
loc)
- return cl, nil
+ span.AddEvent("flight.auth.token.reused", trace.WithAttributes(
+ attribute.String("flight.location", loc),
+ ))
+ return client, nil
}
var authValue string
if d.user != "" || d.pass != "" {
authStart := time.Now()
- d.Logger.InfoContext(ctx, "FlightSQL basic auth started",
- "target", loc,
- "user", d.user,
- )
+ span.AddEvent("flight.auth.basic.started", trace.WithAttributes(
+ attribute.String("target", loc),
+ attribute.String("user", d.user),
+ ))
var header, trailer metadata.MD
- ctx, err = cl.Client.AuthenticateBasicToken(ctx, d.user,
d.pass, grpc.Header(&header), grpc.Trailer(&trailer), d.timeout)
+ ctx, err = client.Client.AuthenticateBasicToken(ctx, d.user,
d.pass, grpc.Header(&header), grpc.Trailer(&trailer), d.timeout)
if err != nil {
- args := []any{
- "target", loc,
- "user", d.user,
- "duration", time.Since(authStart),
- "err", err,
+ args := []attribute.KeyValue{
+ attribute.String("target", loc),
+ attribute.String("user", d.user),
+ attribute.Float64("duration_s",
time.Since(authStart).Seconds()),
}
- args = append(args, correlationHeaderAttrs(header)...)
- args = append(args, correlationHeaderAttrs(trailer)...)
- args = append(args, grpcStatusAttrs(err)...)
- d.Logger.InfoContext(ctx, "FlightSQL basic auth
failed", args...)
+ args = append(args,
correlationHeaderKeyValues(header)...)
+ args = append(args,
correlationHeaderKeyValues(trailer)...)
+ args = append(args, grpcStatusKeyValues(err)...)
+ span.SetAttributes(args...)
return nil, adbcFromFlightStatusWithDetails(err,
header, trailer, "AuthenticateBasicToken")
}
@@ -487,29 +504,47 @@ func getFlightClient(ctx context.Context, loc string, d
*databaseImpl, authMiddl
authValue = md.Get("Authorization")[0]
}
- d.Logger.InfoContext(ctx, "FlightSQL basic auth succeeded",
- "target", loc,
- "user", d.user,
- "duration", time.Since(authStart),
- "token_length", len(authValue),
- )
+ span.AddEvent("flight.auth.basic.completed",
trace.WithAttributes(
+ attribute.String("target", loc),
+ attribute.String("user", d.user),
+ attribute.String("duration_s",
time.Since(authStart).String()),
+ attribute.Int("token_length", len(authValue)),
+ ))
}
if authValue != "" {
- authMiddle.SetHeader(authValue)
+ authMiddle.SetHeader(authValue, span)
}
- return cl, nil
+ return client, nil
}
type support struct {
transactions bool
}
+func closeCachedFlightClient(d *databaseImpl, location, client interface{},
reason string) {
+ startTime := time.Now()
+ var err error
+ _, span := internal.StartSpan(context.Background(),
"FlightSQL.Database.CloseCachedClient", d,
+ trace.WithAttributes(
+ attribute.String("flight.location",
fmt.Sprint(location)),
+ attribute.String("flight.cache.reason", reason),
+ ))
+ defer func() {
+ internal.NewEndSpanHelper(span).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
+ err = client.(*flightsql.Client).Close()
+}
+
func (d *databaseImpl) Open(ctx context.Context) (_ adbc.Connection, err
error) {
ctx, span := internal.StartSpan(
ctx,
- "FlightSQLDatabase.Open",
+ "FlightSQL.Database.Open",
d,
trace.WithAttributes(traceHeaderAttrsWithPrefix(d.hdrs,
traceRequestMetadataPrefix)...),
)
@@ -519,25 +554,37 @@ func (d *databaseImpl) Open(ctx context.Context) (_
adbc.Connection, err error)
EndSpan()
}()
- authMiddle := &bearerAuthMiddleware{hdrs: d.hdrs.Copy(), logger:
safeLogger(d.Logger)}
+ authMiddle := &bearerAuthMiddleware{
+ hdrs: d.hdrs.Copy(),
+ }
var cookies flight.CookieMiddleware
if d.enableCookies {
cookies = flight.NewCookieMiddleware()
}
- cl, err := getFlightClient(ctx, d.uri.String(), d, authMiddle, cookies)
+ cl, err := getFlightClient(ctx, d.uri.String(), d, authMiddle, cookies,
span)
if err != nil {
return nil, err
}
cache := gcache.New(20).LRU().
Expiration(5 * time.Minute).
- LoaderFunc(func(loc interface{}) (interface{}, error) {
+ LoaderFunc(func(loc interface{}) (_ interface{}, err error) {
+ startTime := time.Now()
+ ctx, cacheSpan :=
internal.StartSpan(context.Background(), "FlightSQL.Database.LoadCachedClient",
d)
+ defer func() {
+ internal.NewEndSpanHelper(cacheSpan).
+ WithError(err).
+ WithStartTime(startTime).
+ EndSpan()
+ }()
+
uri, ok := loc.(string)
if !ok {
return nil, adbc.Error{Msg:
fmt.Sprintf("Location must be a string, got %#v",
uri), Code: adbc.StatusInternal}
}
+
cacheSpan.SetAttributes(attribute.String("flight.location", uri))
var cookieMiddleware flight.CookieMiddleware
// if cookies are enabled, start by cloning the
existing cookies
@@ -545,8 +592,13 @@ func (d *databaseImpl) Open(ctx context.Context) (_
adbc.Connection, err error)
cookieMiddleware = cookies.Clone()
}
// use the existing auth token if there is one
- cl, err := getFlightClient(context.Background(), uri, d,
- &bearerAuthMiddleware{hdrs:
authMiddle.hdrs.Copy(), logger: safeLogger(d.Logger)}, cookieMiddleware)
+ cl, err := getFlightClient(
+ ctx,
+ uri,
+ d,
+ &bearerAuthMiddleware{hdrs:
authMiddle.hdrs.Copy()},
+ cookieMiddleware,
+ cacheSpan)
if err != nil {
return nil, err
}
@@ -554,18 +606,10 @@ func (d *databaseImpl) Open(ctx context.Context) (_
adbc.Connection, err error)
cl.Alloc = d.Alloc
return cl, nil
}).
- EvictedFunc(func(_, client interface{}) {
- conn := client.(*flightsql.Client)
- err := conn.Close()
- if err != nil {
- d.Logger.Debug("failed to close client",
"error", err.Error())
- }
- }).PurgeVisitorFunc(func(_ interface{}, client interface{}) {
- conn := client.(*flightsql.Client)
- err := conn.Close()
- if err != nil {
- d.Logger.Debug("failed to close client", "error",
err.Error())
- }
+ EvictedFunc(func(location, client interface{}) {
+ closeCachedFlightClient(d, location, client, "evicted")
+ }).PurgeVisitorFunc(func(location, client interface{}) {
+ closeCachedFlightClient(d, location, client, "purged")
}).Build()
var cnxnSupport support
@@ -576,7 +620,7 @@ func (d *databaseImpl) Open(ctx context.Context) (_
adbc.Connection, err error)
const int32code = 3
for _, endpoint := range info.Endpoint {
- rdr, err := doGetWithLogger(ctx, cl, endpoint, cache,
d.Logger, d.timeout)
+ rdr, err := doGetWithTracer(ctx, cl, endpoint, cache,
d, d.timeout)
if err != nil {
continue
}
@@ -619,12 +663,11 @@ func (d *databaseImpl) Open(ctx context.Context) (_
adbc.Connection, err error)
// this connection (and any statements derived from it).
conn.id = newRandomID("conn")
conn.openedAt = time.Now()
- conn.Logger = safeLogger(conn.Logger).With("connection_id", conn.id)
- conn.Logger.InfoContext(ctx, "FlightSQL connection opened",
- "target", d.uri.String(),
- "transactionsSupported", cnxnSupport.transactions,
- "driver", infoDriverName,
- )
+ span.AddEvent("finished", trace.WithAttributes(
+ attribute.String("target", d.uri.String()),
+ attribute.Bool("transactionsSupported",
cnxnSupport.transactions),
+ attribute.String("driver", infoDriverName),
+ ))
return driverbase.NewConnectionBuilder(conn).
WithDriverInfoPreparer(conn).
@@ -636,9 +679,6 @@ func (d *databaseImpl) Open(ctx context.Context) (_
adbc.Connection, err error)
type bearerAuthMiddleware struct {
mutex sync.RWMutex
hdrs metadata.MD
- // logger, when non-nil, receives an Info event each time the bearer
- // token is rotated. Only token lengths are logged, never values.
- logger *slog.Logger
}
func (b *bearerAuthMiddleware) StartCall(ctx context.Context) context.Context {
@@ -649,50 +689,46 @@ func (b *bearerAuthMiddleware) StartCall(ctx
context.Context) context.Context {
}
// rotateAuth atomically replaces the stored Authorization metadata and
-// returns the previous value plus the current logger. Callers invoke
-// the logger outside the critical section.
-func (b *bearerAuthMiddleware) rotateAuth(headers ...string) (previous
[]string, logger *slog.Logger) {
+// returns the previous value.
+func (b *bearerAuthMiddleware) rotateAuth(headers ...string) (previous
[]string) {
b.mutex.Lock()
defer b.mutex.Unlock()
previous = b.hdrs.Get("authorization")
b.hdrs.Set("authorization", headers...)
- return previous, b.logger
+ return previous
}
func (b *bearerAuthMiddleware) HeadersReceived(ctx context.Context, md
metadata.MD) {
+ captureResponseMetadata(ctx, md)
// apache/arrow-adbc#584
headers := md.Get("authorization")
if len(headers) == 0 {
return
}
- previous, logger := b.rotateAuth(headers...)
- if logger == nil {
- return
- }
+ previous := b.rotateAuth(headers...)
// Log lengths, never values, so credentials never reach the log path.
var prevLen int
if len(previous) > 0 {
prevLen = len(previous[0])
}
- logger.InfoContext(ctx, "FlightSQL bearer token rotated by server",
- "previous_token_length", prevLen,
- "new_token_length", len(headers[0]),
- "source", "HeadersReceived",
- )
+ if span := trace.SpanFromContext(ctx); span != nil &&
span.IsRecording() {
+ span.AddEvent("auth_token.rotated_by_server",
trace.WithAttributes(
+ attribute.Int("previous_token_length", prevLen),
+ attribute.Int("new_token_length", len(headers[0])),
+ attribute.String("source", "HeadersReceived"),
+ ))
+ }
}
-func (b *bearerAuthMiddleware) SetHeader(authValue string) {
- previous, logger := b.rotateAuth(authValue)
- if logger == nil {
- return
- }
+func (b *bearerAuthMiddleware) SetHeader(authValue string, span trace.Span) {
+ previous := b.rotateAuth(authValue)
var prevLen int
if len(previous) > 0 {
prevLen = len(previous[0])
}
- logger.Info("FlightSQL bearer token rotated by client",
- "previous_token_length", prevLen,
- "new_token_length", len(authValue),
- "source", "SetHeader",
- )
+ span.AddEvent("auth_token.rotated_by_client", trace.WithAttributes(
+ attribute.Int("previous_token_length", prevLen),
+ attribute.Int("new_token_length", len(authValue)),
+ attribute.String("source", "SetHeader"),
+ ))
}
diff --git a/go/adbc/driver/flightsql/flightsql_driver.go
b/go/adbc/driver/flightsql/flightsql_driver.go
index 169b60a08..090d7c166 100644
--- a/go/adbc/driver/flightsql/flightsql_driver.go
+++ b/go/adbc/driver/flightsql/flightsql_driver.go
@@ -40,6 +40,7 @@ package flightsql
import (
"context"
+ "errors"
"net/url"
"time"
@@ -127,7 +128,7 @@ func (d *driverImpl) NewDatabaseWithOptions(opts
map[string]string, userDialOpts
return d.NewDatabaseWithOptionsContext(context.Background(), opts,
userDialOpts...)
}
-func (d *driverImpl) NewDatabaseWithOptionsContext(ctx context.Context, opts
map[string]string, userDialOpts ...grpc.DialOption) (adbc.Database, error) {
+func (d *driverImpl) NewDatabaseWithOptionsContext(ctx context.Context, opts
map[string]string, userDialOpts ...grpc.DialOption) (_ adbc.Database, err
error) {
opts = maps.Clone(opts)
uri, ok := opts[adbc.OptionKeyURI]
if !ok {
@@ -151,6 +152,14 @@ func (d *driverImpl) NewDatabaseWithOptionsContext(ctx
context.Context, opts map
if err != nil {
return nil, err
}
+ constructionComplete := false
+ defer func() {
+ if !constructionComplete {
+ if closeErr := dbBase.Close(); closeErr != nil {
+ err = errors.Join(err, closeErr)
+ }
+ }
+ }()
db := &databaseImpl{
DatabaseImplBase: dbBase,
timeout: timeoutOption{
@@ -174,6 +183,7 @@ func (d *driverImpl) NewDatabaseWithOptionsContext(ctx
context.Context, opts map
return nil, err
}
+ constructionComplete = true
return driverbase.NewDatabase(db), nil
}
diff --git a/go/adbc/driver/flightsql/flightsql_statement.go
b/go/adbc/driver/flightsql/flightsql_statement.go
index 61911e9e3..57aca4f94 100644
--- a/go/adbc/driver/flightsql/flightsql_statement.go
+++ b/go/adbc/driver/flightsql/flightsql_statement.go
@@ -534,7 +534,7 @@ func (s *statement) ExecuteQuery(ctx context.Context) (rdr
array.RecordReader, n
ctx, span := internal.StartSpan(
ctx,
- "FlightSQLStatement.ExecuteQuery",
+ "FlightSQL.Statement.ExecuteQuery",
s.cnxn,
trace.WithAttributes(traceHeaderAttrsWithPrefix(s.hdrs,
traceRequestMetadataPrefix)...),
)
@@ -618,7 +618,7 @@ func (s *statement) ExecuteUpdate(ctx context.Context) (n
int64, err error) {
ctx, span := internal.StartSpan(
ctx,
- "FlightSQLStatement.ExecuteUpdate",
+ "FlightSQL.Statement.ExecuteUpdate",
s.cnxn,
trace.WithAttributes(traceHeaderAttrsWithPrefix(s.hdrs,
traceRequestMetadataPrefix)...),
)
@@ -676,7 +676,7 @@ func (s *statement) ExecuteUpdate(ctx context.Context) (n
int64, err error) {
func (s *statement) Prepare(ctx context.Context) (err error) {
ctx, span := internal.StartSpan(
ctx,
- "FlightSQLStatement.Prepare",
+ "FlightSQL.Statement.Prepare",
s.cnxn,
trace.WithAttributes(traceHeaderAttrsWithPrefix(s.hdrs,
traceRequestMetadataPrefix)...),
)
diff --git a/go/adbc/driver/flightsql/flightsql_tracing.go
b/go/adbc/driver/flightsql/flightsql_tracing.go
index 168e993de..853be6897 100644
--- a/go/adbc/driver/flightsql/flightsql_tracing.go
+++ b/go/adbc/driver/flightsql/flightsql_tracing.go
@@ -28,6 +28,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/status"
)
type responseMetadataKey struct{}
@@ -135,6 +136,46 @@ func (p *streamProgress) logKeyValues()
[]attribute.KeyValue {
return attrs
}
+// headerKeyValuesWithPrefix is the shared implementation behind
+// correlationHeaderAttrs (incoming) and outgoingCallHeaderAttrs
+// (outbound). Only headers in wellKnownCorrelationHeaders are emitted;
+// returns nil when none are present.
+func headerKeyValuesWithPrefix(md metadata.MD, prefix string)
[]attribute.KeyValue {
+ if len(md) == 0 {
+ return nil
+ }
+ out := make([]attribute.KeyValue, 0, 4)
+ for _, k := range wellKnownCorrelationHeaders {
+ if vals := md.Get(k); len(vals) > 0 {
+ out = append(out, attribute.StringSlice(prefix+k, vals))
+ }
+ }
+ return out
+}
+
+// correlationHeaderKeyValues returns OpenTelemetry attributes for well-known
+// correlation headers present in md (typically incoming headers/trailers).
Uses the
+// "hdr_" prefix; only allow-listed headers are emitted.
+func correlationHeaderKeyValues(md metadata.MD) []attribute.KeyValue {
+ return headerKeyValuesWithPrefix(md, "hdr_")
+}
+
+// grpcStatusKeyValues returns OpenTelemetry attributes for the gRPC status
+// embedded in err, or nil if err has no status.
+func grpcStatusKeyValues(err error) []attribute.KeyValue {
+ if err == nil {
+ return nil
+ }
+ st, ok := status.FromError(err)
+ if !ok {
+ return nil
+ }
+ return []attribute.KeyValue{
+ attribute.String("grpc_code", st.Code().String()),
+ attribute.String("grpc_message", st.Message()),
+ }
+}
+
// flightInfoTracingKeyValues returns OpenTelemetry attributes describing a
FlightInfo:
// descriptor type and command prefix, AppMetadata prefix (some backends
// embed a server-side query handle there), and advisory record/byte
diff --git a/go/adbc/driver/flightsql/logging.go
b/go/adbc/driver/flightsql/logging.go
index 48a342728..e60627c33 100644
--- a/go/adbc/driver/flightsql/logging.go
+++ b/go/adbc/driver/flightsql/logging.go
@@ -29,9 +29,6 @@ import (
"github.com/apache/arrow-go/v18/arrow/flight"
"go.opentelemetry.io/otel/trace"
- "golang.org/x/exp/maps"
- "golang.org/x/exp/slices"
- "google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
@@ -95,118 +92,6 @@ func formatInt(n int64) string {
return strconv.FormatInt(n, 10)
}
-func makeUnaryLoggingInterceptor(logger *slog.Logger)
grpc.UnaryClientInterceptor {
- interceptor := func(ctx context.Context, method string, req, reply any,
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
- start := time.Now()
- // Ignore errors
- outgoing, _ := metadata.FromOutgoingContext(ctx)
- err := invoker(ctx, method, req, reply, cc, opts...)
- if logger.Enabled(ctx, slog.LevelDebug) {
- args := []any{"target", cc.Target(), "duration",
time.Since(start), "err", err, "metadata", outgoing}
- args = append(args, outgoingCallHeaderAttrs(ctx)...)
- args = append(args, grpcStatusAttrs(err)...)
- logger.DebugContext(ctx, method, args...)
- } else {
- keys := maps.Keys(outgoing)
- slices.Sort(keys)
- args := []any{"target", cc.Target(), "duration",
time.Since(start), "err", err, "metadata", keys}
- // Surface curated outbound correlation IDs regardless
of level.
- args = append(args, outgoingCallHeaderAttrs(ctx)...)
- args = append(args, grpcStatusAttrs(err)...)
- logger.InfoContext(ctx, method, args...)
- }
- return err
- }
- return interceptor
-}
-
-func makeStreamLoggingInterceptor(logger *slog.Logger)
grpc.StreamClientInterceptor {
- interceptor := func(ctx context.Context, desc *grpc.StreamDesc, cc
*grpc.ClientConn, method string, streamer grpc.Streamer, opts
...grpc.CallOption) (grpc.ClientStream, error) {
- start := time.Now()
- // Ignore errors
- outgoing, _ := metadata.FromOutgoingContext(ctx)
- stream, err := streamer(ctx, desc, cc, method, opts...)
- if err != nil {
- args := []any{"target", cc.Target(), "duration",
time.Since(start), "err", err}
- args = append(args, outgoingCallHeaderAttrs(ctx)...)
- args = append(args, grpcStatusAttrs(err)...)
- logger.InfoContext(ctx, method, args...)
- return stream, err
- }
-
- return &loggedStream{ClientStream: stream, logger: logger, ctx:
ctx, method: method, start: start, target: cc.Target(), outgoing: outgoing}, err
- }
- return interceptor
-}
-
-type loggedStream struct {
- grpc.ClientStream
-
- logger *slog.Logger
- ctx context.Context
- method string
- start time.Time
- target string
- outgoing metadata.MD
-
- // recvCount tracks how many messages were received before the stream
- // ended; logged on termination so EOFs on empty streams are
distinguishable
- // from mid-stream failures.
- recvCount int64
-}
-
-func (stream *loggedStream) RecvMsg(m any) error {
- err := stream.ClientStream.RecvMsg(m)
- if err == nil {
- stream.recvCount++
- return nil
- }
-
- loggedErr := err
- if loggedErr == io.EOF {
- loggedErr = nil
- }
-
- // Capture trailers from the terminated stream; they often carry
- // server-side diagnostic information for failure triage.
- trailer := stream.Trailer()
-
- if stream.logger.Enabled(stream.ctx, slog.LevelDebug) {
- stream.logger.DebugContext(stream.ctx, stream.method,
- "target", stream.target,
- "duration", time.Since(stream.start),
- "err", loggedErr,
- "recvMessages", stream.recvCount,
- "metadata", stream.outgoing,
- "trailer", trailer,
- )
- } else {
- keys := maps.Keys(stream.outgoing)
- slices.Sort(keys)
- trailerKeys := maps.Keys(trailer)
- slices.Sort(trailerKeys)
- args := []any{
- "target", stream.target,
- "duration", time.Since(stream.start),
- "err", loggedErr,
- "recvMessages", stream.recvCount,
- "metadata", keys,
- "trailer", trailerKeys,
- }
- // Promote curated correlation headers from the trailer.
- args = append(args, correlationHeaderAttrs(trailer)...)
- // Promote the outbound correlation IDs the caller supplied.
- args = append(args, outgoingCallHeaderAttrs(stream.ctx)...)
- // EOF is a clean close in Flight, so loggedErr was nil-ed
above;
- // only attach status attrs for real errors.
- if loggedErr != nil {
- args = append(args, grpcStatusAttrs(loggedErr)...)
- }
- stream.logger.InfoContext(stream.ctx, stream.method, args...)
- }
- return err
-}
-
// wellKnownCorrelationHeaders is the curated allow-list of inbound gRPC
// header/trailer keys that are surfaced verbatim into log records, for
// cross-referencing client-side logs with server-side traces. Includes
diff --git a/go/adbc/driver/flightsql/tracing_test.go
b/go/adbc/driver/flightsql/tracing_test.go
index 0bd5a74cf..8dfca8c1c 100644
--- a/go/adbc/driver/flightsql/tracing_test.go
+++ b/go/adbc/driver/flightsql/tracing_test.go
@@ -88,7 +88,7 @@ func TestTraceHeaderAttrsWithPrefix_AppliedToSpan(t
*testing.T) {
ctx, span := internal.StartSpan(
context.Background(),
- "FlightSQLStatement.ExecuteQuery",
+ "FlightSQL.Statement.ExecuteQuery",
tracing,
trace.WithAttributes(traceHeaderAttrsWithPrefix(metadata.New(map[string]string{
"x-request-id": "req-123",
@@ -114,7 +114,7 @@ func TestTraceHeaderAttrsWithPrefix_AppliedToSpan(t
*testing.T) {
if _, ok := got["rpc.request.metadata.x-random-header"]; ok {
t.Fatalf("x-random-header leaked into span attrs: %v", got)
}
- if v := got["db.operation.name"]; len(v) != 1 || v[0] !=
"FlightSQLStatement.ExecuteQuery" {
+ if v := got["db.operation.name"]; len(v) != 1 || v[0] !=
"FlightSQL.Statement.ExecuteQuery" {
t.Fatalf("db.operation.name = %v, want
[FlightSQLStatement.ExecuteQuery]", v)
}
}
diff --git a/go/adbc/go.mod b/go/adbc/go.mod
index 0cc0bb65b..f5fbf8495 100644
--- a/go/adbc/go.mod
+++ b/go/adbc/go.mod
@@ -29,6 +29,7 @@ require (
github.com/golang/protobuf v1.5.4
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.12.0
+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
v0.70.0
go.opentelemetry.io/otel v1.45.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0
diff --git a/go/adbc/go.sum b/go/adbc/go.sum
index fca6e93b5..fdb5875b4 100644
--- a/go/adbc/go.sum
+++ b/go/adbc/go.sum
@@ -74,6 +74,8 @@ github.com/zeebo/xxh3 v1.1.0
h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod
h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opentelemetry.io/auto/sdk v1.2.1
h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod
h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
v0.70.0 h1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
v0.70.0/go.mod h1:DqEFwLumhzMBDQv9PcWbyoDxHI/4lAk6CM4nJBH39sc=
go.opentelemetry.io/otel v1.45.0
h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod
h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0
h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg=