components/google-built-opentelemetry-collector/exporter/googleservicecontrolexporter/client.go (86 lines of code) (raw):
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package client implements client interface for service control API
package googleservicecontrolexporter
import (
"context"
servicecontrol "cloud.google.com/go/servicecontrol/apiv1"
scpb "cloud.google.com/go/servicecontrol/apiv1/servicecontrolpb"
"go.uber.org/zap"
"google.golang.org/api/option"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
type HeaderLoggingInterceptor struct {
logger *zap.SugaredLogger
}
// ServiceControlClient defines a interface of client for service control API
type ServiceControlClient interface {
Report(ctx context.Context, request *scpb.ReportRequest) (*scpb.ReportResponse, error)
Close() error
}
type serviceControlClientRaw struct {
service scpb.ServiceControllerClient
conn *grpc.ClientConn
}
type serviceControlClientLibrary struct {
service *servicecontrol.ServiceControllerClient
}
func NewServiceControllerClient(endpoint string, useRawServiceControlClient bool, enableDebugHeaders bool, logger *zap.Logger, opts ...grpc.DialOption) (ServiceControlClient, error) {
ctx := context.Background()
// Use client library. Ignore grpc dial options.
if !useRawServiceControlClient {
// Enable gRPC response interceptor for debug header
var clientOpts []option.ClientOption
if enableDebugHeaders {
interceptor := NewHeaderLoggingInterceptor(logger)
clientOpts = append(clientOpts, option.WithGRPCDialOption(grpc.WithUnaryInterceptor(interceptor.UnaryInterceptor)))
}
clientOpts = append(clientOpts, option.WithEndpoint(endpoint))
c, err := servicecontrol.NewServiceControllerClient(ctx, clientOpts...)
if err != nil {
return nil, err
}
return &serviceControlClientLibrary{
service: c,
}, nil
}
// Use raw client.
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return nil, err
}
return &serviceControlClientRaw{
service: scpb.NewServiceControllerClient(conn),
conn: conn,
}, nil
}
func (c *serviceControlClientRaw) Report(ctx context.Context, request *scpb.ReportRequest) (*scpb.ReportResponse, error) {
return c.service.Report(ctx, request)
}
func (c *serviceControlClientRaw) Close() error {
return c.conn.Close()
}
func (c *serviceControlClientLibrary) Report(ctx context.Context, request *scpb.ReportRequest) (*scpb.ReportResponse, error) {
return c.service.Report(ctx, request)
}
func (c *serviceControlClientLibrary) Close() error {
return c.service.Close()
}
func NewHeaderLoggingInterceptor(logger *zap.Logger) *HeaderLoggingInterceptor {
return &HeaderLoggingInterceptor{
logger: logger.Sugar(),
}
}
// UnaryInterceptor implements grpc.UnaryClientInterceptor interface
func (h *HeaderLoggingInterceptor) UnaryInterceptor(
ctx context.Context,
method string,
req interface{},
reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
var respHeaders metadata.MD
opts = append(opts, grpc.Header(&respHeaders))
err := invoker(ctx, method, req, reply, cc, opts...)
if err != nil {
h.logger.Infof("Request failed for method %s, debug response headers:%v", method, respHeaders)
return err
}
h.logger.Infof("Method: %s, Received response headers: %v", method, respHeaders)
return nil
}