├── .gitignore ├── python ├── setup.cfg ├── .style.yapf ├── requirements.txt ├── examples │ ├── requirements.txt │ ├── store │ │ ├── run_codegen.py │ │ ├── README.md │ │ ├── store_server.py │ │ ├── store_pb2_grpc.py │ │ └── store_client.py │ ├── trivial │ │ ├── README.md │ │ ├── run_codegen.py │ │ ├── trivial_client.py │ │ ├── command_line_pb2_grpc.py │ │ ├── trivial_server.py │ │ └── command_line_pb2.py │ ├── integration │ │ ├── run_codegen.py │ │ ├── README.md │ │ ├── command_line_pb2_grpc.py │ │ ├── integration_client.py │ │ ├── integration_server.py │ │ └── command_line_pb2.py │ └── protos │ │ ├── command_line.proto │ │ └── store.proto ├── requirements-test.txt ├── README.md ├── setup.py ├── .gitignore ├── grpc_opentracing │ ├── _utilities.py │ ├── __init__.py │ ├── grpcext │ │ └── __init__.py │ ├── _client.py │ └── _server.py └── tests │ ├── _tracer.py │ ├── test_interceptor.py │ └── _service.py ├── java ├── .gitignore ├── src │ ├── testgen │ │ └── io │ │ │ └── opentracing │ │ │ └── contrib │ │ │ ├── README.md │ │ │ ├── HelloRequestOrBuilder.java │ │ │ ├── HelloReplyOrBuilder.java │ │ │ ├── HelloWorldProto.java │ │ │ └── GreeterGrpc.java │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── opentracing │ │ │ └── contrib │ │ │ ├── README.md │ │ │ ├── TracedClient.java │ │ │ ├── ActiveSpanSourceTest.java │ │ │ ├── TracedService.java │ │ │ └── OpenTracingContextKeyTest.java │ └── main │ │ └── java │ │ └── io │ │ └── opentracing │ │ └── contrib │ │ ├── OpenTracingContextKey.java │ │ ├── ActiveSpanSource.java │ │ ├── OperationNameConstructor.java │ │ ├── ServerTracingInterceptor.java │ │ └── ClientTracingInterceptor.java ├── build.gradle └── README.rst ├── go └── otgrpc │ ├── package.go │ ├── test │ ├── otgrpc_testing │ │ ├── test.proto │ │ └── test.pb.go │ └── interceptor_test.go │ ├── shared.go │ ├── errors_test.go │ ├── README.md │ ├── errors.go │ ├── options.go │ ├── server.go │ └── client.go ├── PATENTS ├── LICENSE └── README.rst /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/* -------------------------------------------------------------------------------- /python/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /python/.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | based_on_style = google 3 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | # add dependencies in setup.py 2 | 3 | -e . 4 | -------------------------------------------------------------------------------- /python/examples/requirements.txt: -------------------------------------------------------------------------------- 1 | grpcio-opentracing>=1.0 2 | jaeger-client>=3.4.0 3 | -------------------------------------------------------------------------------- /python/requirements-test.txt: -------------------------------------------------------------------------------- 1 | # add dependencies in setup.py 2 | 3 | -r requirements.txt 4 | 5 | -e .[tests] 6 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | .gradle/* 3 | gradle.properties 4 | .classpath 5 | .project 6 | .settings/* 7 | bin/ 8 | -------------------------------------------------------------------------------- /java/src/testgen/io/opentracing/contrib/README.md: -------------------------------------------------------------------------------- 1 | These are protobuf-generated classes to be used for testing purposes only. -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | The repo has moved. 2 | ------------------- 3 | 4 | https://github.com/opentracing-contrib/python-grpc 5 | -------------------------------------------------------------------------------- /python/examples/store/run_codegen.py: -------------------------------------------------------------------------------- 1 | from grpc_tools import protoc 2 | 3 | protoc.main(('', '-I../protos', '--python_out=.', '--grpc_python_out=.', 4 | '../protos/store.proto')) 5 | -------------------------------------------------------------------------------- /python/examples/trivial/README.md: -------------------------------------------------------------------------------- 1 | A simple example showing how to set gRPC up to use OpenTracing. 2 | 3 | ## Usage 4 | ``` 5 | python trivial_server.py & 6 | python trivial_client.py 7 | ``` 8 | -------------------------------------------------------------------------------- /python/examples/integration/run_codegen.py: -------------------------------------------------------------------------------- 1 | from grpc_tools import protoc 2 | 3 | protoc.main(('', '-I../protos', '--python_out=.', '--grpc_python_out=.', 4 | '../protos/command_line.proto')) 5 | -------------------------------------------------------------------------------- /python/examples/trivial/run_codegen.py: -------------------------------------------------------------------------------- 1 | from grpc_tools import protoc 2 | 3 | protoc.main(('', '-I../protos', '--python_out=.', '--grpc_python_out=.', 4 | '../protos/command_line.proto')) 5 | -------------------------------------------------------------------------------- /python/examples/integration/README.md: -------------------------------------------------------------------------------- 1 | An example showing how to connect gRPC's OpenTracing spans to other OpenTracing 2 | spans. 3 | 4 | ## Usage 5 | ``` 6 | python integration_server.py & 7 | python integration_client.py 8 | ``` 9 | -------------------------------------------------------------------------------- /python/examples/store/README.md: -------------------------------------------------------------------------------- 1 | An example that demonstrates how the OpenTracing extensions work with 2 | asynchronous and streaming RPC calls. 3 | 4 | ## Usage 5 | ``` 6 | python store_server.py & 7 | python store_client.py 8 | ``` 9 | -------------------------------------------------------------------------------- /go/otgrpc/package.go: -------------------------------------------------------------------------------- 1 | // Package otgrpc provides OpenTracing support for any gRPC client or server. 2 | // 3 | // See the README for simple usage examples: 4 | // https://github.com/grpc-ecosystem/grpc-opentracing/blob/master/go/otgrpc/README.md 5 | package otgrpc 6 | -------------------------------------------------------------------------------- /java/src/test/java/io/opentracing/contrib/README.md: -------------------------------------------------------------------------------- 1 | ## Tests 2 | 3 | To run tests for grpc-opentracing, run 4 | 5 | ``` 6 | $ gradle test 7 | ``` 8 | 9 | These tests use the protobuf-generated classes from grpc-example, which are located in `src/testgen`. 10 | -------------------------------------------------------------------------------- /python/examples/protos/command_line.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package command_line; 4 | 5 | service CommandLine { 6 | rpc Echo(CommandRequest) returns (CommandResponse) {} 7 | } 8 | 9 | message CommandRequest { 10 | string text = 1; 11 | } 12 | 13 | message CommandResponse { 14 | string text = 1; 15 | } 16 | -------------------------------------------------------------------------------- /go/otgrpc/test/otgrpc_testing/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package otgrpc.testing; 4 | 5 | message SimpleRequest { 6 | int32 payload = 1; 7 | } 8 | 9 | message SimpleResponse { 10 | int32 payload = 1; 11 | } 12 | 13 | service TestService { 14 | rpc UnaryCall(SimpleRequest) returns (SimpleResponse); 15 | 16 | rpc StreamingOutputCall(SimpleRequest) returns (stream SimpleResponse); 17 | 18 | rpc StreamingInputCall(stream SimpleRequest) returns (SimpleResponse); 19 | 20 | rpc StreamingBidirectionalCall(stream SimpleRequest) returns (stream SimpleResponse); 21 | } 22 | -------------------------------------------------------------------------------- /java/src/testgen/io/opentracing/contrib/HelloRequestOrBuilder.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: helloworld.proto 3 | 4 | package io.opentracing.contrib; 5 | 6 | public interface HelloRequestOrBuilder extends 7 | // @@protoc_insertion_point(interface_extends:helloworld.HelloRequest) 8 | com.google.protobuf.MessageOrBuilder { 9 | 10 | /** 11 | * optional string name = 1; 12 | */ 13 | java.lang.String getName(); 14 | /** 15 | * optional string name = 1; 16 | */ 17 | com.google.protobuf.ByteString 18 | getNameBytes(); 19 | } 20 | -------------------------------------------------------------------------------- /java/src/testgen/io/opentracing/contrib/HelloReplyOrBuilder.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: helloworld.proto 3 | 4 | package io.opentracing.contrib; 5 | 6 | public interface HelloReplyOrBuilder extends 7 | // @@protoc_insertion_point(interface_extends:helloworld.HelloReply) 8 | com.google.protobuf.MessageOrBuilder { 9 | 10 | /** 11 | * optional string message = 1; 12 | */ 13 | java.lang.String getMessage(); 14 | /** 15 | * optional string message = 1; 16 | */ 17 | com.google.protobuf.ByteString 18 | getMessageBytes(); 19 | } 20 | -------------------------------------------------------------------------------- /java/src/main/java/io/opentracing/contrib/OpenTracingContextKey.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import io.grpc.Context; 4 | 5 | import io.opentracing.Span; 6 | 7 | /** 8 | * A {@link io.grpc.Context} key for the current OpenTracing trace state. 9 | * 10 | * Can be used to get the active span, or to set the active span for a scoped unit of work. 11 | * See the grpc-java OpenTracing docs for use cases and examples. 12 | */ 13 | public class OpenTracingContextKey { 14 | 15 | public static final String KEY_NAME = "io.opentracing.active-span"; 16 | private static final Context.Key key = Context.key(KEY_NAME); 17 | 18 | /** 19 | * @return the active span for the current request 20 | */ 21 | public static Span activeSpan() { 22 | return key.get(); 23 | } 24 | 25 | /** 26 | * @return the OpenTracing context key 27 | */ 28 | public static Context.Key getKey() { 29 | return key; 30 | } 31 | } -------------------------------------------------------------------------------- /python/examples/protos/store.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package store; 4 | 5 | service Store { 6 | rpc AddItem(AddItemRequest) returns (Empty) {} 7 | rpc AddItems(stream AddItemRequest) returns (Empty) {} 8 | rpc RemoveItem(RemoveItemRequest) returns (RemoveItemResponse) {} 9 | rpc RemoveItems(stream RemoveItemRequest) returns (RemoveItemResponse) {} 10 | rpc ListInventory(Empty) returns (stream QuantityResponse) {} 11 | rpc QueryQuantity(QueryItemRequest) returns (QuantityResponse) {} 12 | rpc QueryQuantities(stream QueryItemRequest) 13 | returns (stream QuantityResponse) {} 14 | } 15 | 16 | message Empty {} 17 | 18 | message AddItemRequest { 19 | string name = 1; 20 | } 21 | 22 | message RemoveItemRequest { 23 | string name = 1; 24 | } 25 | 26 | message RemoveItemResponse { 27 | bool was_successful = 1; 28 | } 29 | 30 | message QueryItemRequest { 31 | string name = 1; 32 | } 33 | 34 | message QuantityResponse { 35 | string name = 1; 36 | int32 count = 2; 37 | } 38 | -------------------------------------------------------------------------------- /java/src/main/java/io/opentracing/contrib/ActiveSpanSource.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import io.opentracing.Span; 4 | import io.opentracing.contrib.OpenTracingContextKey; 5 | 6 | /** 7 | * An interface that defines how to get the current active span 8 | */ 9 | public interface ActiveSpanSource { 10 | 11 | /** 12 | * ActiveSpanSource implementation that always returns 13 | * null as the active span 14 | */ 15 | public static ActiveSpanSource NONE = new ActiveSpanSource() { 16 | @Override 17 | public Span getActiveSpan() { 18 | return null; 19 | } 20 | }; 21 | 22 | /** 23 | * ActiveSpanSource implementation that returns the 24 | * current span stored in the GRPC context under 25 | * {@link OpenTracingContextKey} 26 | */ 27 | public static ActiveSpanSource GRPC_CONTEXT = new ActiveSpanSource() { 28 | @Override 29 | public Span getActiveSpan() { 30 | return OpenTracingContextKey.activeSpan(); 31 | } 32 | }; 33 | 34 | /** 35 | * @return the active span 36 | */ 37 | public Span getActiveSpan(); 38 | } -------------------------------------------------------------------------------- /java/src/main/java/io/opentracing/contrib/OperationNameConstructor.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import io.grpc.MethodDescriptor; 4 | 5 | /** 6 | * Interface that allows span operation names to be constructed from an RPC's 7 | * method descriptor. 8 | */ 9 | public interface OperationNameConstructor { 10 | 11 | /** 12 | * Default span operation name constructor, that will return an RPC's method 13 | * name when constructOperationName is called. 14 | */ 15 | public static OperationNameConstructor DEFAULT = new OperationNameConstructor() { 16 | @Override 17 | public String constructOperationName(MethodDescriptor method) { 18 | return method.getFullMethodName(); 19 | } 20 | }; 21 | 22 | /** 23 | * Constructs a span's operation name from the RPC's method. 24 | * @param method the rpc method to extract a name from 25 | * @param the rpc request type 26 | * @param the rpc response type 27 | * @return the operation name 28 | */ 29 | public String constructOperationName(MethodDescriptor method); 30 | } -------------------------------------------------------------------------------- /java/src/test/java/io/opentracing/contrib/TracedClient.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import io.grpc.ManagedChannel; 4 | import io.grpc.ManagedChannelBuilder; 5 | 6 | public class TracedClient { 7 | private final ManagedChannel channel; 8 | private final GreeterGrpc.GreeterBlockingStub blockingStub; 9 | 10 | public TracedClient(String host, int port, ClientTracingInterceptor tracingInterceptor) { 11 | channel = ManagedChannelBuilder.forAddress(host, port) 12 | .usePlaintext(true) 13 | .build(); 14 | 15 | if(tracingInterceptor==null) { 16 | blockingStub = GreeterGrpc.newBlockingStub(channel); 17 | } else { 18 | blockingStub = GreeterGrpc.newBlockingStub(tracingInterceptor.intercept(channel)); 19 | } 20 | } 21 | 22 | void shutdown() throws InterruptedException { 23 | channel.shutdown(); 24 | } 25 | 26 | boolean greet(String name) { 27 | HelloRequest request = HelloRequest.newBuilder().setName(name).build(); 28 | try { 29 | blockingStub.sayHello(request); 30 | } catch (Exception e) { 31 | return false; 32 | } finally { 33 | try { this.shutdown(); } 34 | catch (Exception e) { return false; } 35 | } 36 | return true; 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /go/otgrpc/shared.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "strings" 5 | 6 | opentracing "github.com/opentracing/opentracing-go" 7 | "github.com/opentracing/opentracing-go/ext" 8 | "google.golang.org/grpc/metadata" 9 | ) 10 | 11 | var ( 12 | // Morally a const: 13 | gRPCComponentTag = opentracing.Tag{string(ext.Component), "gRPC"} 14 | ) 15 | 16 | // metadataReaderWriter satisfies both the opentracing.TextMapReader and 17 | // opentracing.TextMapWriter interfaces. 18 | type metadataReaderWriter struct { 19 | metadata.MD 20 | } 21 | 22 | func (w metadataReaderWriter) Set(key, val string) { 23 | // The GRPC HPACK implementation rejects any uppercase keys here. 24 | // 25 | // As such, since the HTTP_HEADERS format is case-insensitive anyway, we 26 | // blindly lowercase the key (which is guaranteed to work in the 27 | // Inject/Extract sense per the OpenTracing spec). 28 | key = strings.ToLower(key) 29 | w.MD[key] = append(w.MD[key], val) 30 | } 31 | 32 | func (w metadataReaderWriter) ForeachKey(handler func(key, val string) error) error { 33 | for k, vals := range w.MD { 34 | for _, v := range vals { 35 | if err := handler(k, v); err != nil { 36 | return err 37 | } 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import sys 4 | import tempfile 5 | 6 | from setuptools import setup, find_packages 7 | 8 | 9 | def readme(): 10 | """Use `pandoc` to convert `README.md` into a `README.rst` file.""" 11 | if os.path.isfile('README.md') and any('dist' in x for x in sys.argv[1:]): 12 | if os.system('pandoc -s README.md -o %s/README.rst' % 13 | tempfile.mkdtemp()) != 0: 14 | logging.warning('Unable to generate README.rst') 15 | if os.path.isfile('README.rst'): 16 | with open('README.rst') as fd: 17 | return fd.read() 18 | return '' 19 | 20 | 21 | setup( 22 | name='grpcio-opentracing', 23 | version='1.0', 24 | description='Python OpenTracing Extensions for gRPC', 25 | long_description=readme(), 26 | author='LightStep', 27 | license='', 28 | install_requires=['opentracing>=1.2.2', 'grpcio>=1.1.3', 'six>=1.10'], 29 | setup_requires=['pytest-runner'], 30 | tests_require=['pytest', 'future'], 31 | keywords=['opentracing'], 32 | classifiers=[ 33 | 'Operating System :: OS Independent', 34 | 'Programming Language :: Python :: 2.7', 35 | ], 36 | packages=find_packages(exclude=['docs*', 'tests*', 'examples*'])) 37 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the GRPC project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of GRPC, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of GRPC. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of GRPC or any code incorporated within this 19 | implementation of GRPC constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of GRPC 22 | shall terminate as of the date such litigation is filed. 23 | Status API Training Shop Blog About 24 | -------------------------------------------------------------------------------- /python/examples/trivial/trivial_client.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import time 4 | import argparse 5 | 6 | import grpc 7 | from jaeger_client import Config 8 | 9 | from grpc_opentracing import open_tracing_client_interceptor 10 | from grpc_opentracing.grpcext import intercept_channel 11 | 12 | import command_line_pb2 13 | 14 | 15 | def run(): 16 | parser = argparse.ArgumentParser() 17 | parser.add_argument( 18 | '--log_payloads', 19 | action='store_true', 20 | help='log request/response objects to open-tracing spans') 21 | args = parser.parse_args() 22 | 23 | config = Config( 24 | config={ 25 | 'sampler': { 26 | 'type': 'const', 27 | 'param': 1, 28 | }, 29 | 'logging': True, 30 | }, 31 | service_name='trivial-client') 32 | tracer = config.initialize_tracer() 33 | tracer_interceptor = open_tracing_client_interceptor( 34 | tracer, log_payloads=args.log_payloads) 35 | channel = grpc.insecure_channel('localhost:50051') 36 | channel = intercept_channel(channel, tracer_interceptor) 37 | stub = command_line_pb2.CommandLineStub(channel) 38 | response = stub.Echo(command_line_pb2.CommandRequest(text='Hello, hello')) 39 | print(response.text) 40 | 41 | time.sleep(2) 42 | tracer.close() 43 | time.sleep(2) 44 | 45 | 46 | if __name__ == '__main__': 47 | run() 48 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | -------------------------------------------------------------------------------- /go/otgrpc/errors_test.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/opentracing/opentracing-go/mocktracer" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | const ( 14 | firstCode = codes.OK 15 | lastCode = codes.DataLoss 16 | ) 17 | 18 | func TestSpanTags(t *testing.T) { 19 | tracer := mocktracer.New() 20 | for code := firstCode; code <= lastCode; code++ { 21 | // Client error 22 | tracer.Reset() 23 | span := tracer.StartSpan("test-trace-client") 24 | err := status.Error(code, "") 25 | SetSpanTags(span, err, true) 26 | span.Finish() 27 | 28 | // Assert added tags 29 | rawSpan := tracer.FinishedSpans()[0] 30 | expectedTags := map[string]interface{}{ 31 | "response_code": code, 32 | "response_class": ErrorClass(err), 33 | } 34 | if err != nil { 35 | expectedTags["error"] = true 36 | } 37 | assert.Equal(t, expectedTags, rawSpan.Tags()) 38 | 39 | // Server error 40 | tracer.Reset() 41 | span = tracer.StartSpan("test-trace-server") 42 | err = status.Error(code, "") 43 | SetSpanTags(span, err, false) 44 | span.Finish() 45 | 46 | // Assert added tags 47 | rawSpan = tracer.FinishedSpans()[0] 48 | expectedTags = map[string]interface{}{ 49 | "response_code": code, 50 | "response_class": ErrorClass(err), 51 | } 52 | if err != nil && ErrorClass(err) == ServerError { 53 | expectedTags["error"] = true 54 | } 55 | assert.Equal(t, expectedTags, rawSpan.Tags()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /python/examples/trivial/command_line_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | from grpc.framework.common import cardinality 4 | from grpc.framework.interfaces.face import utilities as face_utilities 5 | 6 | import command_line_pb2 as command__line__pb2 7 | 8 | 9 | class CommandLineStub(object): 10 | 11 | def __init__(self, channel): 12 | """Constructor. 13 | 14 | Args: 15 | channel: A grpc.Channel. 16 | """ 17 | self.Echo = channel.unary_unary( 18 | '/command_line.CommandLine/Echo', 19 | request_serializer=command__line__pb2.CommandRequest.SerializeToString, 20 | response_deserializer=command__line__pb2.CommandResponse.FromString, 21 | ) 22 | 23 | 24 | class CommandLineServicer(object): 25 | 26 | def Echo(self, request, context): 27 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 28 | context.set_details('Method not implemented!') 29 | raise NotImplementedError('Method not implemented!') 30 | 31 | 32 | def add_CommandLineServicer_to_server(servicer, server): 33 | rpc_method_handlers = { 34 | 'Echo': grpc.unary_unary_rpc_method_handler( 35 | servicer.Echo, 36 | request_deserializer=command__line__pb2.CommandRequest.FromString, 37 | response_serializer=command__line__pb2.CommandResponse.SerializeToString, 38 | ), 39 | } 40 | generic_handler = grpc.method_handlers_generic_handler( 41 | 'command_line.CommandLine', rpc_method_handlers) 42 | server.add_generic_rpc_handlers((generic_handler,)) 43 | -------------------------------------------------------------------------------- /python/examples/integration/command_line_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | from grpc.framework.common import cardinality 4 | from grpc.framework.interfaces.face import utilities as face_utilities 5 | 6 | import command_line_pb2 as command__line__pb2 7 | 8 | 9 | class CommandLineStub(object): 10 | 11 | def __init__(self, channel): 12 | """Constructor. 13 | 14 | Args: 15 | channel: A grpc.Channel. 16 | """ 17 | self.Echo = channel.unary_unary( 18 | '/command_line.CommandLine/Echo', 19 | request_serializer=command__line__pb2.CommandRequest.SerializeToString, 20 | response_deserializer=command__line__pb2.CommandResponse.FromString, 21 | ) 22 | 23 | 24 | class CommandLineServicer(object): 25 | 26 | def Echo(self, request, context): 27 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 28 | context.set_details('Method not implemented!') 29 | raise NotImplementedError('Method not implemented!') 30 | 31 | 32 | def add_CommandLineServicer_to_server(servicer, server): 33 | rpc_method_handlers = { 34 | 'Echo': grpc.unary_unary_rpc_method_handler( 35 | servicer.Echo, 36 | request_deserializer=command__line__pb2.CommandRequest.FromString, 37 | response_serializer=command__line__pb2.CommandResponse.SerializeToString, 38 | ), 39 | } 40 | generic_handler = grpc.method_handlers_generic_handler( 41 | 'command_line.CommandLine', rpc_method_handlers) 42 | server.add_generic_rpc_handlers((generic_handler,)) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, gRPC Ecosystem 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of grpc-opentracing nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /java/src/test/java/io/opentracing/contrib/ActiveSpanSourceTest.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import io.grpc.Context; 8 | import io.opentracing.Span; 9 | import io.opentracing.Tracer; 10 | import io.opentracing.mock.MockTracer; 11 | 12 | public class ActiveSpanSourceTest { 13 | 14 | Tracer tracer = new MockTracer(); 15 | 16 | @Test 17 | public void TestDefaultNone() { 18 | ActiveSpanSource ss = ActiveSpanSource.NONE; 19 | assertEquals("active span should always be null", ss.getActiveSpan(), null); 20 | 21 | Span span = tracer.buildSpan("s0").start(); 22 | Context ctx = Context.current().withValue(OpenTracingContextKey.getKey(), span); 23 | Context previousCtx = ctx.attach(); 24 | 25 | assertEquals("active span should always be null", ss.getActiveSpan(), null); 26 | 27 | ctx.detach(previousCtx); 28 | span.finish(); 29 | } 30 | 31 | @Test 32 | public void TestDefaultGrpc() { 33 | ActiveSpanSource ss = ActiveSpanSource.GRPC_CONTEXT; 34 | assertEquals("active span should be null, no span in OpenTracingContextKey", ss.getActiveSpan(), null); 35 | 36 | Span span = tracer.buildSpan("s0").start(); 37 | Context ctx = Context.current().withValue(OpenTracingContextKey.getKey(), span); 38 | Context previousCtx = ctx.attach(); 39 | 40 | assertEquals("active span should be OpenTracingContextKey.activeSpan()", ss.getActiveSpan(), span); 41 | 42 | ctx.detach(previousCtx); 43 | span.finish(); 44 | 45 | assertEquals("active span should be null, no span in OpenTracingContextKey", ss.getActiveSpan(), null); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /go/otgrpc/README.md: -------------------------------------------------------------------------------- 1 | # OpenTracing support for gRPC in Go 2 | 3 | The `otgrpc` package makes it easy to add OpenTracing support to gRPC-based 4 | systems in Go. 5 | 6 | ## Installation 7 | 8 | ``` 9 | go get github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc 10 | ``` 11 | 12 | ## Documentation 13 | 14 | See the basic usage examples below and the [package documentation on 15 | godoc.org](https://godoc.org/github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc). 16 | 17 | ## Client-side usage example 18 | 19 | Wherever you call `grpc.Dial`: 20 | 21 | ```go 22 | // You must have some sort of OpenTracing Tracer instance on hand. 23 | var tracer opentracing.Tracer = ... 24 | ... 25 | 26 | // Set up a connection to the server peer. 27 | conn, err := grpc.Dial( 28 | address, 29 | ... // other options 30 | grpc.WithUnaryInterceptor( 31 | otgrpc.OpenTracingClientInterceptor(tracer)), 32 | grpc.WithStreamInterceptor( 33 | otgrpc.OpenTracingStreamClientInterceptor(tracer))) 34 | 35 | // All future RPC activity involving `conn` will be automatically traced. 36 | ``` 37 | 38 | ## Server-side usage example 39 | 40 | Wherever you call `grpc.NewServer`: 41 | 42 | ```go 43 | // You must have some sort of OpenTracing Tracer instance on hand. 44 | var tracer opentracing.Tracer = ... 45 | ... 46 | 47 | // Initialize the gRPC server. 48 | s := grpc.NewServer( 49 | ... // other options 50 | grpc.UnaryInterceptor( 51 | otgrpc.OpenTracingServerInterceptor(tracer)), 52 | grpc.StreamInterceptor( 53 | otgrpc.OpenTracingStreamServerInterceptor(tracer))) 54 | 55 | // All future RPC activity involving `s` will be automatically traced. 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /java/src/test/java/io/opentracing/contrib/TracedService.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import java.io.IOException; 4 | 5 | import io.grpc.Server; 6 | import io.grpc.ServerBuilder; 7 | import io.grpc.stub.StreamObserver; 8 | 9 | public class TracedService { 10 | private int port = 50051; 11 | private Server server; 12 | 13 | void start() throws IOException { 14 | server = ServerBuilder.forPort(port) 15 | .addService(new GreeterImpl()) 16 | .build() 17 | .start(); 18 | 19 | Runtime.getRuntime().addShutdownHook(new Thread() { 20 | @Override 21 | public void run() { 22 | TracedService.this.stop(); 23 | } 24 | }); 25 | } 26 | 27 | void startWithInterceptor(ServerTracingInterceptor tracingInterceptor) throws IOException { 28 | 29 | server = ServerBuilder.forPort(port) 30 | .addService(tracingInterceptor.intercept(new GreeterImpl())) 31 | .build() 32 | .start(); 33 | 34 | Runtime.getRuntime().addShutdownHook(new Thread() { 35 | @Override 36 | public void run() { 37 | TracedService.this.stop(); 38 | } 39 | }); 40 | } 41 | 42 | void blockUntilShutdown() throws InterruptedException { 43 | if (server != null) { 44 | server.awaitTermination(); 45 | } 46 | } 47 | 48 | void stop() { 49 | if (server != null) { 50 | server.shutdown(); 51 | } 52 | } 53 | 54 | private class GreeterImpl extends GreeterGrpc.GreeterImplBase { 55 | @Override 56 | public void sayHello(HelloRequest req, StreamObserver responseObserver) { 57 | HelloReply reply = HelloReply.newBuilder().setMessage("Hello").build(); 58 | responseObserver.onNext(reply); 59 | responseObserver.onCompleted(); 60 | 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /python/examples/trivial/trivial_server.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import time 4 | import argparse 5 | 6 | import grpc 7 | from concurrent import futures 8 | from jaeger_client import Config 9 | 10 | from grpc_opentracing import open_tracing_server_interceptor 11 | from grpc_opentracing.grpcext import intercept_server 12 | 13 | import command_line_pb2 14 | 15 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24 16 | 17 | 18 | class CommandLine(command_line_pb2.CommandLineServicer): 19 | 20 | def Echo(self, request, context): 21 | return command_line_pb2.CommandResponse(text=request.text) 22 | 23 | 24 | def serve(): 25 | parser = argparse.ArgumentParser() 26 | parser.add_argument( 27 | '--log_payloads', 28 | action='store_true', 29 | help='log request/response objects to open-tracing spans') 30 | args = parser.parse_args() 31 | 32 | config = Config( 33 | config={ 34 | 'sampler': { 35 | 'type': 'const', 36 | 'param': 1, 37 | }, 38 | 'logging': True, 39 | }, 40 | service_name='trivial-server') 41 | tracer = config.initialize_tracer() 42 | tracer_interceptor = open_tracing_server_interceptor( 43 | tracer, log_payloads=args.log_payloads) 44 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 45 | server = intercept_server(server, tracer_interceptor) 46 | 47 | command_line_pb2.add_CommandLineServicer_to_server(CommandLine(), server) 48 | server.add_insecure_port('[::]:50051') 49 | server.start() 50 | try: 51 | while True: 52 | time.sleep(_ONE_DAY_IN_SECONDS) 53 | except KeyboardInterrupt: 54 | server.stop(0) 55 | 56 | time.sleep(2) 57 | tracer.close() 58 | time.sleep(2) 59 | 60 | 61 | if __name__ == '__main__': 62 | serve() 63 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ################ 2 | GRPC-OpenTracing 3 | ################ 4 | 5 | This package enables distributed tracing in GRPC clients and servers via `The OpenTracing Project`_: a set of consistent, expressive, vendor-neutral APIs for distributed tracing and context propagation. 6 | 7 | Once a production system contends with real concurrency or splits into many services, crucial (and formerly easy) tasks become difficult: user-facing latency optimization, root-cause analysis of backend errors, communication about distinct pieces of a now-distributed system, etc. Distributed tracing follows a request on its journey from inception to completion from mobile/browser all the way to the microservices. 8 | 9 | As core services and libraries adopt OpenTracing, the application builder is no longer burdened with the task of adding basic tracing instrumentation to their own code. In this way, developers can build their applications with the tools they prefer and benefit from built-in tracing instrumentation. OpenTracing implementations exist for major distributed tracing systems and can be bound or swapped with a one-line configuration change. 10 | 11 | ******************* 12 | Further Information 13 | ******************* 14 | 15 | If you’re interested in learning more about the OpenTracing standard, join the conversation on our `mailing list`_ or `Gitter`_. 16 | 17 | If you want to learn more about the underlying API for your platform, visit the `source code`_. 18 | 19 | If you would like to implement OpenTracing in your project and need help, feel free to send us a note at `community@opentracing.io`_. 20 | 21 | .. _The OpenTracing Project: http://opentracing.io/ 22 | .. _source code: https://github.com/opentracing/ 23 | .. _mailing list: http://opentracing.us13.list-manage.com/subscribe?u=180afe03860541dae59e84153&id=19117aa6cd 24 | .. _Gitter: https://gitter.im/opentracing/public 25 | .. _community@opentracing.io: community@opentracing.io 26 | -------------------------------------------------------------------------------- /python/grpc_opentracing/_utilities.py: -------------------------------------------------------------------------------- 1 | """Internal utilities for gRPC OpenTracing.""" 2 | 3 | import collections 4 | import grpc_opentracing 5 | 6 | 7 | class RpcInfo(grpc_opentracing.RpcInfo): 8 | 9 | def __init__(self, 10 | full_method=None, 11 | metadata=None, 12 | timeout=None, 13 | request=None, 14 | response=None, 15 | error=None): 16 | self.full_method = full_method 17 | self.metadata = metadata 18 | self.timeout = timeout 19 | self.request = request 20 | self.response = response 21 | self.error = error 22 | 23 | 24 | def get_method_type(is_client_stream, is_server_stream): 25 | if is_client_stream and is_server_stream: 26 | return 'BIDI_STREAMING' 27 | elif is_client_stream: 28 | return 'CLIENT_STREAMING' 29 | elif is_server_stream: 30 | return 'SERVER_STREAMING' 31 | else: 32 | return 'UNARY' 33 | 34 | 35 | def get_deadline_millis(timeout): 36 | if timeout is None: 37 | return 'None' 38 | return str(int(round(timeout * 1000))) 39 | 40 | 41 | class _RequestLoggingIterator(object): 42 | 43 | def __init__(self, request_iterator, span): 44 | self._request_iterator = request_iterator 45 | self._span = span 46 | 47 | def __iter__(self): 48 | return self 49 | 50 | def next(self): 51 | request = next(self._request_iterator) 52 | self._span.log_kv({'request': request}) 53 | return request 54 | 55 | def __next__(self): 56 | return self.next() 57 | 58 | 59 | def log_or_wrap_request_or_iterator(span, is_client_stream, 60 | request_or_iterator): 61 | if is_client_stream: 62 | return _RequestLoggingIterator(request_or_iterator, span) 63 | else: 64 | span.log_kv({'request': request_or_iterator}) 65 | return request_or_iterator 66 | -------------------------------------------------------------------------------- /go/otgrpc/errors.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "github.com/opentracing/opentracing-go" 5 | "github.com/opentracing/opentracing-go/ext" 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | // A Class is a set of types of outcomes (including errors) that will often 11 | // be handled in the same way. 12 | type Class string 13 | 14 | const ( 15 | Unknown Class = "0xx" 16 | // Success represents outcomes that achieved the desired results. 17 | Success Class = "2xx" 18 | // ClientError represents errors that were the client's fault. 19 | ClientError Class = "4xx" 20 | // ServerError represents errors that were the server's fault. 21 | ServerError Class = "5xx" 22 | ) 23 | 24 | // ErrorClass returns the class of the given error 25 | func ErrorClass(err error) Class { 26 | if s, ok := status.FromError(err); ok { 27 | switch s.Code() { 28 | // Success or "success" 29 | case codes.OK, codes.Canceled: 30 | return Success 31 | 32 | // Client errors 33 | case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, 34 | codes.PermissionDenied, codes.Unauthenticated, codes.FailedPrecondition, 35 | codes.OutOfRange: 36 | return ClientError 37 | 38 | // Server errors 39 | case codes.DeadlineExceeded, codes.ResourceExhausted, codes.Aborted, 40 | codes.Unimplemented, codes.Internal, codes.Unavailable, codes.DataLoss: 41 | return ServerError 42 | 43 | // Not sure 44 | case codes.Unknown: 45 | fallthrough 46 | default: 47 | return Unknown 48 | } 49 | } 50 | return Unknown 51 | } 52 | 53 | // SetSpanTags sets one or more tags on the given span according to the 54 | // error. 55 | func SetSpanTags(span opentracing.Span, err error, client bool) { 56 | c := ErrorClass(err) 57 | code := codes.Unknown 58 | if s, ok := status.FromError(err); ok { 59 | code = s.Code() 60 | } 61 | span.SetTag("response_code", code) 62 | span.SetTag("response_class", c) 63 | if err == nil { 64 | return 65 | } 66 | if client || c == ServerError { 67 | ext.Error.Set(span, true) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /python/examples/integration/integration_client.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import time 4 | import argparse 5 | 6 | import grpc 7 | from jaeger_client import Config 8 | 9 | from grpc_opentracing import open_tracing_client_interceptor, ActiveSpanSource 10 | from grpc_opentracing.grpcext import intercept_channel 11 | 12 | import command_line_pb2 13 | 14 | 15 | class FixedActiveSpanSource(ActiveSpanSource): 16 | 17 | def __init__(self): 18 | self.active_span = None 19 | 20 | def get_active_span(self): 21 | return self.active_span 22 | 23 | 24 | def echo(tracer, active_span_source, stub): 25 | with tracer.start_span('command_line_client_span') as span: 26 | active_span_source.active_span = span 27 | response = stub.Echo( 28 | command_line_pb2.CommandRequest(text='Hello, hello')) 29 | print(response.text) 30 | 31 | 32 | def run(): 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument( 35 | '--log_payloads', 36 | action='store_true', 37 | help='log request/response objects to open-tracing spans') 38 | args = parser.parse_args() 39 | 40 | config = Config( 41 | config={ 42 | 'sampler': { 43 | 'type': 'const', 44 | 'param': 1, 45 | }, 46 | 'logging': True, 47 | }, 48 | service_name='integration-client') 49 | tracer = config.initialize_tracer() 50 | active_span_source = FixedActiveSpanSource() 51 | tracer_interceptor = open_tracing_client_interceptor( 52 | tracer, 53 | active_span_source=active_span_source, 54 | log_payloads=args.log_payloads) 55 | channel = grpc.insecure_channel('localhost:50051') 56 | channel = intercept_channel(channel, tracer_interceptor) 57 | stub = command_line_pb2.CommandLineStub(channel) 58 | 59 | echo(tracer, active_span_source, stub) 60 | 61 | time.sleep(2) 62 | tracer.close() 63 | time.sleep(2) 64 | 65 | 66 | if __name__ == '__main__': 67 | run() 68 | -------------------------------------------------------------------------------- /python/examples/integration/integration_server.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import time 4 | import argparse 5 | 6 | import grpc 7 | from concurrent import futures 8 | from jaeger_client import Config 9 | 10 | from grpc_opentracing import open_tracing_server_interceptor 11 | from grpc_opentracing.grpcext import intercept_server 12 | 13 | import command_line_pb2 14 | 15 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24 16 | 17 | 18 | class CommandLine(command_line_pb2.CommandLineServicer): 19 | 20 | def __init__(self, tracer): 21 | self._tracer = tracer 22 | 23 | def Echo(self, request, context): 24 | with self._tracer.start_span( 25 | 'command_line_server_span', 26 | child_of=context.get_active_span().context): 27 | return command_line_pb2.CommandResponse(text=request.text) 28 | 29 | 30 | def serve(): 31 | parser = argparse.ArgumentParser() 32 | parser.add_argument( 33 | '--log_payloads', 34 | action='store_true', 35 | help='log request/response objects to open-tracing spans') 36 | args = parser.parse_args() 37 | 38 | config = Config( 39 | config={ 40 | 'sampler': { 41 | 'type': 'const', 42 | 'param': 1, 43 | }, 44 | 'logging': True, 45 | }, 46 | service_name='integration-server') 47 | tracer = config.initialize_tracer() 48 | tracer_interceptor = open_tracing_server_interceptor( 49 | tracer, log_payloads=args.log_payloads) 50 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 51 | server = intercept_server(server, tracer_interceptor) 52 | 53 | command_line_pb2.add_CommandLineServicer_to_server( 54 | CommandLine(tracer), server) 55 | server.add_insecure_port('[::]:50051') 56 | server.start() 57 | try: 58 | while True: 59 | time.sleep(_ONE_DAY_IN_SECONDS) 60 | except KeyboardInterrupt: 61 | server.stop(0) 62 | 63 | time.sleep(2) 64 | tracer.close() 65 | time.sleep(2) 66 | 67 | 68 | if __name__ == '__main__': 69 | serve() 70 | -------------------------------------------------------------------------------- /java/src/test/java/io/opentracing/contrib/OpenTracingContextKeyTest.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import io.grpc.Context; 8 | import io.opentracing.Span; 9 | import io.opentracing.Tracer; 10 | import io.opentracing.mock.MockTracer; 11 | 12 | public class OpenTracingContextKeyTest { 13 | 14 | Tracer tracer = new MockTracer(); 15 | 16 | @Test 17 | public void TestGetKey() { 18 | Context.Key key = OpenTracingContextKey.getKey(); 19 | assertEquals("Key should have correct name", key.toString(), (OpenTracingContextKey.KEY_NAME)); 20 | } 21 | 22 | @Test 23 | public void TestNoActiveSpan() { 24 | assertEquals("activeSpan() should return null when no span is active", 25 | OpenTracingContextKey.activeSpan(), null); 26 | } 27 | 28 | @Test 29 | public void TestGetActiveSpan() { 30 | Span span = tracer.buildSpan("s0").start(); 31 | Context ctx = Context.current().withValue(OpenTracingContextKey.getKey(), span); 32 | Context previousCtx = ctx.attach(); 33 | 34 | assertEquals(OpenTracingContextKey.activeSpan(), span); 35 | 36 | ctx.detach(previousCtx); 37 | span.finish(); 38 | 39 | assertEquals(OpenTracingContextKey.activeSpan(), null); 40 | } 41 | 42 | @Test 43 | public void TestMultipleContextLayers() { 44 | Span parentSpan = tracer.buildSpan("s0").start(); 45 | Context parentCtx = Context.current().withValue(OpenTracingContextKey.getKey(), parentSpan); 46 | Context previousCtx = parentCtx.attach(); 47 | 48 | Span childSpan = tracer.buildSpan("s1").start(); 49 | Context childCtx = Context.current().withValue(OpenTracingContextKey.getKey(), childSpan); 50 | parentCtx = childCtx.attach(); 51 | 52 | assertEquals(OpenTracingContextKey.activeSpan(), childSpan); 53 | 54 | childCtx.detach(parentCtx); 55 | childSpan.finish(); 56 | 57 | assertEquals(OpenTracingContextKey.activeSpan(), parentSpan); 58 | 59 | parentCtx.detach(previousCtx); 60 | parentSpan.finish(); 61 | 62 | assertEquals(OpenTracingContextKey.activeSpan(), null); 63 | } 64 | 65 | @Test 66 | public void TestWrappedCall() { 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /go/otgrpc/options.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import "github.com/opentracing/opentracing-go" 4 | 5 | // Option instances may be used in OpenTracing(Server|Client)Interceptor 6 | // initialization. 7 | // 8 | // See this post about the "functional options" pattern: 9 | // http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis 10 | type Option func(o *options) 11 | 12 | // LogPayloads returns an Option that tells the OpenTracing instrumentation to 13 | // try to log application payloads in both directions. 14 | func LogPayloads() Option { 15 | return func(o *options) { 16 | o.logPayloads = true 17 | } 18 | } 19 | 20 | // SpanInclusionFunc provides an optional mechanism to decide whether or not 21 | // to trace a given gRPC call. Return true to create a Span and initiate 22 | // tracing, false to not create a Span and not trace. 23 | // 24 | // parentSpanCtx may be nil if no parent could be extraction from either the Go 25 | // context.Context (on the client) or the RPC (on the server). 26 | type SpanInclusionFunc func( 27 | parentSpanCtx opentracing.SpanContext, 28 | method string, 29 | req, resp interface{}) bool 30 | 31 | // IncludingSpans binds a IncludeSpanFunc to the options 32 | func IncludingSpans(inclusionFunc SpanInclusionFunc) Option { 33 | return func(o *options) { 34 | o.inclusionFunc = inclusionFunc 35 | } 36 | } 37 | 38 | // SpanDecoratorFunc provides an (optional) mechanism for otgrpc users to add 39 | // arbitrary tags/logs/etc to the opentracing.Span associated with client 40 | // and/or server RPCs. 41 | type SpanDecoratorFunc func( 42 | span opentracing.Span, 43 | method string, 44 | req, resp interface{}, 45 | grpcError error) 46 | 47 | // SpanDecorator binds a function that decorates gRPC Spans. 48 | func SpanDecorator(decorator SpanDecoratorFunc) Option { 49 | return func(o *options) { 50 | o.decorator = decorator 51 | } 52 | } 53 | 54 | // The internal-only options struct. Obviously overkill at the moment; but will 55 | // scale well as production use dictates other configuration and tuning 56 | // parameters. 57 | type options struct { 58 | logPayloads bool 59 | decorator SpanDecoratorFunc 60 | // May be nil. 61 | inclusionFunc SpanInclusionFunc 62 | } 63 | 64 | // newOptions returns the default options. 65 | func newOptions() *options { 66 | return &options{ 67 | logPayloads: false, 68 | inclusionFunc: nil, 69 | } 70 | } 71 | 72 | func (o *options) apply(opts ...Option) { 73 | for _, opt := range opts { 74 | opt(o) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /python/tests/_tracer.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import enum 3 | 4 | import opentracing 5 | 6 | 7 | @enum.unique 8 | class SpanRelationship(enum.Enum): 9 | NONE = 0 10 | FOLLOWS_FROM = 1 11 | CHILD_OF = 2 12 | 13 | 14 | class _SpanContext(opentracing.SpanContext): 15 | 16 | def __init__(self, identity): 17 | self.identity = identity 18 | 19 | 20 | class _Span(opentracing.Span): 21 | 22 | def __init__(self, tracer, identity, tags=None): 23 | super(_Span, self).__init__(tracer, _SpanContext(identity)) 24 | self._identity = identity 25 | if tags is None: 26 | tags = {} 27 | self._tags = tags 28 | 29 | def set_tag(self, key, value): 30 | self._tags[key] = value 31 | 32 | def get_tag(self, key): 33 | return self._tags.get(key, None) 34 | 35 | 36 | class Tracer(opentracing.Tracer): 37 | 38 | def __init__(self): 39 | super(Tracer, self).__init__() 40 | self._counter = 0 41 | self._spans = {} 42 | self._relationships = defaultdict(lambda: None) 43 | 44 | def start_span(self, 45 | operation_name=None, 46 | child_of=None, 47 | references=None, 48 | tags=None, 49 | start_time=None): 50 | identity = self._counter 51 | self._counter += 1 52 | if child_of is not None: 53 | self._relationships[(child_of.identity, 54 | identity)] = opentracing.ReferenceType.CHILD_OF 55 | if references is not None: 56 | assert child_of is None and len( 57 | references) == 1, 'Only a single reference is supported' 58 | reference_type, span_context = references[0] 59 | self._relationships[(span_context.identity, 60 | identity)] = reference_type 61 | span = _Span(self, identity, tags) 62 | self._spans[identity] = span 63 | return span 64 | 65 | def inject(self, span_context, format, carrier): 66 | if format != opentracing.Format.HTTP_HEADERS and isinstance(carrier, 67 | dict): 68 | raise opentracing.UnsupportedFormatException(format) 69 | carrier['span-identity'] = str(span_context.identity) 70 | 71 | def extract(self, format, carrier): 72 | if format != opentracing.Format.HTTP_HEADERS and isinstance(carrier, 73 | dict): 74 | raise opentracing.UnsupportedFormatException(format) 75 | if 'span-identity' not in carrier: 76 | raise opentracing.SpanContextCorruptedException 77 | return _SpanContext(int(carrier['span-identity'])) 78 | 79 | def get_relationship(self, identity1, identity2): 80 | return self._relationships[(identity1, identity2)] 81 | 82 | def get_span(self, identity): 83 | return self._spans.get(identity, None) 84 | -------------------------------------------------------------------------------- /java/src/testgen/io/opentracing/contrib/HelloWorldProto.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: helloworld.proto 3 | 4 | package io.opentracing.contrib; 5 | 6 | public final class HelloWorldProto { 7 | private HelloWorldProto() {} 8 | public static void registerAllExtensions( 9 | com.google.protobuf.ExtensionRegistry registry) { 10 | } 11 | static final com.google.protobuf.Descriptors.Descriptor 12 | internal_static_helloworld_HelloRequest_descriptor; 13 | static final 14 | com.google.protobuf.GeneratedMessage.FieldAccessorTable 15 | internal_static_helloworld_HelloRequest_fieldAccessorTable; 16 | static final com.google.protobuf.Descriptors.Descriptor 17 | internal_static_helloworld_HelloReply_descriptor; 18 | static final 19 | com.google.protobuf.GeneratedMessage.FieldAccessorTable 20 | internal_static_helloworld_HelloReply_fieldAccessorTable; 21 | 22 | public static com.google.protobuf.Descriptors.FileDescriptor 23 | getDescriptor() { 24 | return descriptor; 25 | } 26 | private static com.google.protobuf.Descriptors.FileDescriptor 27 | descriptor; 28 | static { 29 | java.lang.String[] descriptorData = { 30 | "\n\020helloworld.proto\022\nhelloworld\"\034\n\014HelloR" + 31 | "equest\022\014\n\004name\030\001 \001(\t\"\035\n\nHelloReply\022\017\n\007me" + 32 | "ssage\030\001 \001(\t2I\n\007Greeter\022>\n\010SayHello\022\030.hel" + 33 | "loworld.HelloRequest\032\026.helloworld.HelloR" + 34 | "eply\"\000B6\n\033io.grpc.examples.helloworldB\017H" + 35 | "elloWorldProtoP\001\242\002\003HLWb\006proto3" 36 | }; 37 | com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = 38 | new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { 39 | public com.google.protobuf.ExtensionRegistry assignDescriptors( 40 | com.google.protobuf.Descriptors.FileDescriptor root) { 41 | descriptor = root; 42 | return null; 43 | } 44 | }; 45 | com.google.protobuf.Descriptors.FileDescriptor 46 | .internalBuildGeneratedFileFrom(descriptorData, 47 | new com.google.protobuf.Descriptors.FileDescriptor[] { 48 | }, assigner); 49 | internal_static_helloworld_HelloRequest_descriptor = 50 | getDescriptor().getMessageTypes().get(0); 51 | internal_static_helloworld_HelloRequest_fieldAccessorTable = new 52 | com.google.protobuf.GeneratedMessage.FieldAccessorTable( 53 | internal_static_helloworld_HelloRequest_descriptor, 54 | new java.lang.String[] { "Name", }); 55 | internal_static_helloworld_HelloReply_descriptor = 56 | getDescriptor().getMessageTypes().get(1); 57 | internal_static_helloworld_HelloReply_fieldAccessorTable = new 58 | com.google.protobuf.GeneratedMessage.FieldAccessorTable( 59 | internal_static_helloworld_HelloReply_descriptor, 60 | new java.lang.String[] { "Message", }); 61 | } 62 | 63 | // @@protoc_insertion_point(outer_class_scope) 64 | } 65 | -------------------------------------------------------------------------------- /python/grpc_opentracing/__init__.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import enum 3 | 4 | import six 5 | 6 | import grpc 7 | 8 | 9 | class ActiveSpanSource(six.with_metaclass(abc.ABCMeta)): 10 | """Provides a way to access an the active span.""" 11 | 12 | @abc.abstractmethod 13 | def get_active_span(self): 14 | """Identifies the active span. 15 | 16 | Returns: 17 | An object that implements the opentracing.Span interface. 18 | """ 19 | raise NotImplementedError() 20 | 21 | 22 | class RpcInfo(six.with_metaclass(abc.ABCMeta)): 23 | """Provides information for an RPC call. 24 | 25 | Attributes: 26 | full_method: A string of the full RPC method, i.e., /package.service/method. 27 | metadata: The initial :term:`metadata`. 28 | timeout: The length of time in seconds to wait for the computation to 29 | terminate or be cancelled. 30 | request: The RPC request or None for request-streaming RPCs. 31 | response: The RPC response or None for response-streaming or erroring RPCs. 32 | error: The RPC error or None for successful RPCs. 33 | """ 34 | 35 | 36 | class SpanDecorator(six.with_metaclass(abc.ABCMeta)): 37 | """Provides a mechanism to add arbitrary tags/logs/etc to the 38 | opentracing.Span associated with client and/or server RPCs.""" 39 | 40 | @abc.abstractmethod 41 | def __call__(self, span, rpc_info): 42 | """Customizes an RPC span. 43 | 44 | Args: 45 | span: The client-side or server-side opentracing.Span for the RPC. 46 | rpc_info: An RpcInfo describing the RPC. 47 | """ 48 | raise NotImplementedError() 49 | 50 | 51 | def open_tracing_client_interceptor(tracer, 52 | active_span_source=None, 53 | log_payloads=False, 54 | span_decorator=None): 55 | """Creates an invocation-side interceptor that can be use with gRPC to add 56 | OpenTracing information. 57 | 58 | Args: 59 | tracer: An object implmenting the opentracing.Tracer interface. 60 | active_span_source: An optional ActiveSpanSource to customize how the 61 | active span is determined. 62 | log_payloads: Indicates whether requests should be logged. 63 | span_decorator: An optional SpanDecorator. 64 | 65 | Returns: 66 | An invocation-side interceptor object. 67 | """ 68 | from grpc_opentracing import _client 69 | return _client.OpenTracingClientInterceptor(tracer, active_span_source, 70 | log_payloads, span_decorator) 71 | 72 | 73 | def open_tracing_server_interceptor(tracer, 74 | log_payloads=False, 75 | span_decorator=None): 76 | """Creates a service-side interceptor that can be use with gRPC to add 77 | OpenTracing information. 78 | 79 | Args: 80 | tracer: An object implmenting the opentracing.Tracer interface. 81 | log_payloads: Indicates whether requests should be logged. 82 | span_decorator: An optional SpanDecorator. 83 | 84 | Returns: 85 | A service-side interceptor object. 86 | """ 87 | from grpc_opentracing import _server 88 | return _server.OpenTracingServerInterceptor(tracer, log_payloads, 89 | span_decorator) 90 | 91 | 92 | ################################### __all__ ################################# 93 | 94 | __all__ = ('ActiveSpanSource', 'RpcInfo', 'SpanDecorator', 95 | 'open_tracing_client_interceptor', 96 | 'open_tracing_server_interceptor',) 97 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | description = "grpc-java: OpenTracing" 2 | 3 | group = "io.opentracing.contrib" 4 | version = '0.2.0' 5 | version = '0.3.0' 6 | 7 | apply plugin: 'java' 8 | apply plugin: 'maven' 9 | apply plugin: 'signing' 10 | 11 | sourceSets { 12 | main { 13 | java { 14 | srcDirs = ['src/main/java'] 15 | } 16 | } 17 | test { 18 | java { 19 | srcDirs = ['src/test/java', 'src/testgen'] 20 | } 21 | } 22 | } 23 | 24 | task javadocJar(type: Jar) { 25 | classifier = 'javadoc' 26 | from javadoc 27 | } 28 | 29 | task sourcesJar(type: Jar) { 30 | classifier = 'sources' 31 | from sourceSets.main.allSource 32 | } 33 | 34 | artifacts { 35 | archives jar 36 | 37 | archives javadocJar 38 | archives sourcesJar 39 | } 40 | 41 | signing { 42 | sign configurations.archives 43 | } 44 | 45 | repositories { 46 | mavenCentral() 47 | } 48 | 49 | jar { 50 | baseName 'grpc-opentracing' 51 | version = '0.2.0' 52 | version = '0.3.0' 53 | } 54 | 55 | dependencies { 56 | compile 'io.grpc:grpc-core:1.6.1' 57 | compile 'io.opentracing:opentracing-api:0.30.0' 58 | testCompile 'io.opentracing:opentracing-mock:0.30.0' 59 | testCompile 'io.grpc:grpc-protobuf:1.6.1' 60 | testCompile 'io.grpc:grpc-netty:1.6.1' 61 | testCompile 'io.grpc:grpc-stub:1.6.1' 62 | testCompile 'junit:junit:4.12' 63 | } 64 | 65 | // Allow for automatic promotion and release to Maven Central 66 | buildscript { 67 | repositories { 68 | mavenCentral() 69 | } 70 | dependencies { 71 | classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" 72 | } 73 | } 74 | apply plugin: 'io.codearte.nexus-staging' 75 | 76 | nexusStaging { 77 | packageGroup = "io.opentracing" 78 | } 79 | 80 | uploadArchives { 81 | repositories { 82 | mavenDeployer { 83 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 84 | 85 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 86 | authentication(userName: sonatypeUsername, password: sonatypePassword) 87 | } 88 | 89 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 90 | authentication(userName: sonatypeUsername, password: sonatypePassword) 91 | } 92 | 93 | pom.project { 94 | name 'grpc-opentracing' 95 | packaging 'jar' 96 | // optionally artifactId can be defined here 97 | description 'Provides support for integrating OpenTracing in grpc clients and servers.' 98 | url 'http://www.github.com/grpc-ecosystem/grpc-opentracing' 99 | 100 | scm { 101 | url 'scm:git@github.com:grpc-ecosystem:grpc-opentracing.git' 102 | connection 'scm:git@github.com:grpc-ecosystem/grpc-opentracing.git' 103 | developerConnection 'scm:git@github.com:grpc-ecosystem/grpc-opentracing.git' 104 | } 105 | 106 | licenses { 107 | license { 108 | name 'BSD-3' 109 | url 'https://opensource.org/licenses/BSD-3-Clause' 110 | } 111 | } 112 | 113 | developers { 114 | developer { 115 | name 'Kathy Camenzind' 116 | email 'kcamenzind@lightstep.com' 117 | } 118 | } 119 | 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /python/examples/store/store_server.py: -------------------------------------------------------------------------------- 1 | # A OpenTraced server for a Python service that implements the store interface. 2 | from __future__ import print_function 3 | 4 | import time 5 | import argparse 6 | from collections import defaultdict 7 | 8 | from six import iteritems 9 | 10 | import grpc 11 | from concurrent import futures 12 | from jaeger_client import Config 13 | 14 | from grpc_opentracing import open_tracing_server_interceptor, \ 15 | SpanDecorator 16 | from grpc_opentracing.grpcext import intercept_server 17 | 18 | import store_pb2 19 | 20 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24 21 | 22 | 23 | class Store(store_pb2.StoreServicer): 24 | 25 | def __init__(self): 26 | self._inventory = defaultdict(int) 27 | 28 | def AddItem(self, request, context): 29 | self._inventory[request.name] += 1 30 | return store_pb2.Empty() 31 | 32 | def AddItems(self, request_iter, context): 33 | for request in request_iter: 34 | self._inventory[request.name] += 1 35 | return store_pb2.Empty() 36 | 37 | def RemoveItem(self, request, context): 38 | new_quantity = self._inventory[request.name] - 1 39 | if new_quantity < 0: 40 | return store_pb2.RemoveItemResponse(was_successful=False) 41 | self._inventory[request.name] = new_quantity 42 | return store_pb2.RemoveItemResponse(was_successful=True) 43 | 44 | def RemoveItems(self, request_iter, context): 45 | response = store_pb2.RemoveItemResponse(was_successful=True) 46 | for request in request_iter: 47 | response = self.RemoveItem(request, context) 48 | if not response.was_successful: 49 | break 50 | return response 51 | 52 | def ListInventory(self, request, context): 53 | for name, count in iteritems(self._inventory): 54 | if not count: 55 | continue 56 | else: 57 | yield store_pb2.QuantityResponse(name=name, count=count) 58 | 59 | def QueryQuantity(self, request, context): 60 | count = self._inventory[request.name] 61 | return store_pb2.QuantityResponse(name=request.name, count=count) 62 | 63 | def QueryQuantities(self, request_iter, context): 64 | for request in request_iter: 65 | count = self._inventory[request.name] 66 | yield store_pb2.QuantityResponse(name=request.name, count=count) 67 | 68 | 69 | class StoreSpanDecorator(SpanDecorator): 70 | 71 | def __call__(self, span, rpc_info): 72 | span.set_tag('grpc.method', rpc_info.full_method) 73 | span.set_tag('grpc.headers', str(rpc_info.metadata)) 74 | span.set_tag('grpc.deadline', str(rpc_info.timeout)) 75 | 76 | 77 | def serve(): 78 | parser = argparse.ArgumentParser() 79 | parser.add_argument( 80 | '--log_payloads', 81 | action='store_true', 82 | help='log request/response objects to open-tracing spans') 83 | parser.add_argument( 84 | '--include_grpc_tags', 85 | action='store_true', 86 | help='set gRPC-specific tags on spans') 87 | args = parser.parse_args() 88 | 89 | config = Config( 90 | config={ 91 | 'sampler': { 92 | 'type': 'const', 93 | 'param': 1, 94 | }, 95 | 'logging': True, 96 | }, 97 | service_name='store-server') 98 | tracer = config.initialize_tracer() 99 | span_decorator = None 100 | if args.include_grpc_tags: 101 | span_decorator = StoreSpanDecorator() 102 | tracer_interceptor = open_tracing_server_interceptor( 103 | tracer, log_payloads=args.log_payloads, span_decorator=span_decorator) 104 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 105 | server = intercept_server(server, tracer_interceptor) 106 | 107 | store_pb2.add_StoreServicer_to_server(Store(), server) 108 | server.add_insecure_port('[::]:50051') 109 | server.start() 110 | try: 111 | while True: 112 | time.sleep(_ONE_DAY_IN_SECONDS) 113 | except KeyboardInterrupt: 114 | server.stop(0) 115 | 116 | time.sleep(2) 117 | tracer.close() 118 | time.sleep(2) 119 | 120 | 121 | if __name__ == '__main__': 122 | serve() 123 | -------------------------------------------------------------------------------- /go/otgrpc/server.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "github.com/opentracing/opentracing-go" 5 | "github.com/opentracing/opentracing-go/ext" 6 | "github.com/opentracing/opentracing-go/log" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/metadata" 10 | ) 11 | 12 | // OpenTracingServerInterceptor returns a grpc.UnaryServerInterceptor suitable 13 | // for use in a grpc.NewServer call. 14 | // 15 | // For example: 16 | // 17 | // s := grpc.NewServer( 18 | // ..., // (existing ServerOptions) 19 | // grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer))) 20 | // 21 | // All gRPC server spans will look for an OpenTracing SpanContext in the gRPC 22 | // metadata; if found, the server span will act as the ChildOf that RPC 23 | // SpanContext. 24 | // 25 | // Root or not, the server Span will be embedded in the context.Context for the 26 | // application-specific gRPC handler(s) to access. 27 | func OpenTracingServerInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.UnaryServerInterceptor { 28 | otgrpcOpts := newOptions() 29 | otgrpcOpts.apply(optFuncs...) 30 | return func( 31 | ctx context.Context, 32 | req interface{}, 33 | info *grpc.UnaryServerInfo, 34 | handler grpc.UnaryHandler, 35 | ) (resp interface{}, err error) { 36 | spanContext, err := extractSpanContext(ctx, tracer) 37 | if err != nil && err != opentracing.ErrSpanContextNotFound { 38 | // TODO: establish some sort of error reporting mechanism here. We 39 | // don't know where to put such an error and must rely on Tracer 40 | // implementations to do something appropriate for the time being. 41 | } 42 | if otgrpcOpts.inclusionFunc != nil && 43 | !otgrpcOpts.inclusionFunc(spanContext, info.FullMethod, req, nil) { 44 | return handler(ctx, req) 45 | } 46 | serverSpan := tracer.StartSpan( 47 | info.FullMethod, 48 | ext.RPCServerOption(spanContext), 49 | gRPCComponentTag, 50 | ) 51 | defer serverSpan.Finish() 52 | 53 | ctx = opentracing.ContextWithSpan(ctx, serverSpan) 54 | if otgrpcOpts.logPayloads { 55 | serverSpan.LogFields(log.Object("gRPC request", req)) 56 | } 57 | resp, err = handler(ctx, req) 58 | if err == nil { 59 | if otgrpcOpts.logPayloads { 60 | serverSpan.LogFields(log.Object("gRPC response", resp)) 61 | } 62 | } else { 63 | SetSpanTags(serverSpan, err, false) 64 | serverSpan.LogFields(log.String("event", "error"), log.String("message", err.Error())) 65 | } 66 | if otgrpcOpts.decorator != nil { 67 | otgrpcOpts.decorator(serverSpan, info.FullMethod, req, resp, err) 68 | } 69 | return resp, err 70 | } 71 | } 72 | 73 | // OpenTracingStreamServerInterceptor returns a grpc.StreamServerInterceptor suitable 74 | // for use in a grpc.NewServer call. The interceptor instruments streaming RPCs by 75 | // creating a single span to correspond to the lifetime of the RPC's stream. 76 | // 77 | // For example: 78 | // 79 | // s := grpc.NewServer( 80 | // ..., // (existing ServerOptions) 81 | // grpc.StreamInterceptor(otgrpc.OpenTracingStreamServerInterceptor(tracer))) 82 | // 83 | // All gRPC server spans will look for an OpenTracing SpanContext in the gRPC 84 | // metadata; if found, the server span will act as the ChildOf that RPC 85 | // SpanContext. 86 | // 87 | // Root or not, the server Span will be embedded in the context.Context for the 88 | // application-specific gRPC handler(s) to access. 89 | func OpenTracingStreamServerInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.StreamServerInterceptor { 90 | otgrpcOpts := newOptions() 91 | otgrpcOpts.apply(optFuncs...) 92 | return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 93 | spanContext, err := extractSpanContext(ss.Context(), tracer) 94 | if err != nil && err != opentracing.ErrSpanContextNotFound { 95 | // TODO: establish some sort of error reporting mechanism here. We 96 | // don't know where to put such an error and must rely on Tracer 97 | // implementations to do something appropriate for the time being. 98 | } 99 | if otgrpcOpts.inclusionFunc != nil && 100 | !otgrpcOpts.inclusionFunc(spanContext, info.FullMethod, nil, nil) { 101 | return handler(srv, ss) 102 | } 103 | 104 | serverSpan := tracer.StartSpan( 105 | info.FullMethod, 106 | ext.RPCServerOption(spanContext), 107 | gRPCComponentTag, 108 | ) 109 | defer serverSpan.Finish() 110 | ss = &openTracingServerStream{ 111 | ServerStream: ss, 112 | ctx: opentracing.ContextWithSpan(ss.Context(), serverSpan), 113 | } 114 | err = handler(srv, ss) 115 | if err != nil { 116 | SetSpanTags(serverSpan, err, false) 117 | serverSpan.LogFields(log.String("event", "error"), log.String("message", err.Error())) 118 | } 119 | if otgrpcOpts.decorator != nil { 120 | otgrpcOpts.decorator(serverSpan, info.FullMethod, nil, nil, err) 121 | } 122 | return err 123 | } 124 | } 125 | 126 | type openTracingServerStream struct { 127 | grpc.ServerStream 128 | ctx context.Context 129 | } 130 | 131 | func (ss *openTracingServerStream) Context() context.Context { 132 | return ss.ctx 133 | } 134 | 135 | func extractSpanContext(ctx context.Context, tracer opentracing.Tracer) (opentracing.SpanContext, error) { 136 | md, ok := metadata.FromIncomingContext(ctx) 137 | if !ok { 138 | md = metadata.New(nil) 139 | } 140 | return tracer.Extract(opentracing.HTTPHeaders, metadataReaderWriter{md}) 141 | } 142 | -------------------------------------------------------------------------------- /python/examples/store/store_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | from grpc.framework.common import cardinality 4 | from grpc.framework.interfaces.face import utilities as face_utilities 5 | 6 | import store_pb2 as store__pb2 7 | 8 | 9 | class StoreStub(object): 10 | 11 | def __init__(self, channel): 12 | """Constructor. 13 | 14 | Args: 15 | channel: A grpc.Channel. 16 | """ 17 | self.AddItem = channel.unary_unary( 18 | '/store.Store/AddItem', 19 | request_serializer=store__pb2.AddItemRequest.SerializeToString, 20 | response_deserializer=store__pb2.Empty.FromString, 21 | ) 22 | self.AddItems = channel.stream_unary( 23 | '/store.Store/AddItems', 24 | request_serializer=store__pb2.AddItemRequest.SerializeToString, 25 | response_deserializer=store__pb2.Empty.FromString, 26 | ) 27 | self.RemoveItem = channel.unary_unary( 28 | '/store.Store/RemoveItem', 29 | request_serializer=store__pb2.RemoveItemRequest.SerializeToString, 30 | response_deserializer=store__pb2.RemoveItemResponse.FromString, 31 | ) 32 | self.RemoveItems = channel.stream_unary( 33 | '/store.Store/RemoveItems', 34 | request_serializer=store__pb2.RemoveItemRequest.SerializeToString, 35 | response_deserializer=store__pb2.RemoveItemResponse.FromString, 36 | ) 37 | self.ListInventory = channel.unary_stream( 38 | '/store.Store/ListInventory', 39 | request_serializer=store__pb2.Empty.SerializeToString, 40 | response_deserializer=store__pb2.QuantityResponse.FromString, 41 | ) 42 | self.QueryQuantity = channel.unary_unary( 43 | '/store.Store/QueryQuantity', 44 | request_serializer=store__pb2.QueryItemRequest.SerializeToString, 45 | response_deserializer=store__pb2.QuantityResponse.FromString, 46 | ) 47 | self.QueryQuantities = channel.stream_stream( 48 | '/store.Store/QueryQuantities', 49 | request_serializer=store__pb2.QueryItemRequest.SerializeToString, 50 | response_deserializer=store__pb2.QuantityResponse.FromString, 51 | ) 52 | 53 | 54 | class StoreServicer(object): 55 | 56 | def AddItem(self, request, context): 57 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 58 | context.set_details('Method not implemented!') 59 | raise NotImplementedError('Method not implemented!') 60 | 61 | def AddItems(self, request_iterator, context): 62 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 63 | context.set_details('Method not implemented!') 64 | raise NotImplementedError('Method not implemented!') 65 | 66 | def RemoveItem(self, request, context): 67 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 68 | context.set_details('Method not implemented!') 69 | raise NotImplementedError('Method not implemented!') 70 | 71 | def RemoveItems(self, request_iterator, context): 72 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 73 | context.set_details('Method not implemented!') 74 | raise NotImplementedError('Method not implemented!') 75 | 76 | def ListInventory(self, request, context): 77 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 78 | context.set_details('Method not implemented!') 79 | raise NotImplementedError('Method not implemented!') 80 | 81 | def QueryQuantity(self, request, context): 82 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 83 | context.set_details('Method not implemented!') 84 | raise NotImplementedError('Method not implemented!') 85 | 86 | def QueryQuantities(self, request_iterator, context): 87 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 88 | context.set_details('Method not implemented!') 89 | raise NotImplementedError('Method not implemented!') 90 | 91 | 92 | def add_StoreServicer_to_server(servicer, server): 93 | rpc_method_handlers = { 94 | 'AddItem': grpc.unary_unary_rpc_method_handler( 95 | servicer.AddItem, 96 | request_deserializer=store__pb2.AddItemRequest.FromString, 97 | response_serializer=store__pb2.Empty.SerializeToString, 98 | ), 99 | 'AddItems': grpc.stream_unary_rpc_method_handler( 100 | servicer.AddItems, 101 | request_deserializer=store__pb2.AddItemRequest.FromString, 102 | response_serializer=store__pb2.Empty.SerializeToString, 103 | ), 104 | 'RemoveItem': grpc.unary_unary_rpc_method_handler( 105 | servicer.RemoveItem, 106 | request_deserializer=store__pb2.RemoveItemRequest.FromString, 107 | response_serializer=store__pb2.RemoveItemResponse.SerializeToString, 108 | ), 109 | 'RemoveItems': grpc.stream_unary_rpc_method_handler( 110 | servicer.RemoveItems, 111 | request_deserializer=store__pb2.RemoveItemRequest.FromString, 112 | response_serializer=store__pb2.RemoveItemResponse.SerializeToString, 113 | ), 114 | 'ListInventory': grpc.unary_stream_rpc_method_handler( 115 | servicer.ListInventory, 116 | request_deserializer=store__pb2.Empty.FromString, 117 | response_serializer=store__pb2.QuantityResponse.SerializeToString, 118 | ), 119 | 'QueryQuantity': grpc.unary_unary_rpc_method_handler( 120 | servicer.QueryQuantity, 121 | request_deserializer=store__pb2.QueryItemRequest.FromString, 122 | response_serializer=store__pb2.QuantityResponse.SerializeToString, 123 | ), 124 | 'QueryQuantities': grpc.stream_stream_rpc_method_handler( 125 | servicer.QueryQuantities, 126 | request_deserializer=store__pb2.QueryItemRequest.FromString, 127 | response_serializer=store__pb2.QuantityResponse.SerializeToString, 128 | ), 129 | } 130 | generic_handler = grpc.method_handlers_generic_handler( 131 | 'store.Store', rpc_method_handlers) 132 | server.add_generic_rpc_handlers((generic_handler,)) 133 | -------------------------------------------------------------------------------- /python/grpc_opentracing/grpcext/__init__.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | import six 4 | 5 | 6 | class UnaryClientInfo(six.with_metaclass(abc.ABCMeta)): 7 | """Consists of various information about a unary RPC on the invocation-side. 8 | 9 | Attributes: 10 | full_method: A string of the full RPC method, i.e., /package.service/method. 11 | timeout: The length of time in seconds to wait for the computation to 12 | terminate or be cancelled, or None if this method should block until 13 | the computation is terminated or is cancelled no matter how long that 14 | takes. 15 | """ 16 | 17 | 18 | class StreamClientInfo(six.with_metaclass(abc.ABCMeta)): 19 | """Consists of various information about a stream RPC on the invocation-side. 20 | 21 | Attributes: 22 | full_method: A string of the full RPC method, i.e., /package.service/method. 23 | is_client_stream: Indicates whether the RPC is client-streaming. 24 | is_server_stream: Indicates whether the RPC is server-streaming. 25 | timeout: The length of time in seconds to wait for the computation to 26 | terminate or be cancelled, or None if this method should block until 27 | the computation is terminated or is cancelled no matter how long that 28 | takes. 29 | """ 30 | 31 | 32 | class UnaryClientInterceptor(six.with_metaclass(abc.ABCMeta)): 33 | """Affords intercepting unary-unary RPCs on the invocation-side.""" 34 | 35 | @abc.abstractmethod 36 | def intercept_unary(self, request, metadata, client_info, invoker): 37 | """Intercepts unary-unary RPCs on the invocation-side. 38 | 39 | Args: 40 | request: The request value for the RPC. 41 | metadata: Optional :term:`metadata` to be transmitted to the 42 | service-side of the RPC. 43 | client_info: A UnaryClientInfo containing various information about 44 | the RPC. 45 | invoker: The handler to complete the RPC on the client. It is the 46 | interceptor's responsibility to call it. 47 | 48 | Returns: 49 | The result from calling invoker(request, metadata). 50 | """ 51 | raise NotImplementedError() 52 | 53 | 54 | class StreamClientInterceptor(six.with_metaclass(abc.ABCMeta)): 55 | """Affords intercepting stream RPCs on the invocation-side.""" 56 | 57 | @abc.abstractmethod 58 | def intercept_stream(self, request_or_iterator, metadata, client_info, 59 | invoker): 60 | """Intercepts stream RPCs on the invocation-side. 61 | 62 | Args: 63 | request_or_iterator: The request value for the RPC if 64 | `client_info.is_client_stream` is `false`; otherwise, an iterator of 65 | request values. 66 | metadata: Optional :term:`metadata` to be transmitted to the service-side 67 | of the RPC. 68 | client_info: A StreamClientInfo containing various information about 69 | the RPC. 70 | invoker: The handler to complete the RPC on the client. It is the 71 | interceptor's responsibility to call it. 72 | 73 | Returns: 74 | The result from calling invoker(metadata). 75 | """ 76 | raise NotImplementedError() 77 | 78 | 79 | def intercept_channel(channel, *interceptors): 80 | """Creates an intercepted channel. 81 | 82 | Args: 83 | channel: A Channel. 84 | interceptors: Zero or more UnaryClientInterceptors or 85 | StreamClientInterceptors 86 | 87 | Returns: 88 | A Channel. 89 | 90 | Raises: 91 | TypeError: If an interceptor derives from neither UnaryClientInterceptor 92 | nor StreamClientInterceptor. 93 | """ 94 | from grpc_opentracing.grpcext import _interceptor 95 | return _interceptor.intercept_channel(channel, *interceptors) 96 | 97 | 98 | class UnaryServerInfo(six.with_metaclass(abc.ABCMeta)): 99 | """Consists of various information about a unary RPC on the service-side. 100 | 101 | Attributes: 102 | full_method: A string of the full RPC method, i.e., /package.service/method. 103 | """ 104 | 105 | 106 | class StreamServerInfo(six.with_metaclass(abc.ABCMeta)): 107 | """Consists of various information about a stream RPC on the service-side. 108 | 109 | Attributes: 110 | full_method: A string of the full RPC method, i.e., /package.service/method. 111 | is_client_stream: Indicates whether the RPC is client-streaming. 112 | is_server_stream: Indicates whether the RPC is server-streaming. 113 | """ 114 | 115 | 116 | class UnaryServerInterceptor(six.with_metaclass(abc.ABCMeta)): 117 | """Affords intercepting unary-unary RPCs on the service-side.""" 118 | 119 | @abc.abstractmethod 120 | def intercept_unary(self, request, servicer_context, server_info, handler): 121 | """Intercepts unary-unary RPCs on the service-side. 122 | 123 | Args: 124 | request: The request value for the RPC. 125 | servicer_context: A ServicerContext. 126 | server_info: A UnaryServerInfo containing various information about 127 | the RPC. 128 | handler: The handler to complete the RPC on the server. It is the 129 | interceptor's responsibility to call it. 130 | 131 | Returns: 132 | The result from calling handler(request, servicer_context). 133 | """ 134 | raise NotImplementedError() 135 | 136 | 137 | class StreamServerInterceptor(six.with_metaclass(abc.ABCMeta)): 138 | """Affords intercepting stream RPCs on the service-side.""" 139 | 140 | @abc.abstractmethod 141 | def intercept_stream(self, request_or_iterator, servicer_context, 142 | server_info, handler): 143 | """Intercepts stream RPCs on the service-side. 144 | 145 | Args: 146 | request_or_iterator: The request value for the RPC if 147 | `server_info.is_client_stream` is `False`; otherwise, an iterator of 148 | request values. 149 | servicer_context: A ServicerContext. 150 | server_info: A StreamServerInfo containing various information about 151 | the RPC. 152 | handler: The handler to complete the RPC on the server. It is the 153 | interceptor's responsibility to call it. 154 | 155 | Returns: 156 | The result from calling handler(servicer_context). 157 | """ 158 | raise NotImplementedError() 159 | 160 | 161 | def intercept_server(server, *interceptors): 162 | """Creates an intercepted server. 163 | 164 | Args: 165 | server: A Server. 166 | interceptors: Zero or more UnaryServerInterceptors or 167 | StreamServerInterceptors 168 | 169 | Returns: 170 | A Server. 171 | 172 | Raises: 173 | TypeError: If an interceptor derives from neither UnaryServerInterceptor 174 | nor StreamServerInterceptor. 175 | """ 176 | from grpc_opentracing.grpcext import _interceptor 177 | return _interceptor.intercept_server(server, *interceptors) 178 | 179 | 180 | ################################### __all__ ################################# 181 | 182 | __all__ = ('UnaryClientInterceptor', 'StreamClientInfo', 183 | 'StreamClientInterceptor', 'UnaryServerInfo', 'StreamServerInfo', 184 | 'UnaryServerInterceptor', 'StreamServerInterceptor', 185 | 'intercept_channel', 'intercept_server',) 186 | -------------------------------------------------------------------------------- /python/tests/test_interceptor.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import grpc 4 | from grpc_opentracing import grpcext 5 | 6 | from _service import Service 7 | 8 | 9 | class ClientInterceptor(grpcext.UnaryClientInterceptor, 10 | grpcext.StreamClientInterceptor): 11 | 12 | def __init__(self): 13 | self.intercepted = False 14 | 15 | def intercept_unary(self, request, metadata, client_info, invoker): 16 | self.intercepted = True 17 | return invoker(request, metadata) 18 | 19 | def intercept_stream(self, request_or_iterator, metadata, client_info, 20 | invoker): 21 | self.intercepted = True 22 | return invoker(request_or_iterator, metadata) 23 | 24 | 25 | class ServerInterceptor(grpcext.UnaryServerInterceptor, 26 | grpcext.StreamServerInterceptor): 27 | 28 | def __init__(self): 29 | self.intercepted = False 30 | 31 | def intercept_unary(self, request, servicer_context, server_info, handler): 32 | self.intercepted = True 33 | return handler(request, servicer_context) 34 | 35 | def intercept_stream(self, request_or_iterator, servicer_context, 36 | server_info, handler): 37 | self.intercepted = True 38 | return handler(request_or_iterator, servicer_context) 39 | 40 | 41 | class InterceptorTest(unittest.TestCase): 42 | """Test that RPC calls are intercepted.""" 43 | 44 | def setUp(self): 45 | self._client_interceptor = ClientInterceptor() 46 | self._server_interceptor = ServerInterceptor() 47 | self._service = Service([self._client_interceptor], 48 | [self._server_interceptor]) 49 | 50 | def testUnaryUnaryInterception(self): 51 | multi_callable = self._service.unary_unary_multi_callable 52 | request = b'\x01' 53 | expected_response = self._service.handler.handle_unary_unary(request, 54 | None) 55 | response = multi_callable(request) 56 | 57 | self.assertEqual(response, expected_response) 58 | self.assertTrue(self._client_interceptor.intercepted) 59 | self.assertTrue(self._server_interceptor.intercepted) 60 | 61 | def testUnaryUnaryInterceptionWithCall(self): 62 | multi_callable = self._service.unary_unary_multi_callable 63 | request = b'\x01' 64 | expected_response = self._service.handler.handle_unary_unary(request, 65 | None) 66 | response, call = multi_callable.with_call(request) 67 | 68 | self.assertEqual(response, expected_response) 69 | self.assertIs(grpc.StatusCode.OK, call.code()) 70 | self.assertTrue(self._client_interceptor.intercepted) 71 | self.assertTrue(self._server_interceptor.intercepted) 72 | 73 | def testUnaryUnaryInterceptionFuture(self): 74 | multi_callable = self._service.unary_unary_multi_callable 75 | request = b'\x01' 76 | expected_response = self._service.handler.handle_unary_unary(request, 77 | None) 78 | response = multi_callable.future(request).result() 79 | 80 | self.assertEqual(response, expected_response) 81 | self.assertTrue(self._client_interceptor.intercepted) 82 | self.assertTrue(self._server_interceptor.intercepted) 83 | 84 | def testUnaryStreamInterception(self): 85 | multi_callable = self._service.unary_stream_multi_callable 86 | request = b'\x01' 87 | expected_response = self._service.handler.handle_unary_stream(request, 88 | None) 89 | response = multi_callable(request) 90 | 91 | self.assertEqual(list(response), list(expected_response)) 92 | self.assertTrue(self._client_interceptor.intercepted) 93 | self.assertTrue(self._server_interceptor.intercepted) 94 | 95 | def testStreamUnaryInterception(self): 96 | multi_callable = self._service.stream_unary_multi_callable 97 | requests = [b'\x01', b'\x02'] 98 | expected_response = self._service.handler.handle_stream_unary( 99 | iter(requests), None) 100 | response = multi_callable(iter(requests)) 101 | 102 | self.assertEqual(response, expected_response) 103 | self.assertTrue(self._client_interceptor.intercepted) 104 | self.assertTrue(self._server_interceptor.intercepted) 105 | 106 | def testStreamUnaryInterceptionWithCall(self): 107 | multi_callable = self._service.stream_unary_multi_callable 108 | requests = [b'\x01', b'\x02'] 109 | expected_response = self._service.handler.handle_stream_unary( 110 | iter(requests), None) 111 | response, call = multi_callable.with_call(iter(requests)) 112 | 113 | self.assertEqual(response, expected_response) 114 | self.assertIs(grpc.StatusCode.OK, call.code()) 115 | self.assertTrue(self._client_interceptor.intercepted) 116 | self.assertTrue(self._server_interceptor.intercepted) 117 | 118 | def testStreamUnaryInterceptionFuture(self): 119 | multi_callable = self._service.stream_unary_multi_callable 120 | requests = [b'\x01', b'\x02'] 121 | expected_response = self._service.handler.handle_stream_unary( 122 | iter(requests), None) 123 | response = multi_callable.future(iter(requests)).result() 124 | 125 | self.assertEqual(response, expected_response) 126 | self.assertTrue(self._client_interceptor.intercepted) 127 | self.assertTrue(self._server_interceptor.intercepted) 128 | 129 | def testStreamStreamInterception(self): 130 | multi_callable = self._service.stream_stream_multi_callable 131 | requests = [b'\x01', b'\x02'] 132 | expected_response = self._service.handler.handle_stream_stream( 133 | iter(requests), None) 134 | response = multi_callable(iter(requests)) 135 | 136 | self.assertEqual(list(response), list(expected_response)) 137 | self.assertTrue(self._client_interceptor.intercepted) 138 | self.assertTrue(self._server_interceptor.intercepted) 139 | 140 | 141 | class MultiInterceptorTest(unittest.TestCase): 142 | """Test that you can chain multiple interceptors together.""" 143 | 144 | def setUp(self): 145 | self._client_interceptors = [ClientInterceptor(), ClientInterceptor()] 146 | self._server_interceptors = [ServerInterceptor(), ServerInterceptor()] 147 | self._service = Service(self._client_interceptors, 148 | self._server_interceptors) 149 | 150 | def _clear(self): 151 | for client_interceptor in self._client_interceptors: 152 | client_interceptor.intercepted = False 153 | 154 | for server_interceptor in self._server_interceptors: 155 | server_interceptor.intercepted = False 156 | 157 | def testUnaryUnaryMultiInterception(self): 158 | multi_callable = self._service.unary_unary_multi_callable 159 | request = b'\x01' 160 | expected_response = self._service.handler.handle_unary_unary(request, 161 | None) 162 | response = multi_callable(request) 163 | 164 | self.assertEqual(response, expected_response) 165 | for client_interceptor in self._client_interceptors: 166 | self.assertTrue(client_interceptor.intercepted) 167 | for server_interceptor in self._server_interceptors: 168 | self.assertTrue(server_interceptor.intercepted) 169 | -------------------------------------------------------------------------------- /python/tests/_service.py: -------------------------------------------------------------------------------- 1 | """Creates a simple service on top of gRPC for testing.""" 2 | 3 | from builtins import input, range 4 | 5 | import grpc 6 | from grpc.framework.foundation import logging_pool 7 | from grpc_opentracing import grpcext 8 | 9 | _SERIALIZE_REQUEST = lambda bytestring: bytestring 10 | _DESERIALIZE_REQUEST = lambda bytestring: bytestring 11 | _SERIALIZE_RESPONSE = lambda bytestring: bytestring 12 | _DESERIALIZE_RESPONSE = lambda bytestring: bytestring 13 | 14 | _UNARY_UNARY = '/test/UnaryUnary' 15 | _UNARY_STREAM = '/test/UnaryStream' 16 | _STREAM_UNARY = '/test/StreamUnary' 17 | _STREAM_STREAM = '/test/StreamStream' 18 | 19 | _STREAM_LENGTH = 5 20 | 21 | 22 | class _MethodHandler(grpc.RpcMethodHandler): 23 | 24 | def __init__(self, 25 | request_streaming=False, 26 | response_streaming=False, 27 | unary_unary=None, 28 | unary_stream=None, 29 | stream_unary=None, 30 | stream_stream=None): 31 | self.request_streaming = request_streaming 32 | self.response_streaming = response_streaming 33 | self.request_deserializer = _DESERIALIZE_REQUEST 34 | self.response_serializer = _SERIALIZE_RESPONSE 35 | self.unary_unary = unary_unary 36 | self.unary_stream = unary_stream 37 | self.stream_unary = stream_unary 38 | self.stream_stream = stream_stream 39 | 40 | 41 | class Handler(object): 42 | 43 | def __init__(self): 44 | self.invocation_metadata = None 45 | self.trailing_metadata = None 46 | 47 | def handle_unary_unary(self, request, servicer_context): 48 | if servicer_context is not None: 49 | self.invocation_metadata = servicer_context.invocation_metadata() 50 | if self.trailing_metadata is not None: 51 | servicer_context.set_trailing_metadata(self.trailing_metadata) 52 | return request 53 | 54 | def handle_unary_stream(self, request, servicer_context): 55 | if servicer_context is not None: 56 | self.invocation_metadata = servicer_context.invocation_metadata() 57 | if self.trailing_metadata is not None: 58 | servicer_context.set_trailing_metadata(self.trailing_metadata) 59 | for _ in range(_STREAM_LENGTH): 60 | yield request 61 | 62 | def handle_stream_unary(self, request_iterator, servicer_context): 63 | if servicer_context is not None: 64 | self.invocation_metadata = servicer_context.invocation_metadata() 65 | if self.trailing_metadata is not None: 66 | servicer_context.set_trailing_metadata(self.trailing_metadata) 67 | return b''.join(list(request_iterator)) 68 | 69 | def handle_stream_stream(self, request_iterator, servicer_context): 70 | if servicer_context is not None: 71 | self.invocation_metadata = servicer_context.invocation_metadata() 72 | if self.trailing_metadata is not None: 73 | servicer_context.set_trailing_metadata(self.trailing_metadata) 74 | for request in request_iterator: 75 | yield request 76 | 77 | 78 | def _set_error_code(servicer_context): 79 | servicer_context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 80 | servicer_context.set_details('ErroringHandler') 81 | 82 | 83 | class ErroringHandler(Handler): 84 | 85 | def __init__(self): 86 | super(ErroringHandler, self).__init__() 87 | 88 | def handle_unary_unary(self, request, servicer_context): 89 | _set_error_code(servicer_context) 90 | return super(ErroringHandler, self).handle_unary_unary(request, 91 | servicer_context) 92 | 93 | def handle_unary_stream(self, request, servicer_context): 94 | _set_error_code(servicer_context) 95 | return super(ErroringHandler, self).handle_unary_stream( 96 | request, servicer_context) 97 | 98 | def handle_stream_unary(self, request_iterator, servicer_context): 99 | _set_error_code(servicer_context) 100 | return super(ErroringHandler, self).handle_stream_unary( 101 | request_iterator, servicer_context) 102 | 103 | def handle_stream_stream(self, request_iterator, servicer_context): 104 | _set_error_code(servicer_context) 105 | return super(ErroringHandler, self).handle_stream_stream( 106 | request_iterator, servicer_context) 107 | 108 | 109 | class ExceptionErroringHandler(Handler): 110 | 111 | def __init__(self): 112 | super(ExceptionErroringHandler, self).__init__() 113 | 114 | def handle_unary_unary(self, request, servicer_context): 115 | raise IndexError() 116 | 117 | def handle_unary_stream(self, request, servicer_context): 118 | raise IndexError() 119 | 120 | def handle_stream_unary(self, request_iterator, servicer_context): 121 | raise IndexError() 122 | 123 | def handle_stream_stream(self, request_iterator, servicer_context): 124 | raise IndexError() 125 | 126 | 127 | class _GenericHandler(grpc.GenericRpcHandler): 128 | 129 | def __init__(self, handler): 130 | self._handler = handler 131 | 132 | def service(self, handler_call_details): 133 | method = handler_call_details.method 134 | if method == _UNARY_UNARY: 135 | return _MethodHandler(unary_unary=self._handler.handle_unary_unary) 136 | elif method == _UNARY_STREAM: 137 | return _MethodHandler( 138 | response_streaming=True, 139 | unary_stream=self._handler.handle_unary_stream) 140 | elif method == _STREAM_UNARY: 141 | return _MethodHandler( 142 | request_streaming=True, 143 | stream_unary=self._handler.handle_stream_unary) 144 | elif method == _STREAM_STREAM: 145 | return _MethodHandler( 146 | request_streaming=True, 147 | response_streaming=True, 148 | stream_stream=self._handler.handle_stream_stream) 149 | else: 150 | return None 151 | 152 | 153 | class Service(object): 154 | 155 | def __init__(self, 156 | client_interceptors, 157 | server_interceptors, 158 | handler=Handler()): 159 | self.handler = handler 160 | self._server_pool = logging_pool.pool(2) 161 | self._server = grpcext.intercept_server( 162 | grpc.server(self._server_pool), *server_interceptors) 163 | port = self._server.add_insecure_port('[::]:0') 164 | self._server.add_generic_rpc_handlers((_GenericHandler(self.handler),)) 165 | self._server.start() 166 | self.channel = grpcext.intercept_channel( 167 | grpc.insecure_channel('localhost:%d' % port), *client_interceptors) 168 | 169 | @property 170 | def unary_unary_multi_callable(self): 171 | return self.channel.unary_unary(_UNARY_UNARY) 172 | 173 | @property 174 | def unary_stream_multi_callable(self): 175 | return self.channel.unary_stream( 176 | _UNARY_STREAM, 177 | request_serializer=_SERIALIZE_REQUEST, 178 | response_deserializer=_DESERIALIZE_RESPONSE) 179 | 180 | @property 181 | def stream_unary_multi_callable(self): 182 | return self.channel.stream_unary( 183 | _STREAM_UNARY, 184 | request_serializer=_SERIALIZE_REQUEST, 185 | response_deserializer=_DESERIALIZE_RESPONSE) 186 | 187 | @property 188 | def stream_stream_multi_callable(self): 189 | return self.channel.stream_stream( 190 | _STREAM_STREAM, 191 | request_serializer=_SERIALIZE_REQUEST, 192 | response_deserializer=_DESERIALIZE_RESPONSE) 193 | -------------------------------------------------------------------------------- /python/examples/store/store_client.py: -------------------------------------------------------------------------------- 1 | # A OpenTraced client for a Python service that implements the store interface. 2 | from __future__ import print_function 3 | 4 | import time 5 | import argparse 6 | from builtins import input, range 7 | 8 | import grpc 9 | from jaeger_client import Config 10 | 11 | from grpc_opentracing import open_tracing_client_interceptor, \ 12 | SpanDecorator 13 | from grpc_opentracing.grpcext import intercept_channel 14 | 15 | import store_pb2 16 | 17 | 18 | class CommandExecuter(object): 19 | 20 | def __init__(self, stub): 21 | self._stub = stub 22 | 23 | def _execute_rpc(self, method, via, timeout, request_or_iterator): 24 | if via == 'future': 25 | result = getattr(self._stub, method).future(request_or_iterator, 26 | timeout) 27 | return result.result() 28 | elif via == 'with_call': 29 | return getattr(self._stub, method).with_call(request_or_iterator, 30 | timeout)[0] 31 | else: 32 | return getattr(self._stub, method)(request_or_iterator, timeout) 33 | 34 | def do_stock_item(self, via, timeout, arguments): 35 | if len(arguments) != 1: 36 | print('must input a single item') 37 | return 38 | request = store_pb2.AddItemRequest(name=arguments[0]) 39 | self._execute_rpc('AddItem', via, timeout, request) 40 | 41 | def do_stock_items(self, via, timeout, arguments): 42 | if not arguments: 43 | print('must input at least one item') 44 | return 45 | requests = [store_pb2.AddItemRequest(name=name) for name in arguments] 46 | self._execute_rpc('AddItems', via, timeout, iter(requests)) 47 | 48 | def do_sell_item(self, via, timeout, arguments): 49 | if len(arguments) != 1: 50 | print('must input a single item') 51 | return 52 | request = store_pb2.RemoveItemRequest(name=arguments[0]) 53 | response = self._execute_rpc('RemoveItem', via, timeout, request) 54 | if not response.was_successful: 55 | print('unable to sell') 56 | 57 | def do_sell_items(self, via, timeout, arguments): 58 | if not arguments: 59 | print('must input at least one item') 60 | return 61 | requests = [ 62 | store_pb2.RemoveItemRequest(name=name) for name in arguments 63 | ] 64 | response = self._execute_rpc('RemoveItems', via, timeout, 65 | iter(requests)) 66 | if not response.was_successful: 67 | print('unable to sell') 68 | 69 | def do_inventory(self, via, timeout, arguments): 70 | if arguments: 71 | print('inventory does not take any arguments') 72 | return 73 | if via != 'functor': 74 | print('inventory can only be called via functor') 75 | return 76 | request = store_pb2.Empty() 77 | result = self._execute_rpc('ListInventory', via, timeout, request) 78 | for query in result: 79 | print(query.name, '\t', query.count) 80 | 81 | def do_query_item(self, via, timeout, arguments): 82 | if len(arguments) != 1: 83 | print('must input a single item') 84 | return 85 | request = store_pb2.QueryItemRequest(name=arguments[0]) 86 | query = self._execute_rpc('QueryQuantity', via, timeout, request) 87 | print(query.name, '\t', query.count) 88 | 89 | def do_query_items(self, via, timeout, arguments): 90 | if not arguments: 91 | print('must input at least one item') 92 | return 93 | if via != 'functor': 94 | print('query_items can only be called via functor') 95 | return 96 | requests = [store_pb2.QueryItemRequest(name=name) for name in arguments] 97 | result = self._execute_rpc('QueryQuantities', via, timeout, 98 | iter(requests)) 99 | for query in result: 100 | print(query.name, '\t', query.count) 101 | 102 | 103 | def execute_command(command_executer, command, arguments): 104 | via = 'functor' 105 | timeout = None 106 | for argument_index in range(0, len(arguments), 2): 107 | argument = arguments[argument_index] 108 | if argument == '--via' and argument_index + 1 < len(arguments): 109 | if via not in ('functor', 'with_call', 'future'): 110 | print('invalid --via option') 111 | return 112 | via = arguments[argument_index + 1] 113 | elif argument == '--timeout' and argument_index + 1 < len(arguments): 114 | timeout = float(arguments[argument_index + 1]) 115 | else: 116 | arguments = arguments[argument_index:] 117 | break 118 | 119 | try: 120 | getattr(command_executer, 'do_' + command)(via, timeout, arguments) 121 | except AttributeError: 122 | print('unknown command: \"%s\"' % command) 123 | 124 | 125 | INSTRUCTIONS = \ 126 | """Enter commands to interact with the store service: 127 | 128 | stock_item Stock a single item. 129 | stock_items Stock one or more items. 130 | sell_item Sell a single item. 131 | sell_items Sell one or more items. 132 | inventory List the store's inventory. 133 | query_item Query the inventory for a single item. 134 | query_items Query the inventory for one or more items. 135 | 136 | You can also optionally provide a --via argument to instruct the RPC to be 137 | initiated via either the functor, with_call, or future method; or provide a 138 | --timeout argument to set a deadline for the RPC to be completed. 139 | 140 | Example: 141 | > stock_item apple 142 | > stock_items --via future apple milk 143 | > inventory 144 | apple 2 145 | milk 1 146 | """ 147 | 148 | 149 | def read_and_execute(command_executer): 150 | print(INSTRUCTIONS) 151 | while True: 152 | try: 153 | line = input('> ') 154 | components = line.split() 155 | if not components: 156 | continue 157 | command = components[0] 158 | arguments = components[1:] 159 | execute_command(command_executer, command, arguments) 160 | except EOFError: 161 | break 162 | 163 | 164 | class StoreSpanDecorator(SpanDecorator): 165 | 166 | def __call__(self, span, rpc_info): 167 | span.set_tag('grpc.method', rpc_info.full_method) 168 | span.set_tag('grpc.headers', str(rpc_info.metadata)) 169 | span.set_tag('grpc.deadline', str(rpc_info.timeout)) 170 | 171 | 172 | def run(): 173 | parser = argparse.ArgumentParser() 174 | parser.add_argument( 175 | '--log_payloads', 176 | action='store_true', 177 | help='log request/response objects to open-tracing spans') 178 | parser.add_argument( 179 | '--include_grpc_tags', 180 | action='store_true', 181 | help='set gRPC-specific tags on spans') 182 | args = parser.parse_args() 183 | 184 | config = Config( 185 | config={ 186 | 'sampler': { 187 | 'type': 'const', 188 | 'param': 1, 189 | }, 190 | 'logging': True, 191 | }, 192 | service_name='store-client') 193 | tracer = config.initialize_tracer() 194 | span_decorator = None 195 | if args.include_grpc_tags: 196 | span_decorator = StoreSpanDecorator() 197 | tracer_interceptor = open_tracing_client_interceptor( 198 | tracer, log_payloads=args.log_payloads, span_decorator=span_decorator) 199 | channel = grpc.insecure_channel('localhost:50051') 200 | channel = intercept_channel(channel, tracer_interceptor) 201 | stub = store_pb2.StoreStub(channel) 202 | 203 | read_and_execute(CommandExecuter(stub)) 204 | 205 | time.sleep(2) 206 | tracer.close() 207 | time.sleep(2) 208 | 209 | 210 | if __name__ == '__main__': 211 | run() 212 | -------------------------------------------------------------------------------- /go/otgrpc/test/interceptor_test.go: -------------------------------------------------------------------------------- 1 | package interceptor_test 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" 12 | testpb "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc/test/otgrpc_testing" 13 | "github.com/opentracing/opentracing-go/mocktracer" 14 | "golang.org/x/net/context" 15 | "google.golang.org/grpc" 16 | ) 17 | 18 | const ( 19 | streamLength = 5 20 | ) 21 | 22 | type testServer struct{} 23 | 24 | func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { 25 | return &testpb.SimpleResponse{in.Payload}, nil 26 | } 27 | 28 | func (s *testServer) StreamingOutputCall(in *testpb.SimpleRequest, stream testpb.TestService_StreamingOutputCallServer) error { 29 | for i := 0; i < streamLength; i++ { 30 | if err := stream.Send(&testpb.SimpleResponse{in.Payload}); err != nil { 31 | return err 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | func (s *testServer) StreamingInputCall(stream testpb.TestService_StreamingInputCallServer) error { 38 | sum := int32(0) 39 | for { 40 | in, err := stream.Recv() 41 | if err == io.EOF { 42 | break 43 | } 44 | if err != nil { 45 | return err 46 | } 47 | sum += in.Payload 48 | } 49 | return stream.SendAndClose(&testpb.SimpleResponse{sum}) 50 | } 51 | 52 | func (s *testServer) StreamingBidirectionalCall(stream testpb.TestService_StreamingBidirectionalCallServer) error { 53 | for { 54 | in, err := stream.Recv() 55 | if err == io.EOF { 56 | return nil 57 | } 58 | if err != nil { 59 | return err 60 | } 61 | if err = stream.Send(&testpb.SimpleResponse{in.Payload}); err != nil { 62 | return err 63 | } 64 | } 65 | } 66 | 67 | type env struct { 68 | unaryClientInt grpc.UnaryClientInterceptor 69 | streamClientInt grpc.StreamClientInterceptor 70 | unaryServerInt grpc.UnaryServerInterceptor 71 | streamServerInt grpc.StreamServerInterceptor 72 | } 73 | 74 | type test struct { 75 | t *testing.T 76 | e env 77 | srv *grpc.Server 78 | cc *grpc.ClientConn 79 | c testpb.TestServiceClient 80 | } 81 | 82 | func newTest(t *testing.T, e env) *test { 83 | te := &test{ 84 | t: t, 85 | e: e, 86 | } 87 | 88 | // Set up the server. 89 | sOpts := []grpc.ServerOption{} 90 | if e.unaryServerInt != nil { 91 | sOpts = append(sOpts, grpc.UnaryInterceptor(e.unaryServerInt)) 92 | } 93 | if e.streamServerInt != nil { 94 | sOpts = append(sOpts, grpc.StreamInterceptor(e.streamServerInt)) 95 | } 96 | lis, err := net.Listen("tcp", "localhost:0") 97 | if err != nil { 98 | te.t.Fatalf("Failed to listen: %v", err) 99 | } 100 | te.srv = grpc.NewServer(sOpts...) 101 | testpb.RegisterTestServiceServer(te.srv, &testServer{}) 102 | go te.srv.Serve(lis) 103 | 104 | // Set up a connection to the server. 105 | cOpts := []grpc.DialOption{grpc.WithInsecure()} 106 | if e.unaryClientInt != nil { 107 | cOpts = append(cOpts, grpc.WithUnaryInterceptor(e.unaryClientInt)) 108 | } 109 | if e.streamClientInt != nil { 110 | cOpts = append(cOpts, grpc.WithStreamInterceptor(e.streamClientInt)) 111 | } 112 | _, port, err := net.SplitHostPort(lis.Addr().String()) 113 | if err != nil { 114 | te.t.Fatalf("Failed to parse listener address: %v", err) 115 | } 116 | srvAddr := "localhost:" + port 117 | te.cc, err = grpc.Dial(srvAddr, cOpts...) 118 | if err != nil { 119 | te.t.Fatalf("Dial(%q) = %v", srvAddr, err) 120 | } 121 | te.c = testpb.NewTestServiceClient(te.cc) 122 | return te 123 | } 124 | 125 | func (te *test) tearDown() { 126 | te.cc.Close() 127 | } 128 | 129 | func assertChildParentSpans(t *testing.T, tracer *mocktracer.MockTracer) { 130 | spans := tracer.FinishedSpans() 131 | assert.Equal(t, 2, len(spans)) 132 | if len(spans) != 2 { 133 | t.Fatalf("Incorrect span length") 134 | } 135 | parent := spans[1] 136 | child := spans[0] 137 | assert.Equal(t, child.ParentID, parent.Context().(mocktracer.MockSpanContext).SpanID) 138 | } 139 | 140 | func TestUnaryOpenTracing(t *testing.T) { 141 | tracer := mocktracer.New() 142 | e := env{ 143 | unaryClientInt: otgrpc.OpenTracingClientInterceptor(tracer), 144 | unaryServerInt: otgrpc.OpenTracingServerInterceptor(tracer), 145 | } 146 | te := newTest(t, e) 147 | defer te.tearDown() 148 | 149 | payload := int32(0) 150 | resp, err := te.c.UnaryCall(context.Background(), &testpb.SimpleRequest{payload}) 151 | if err != nil { 152 | t.Fatalf("Failed UnaryCall: %v", err) 153 | } 154 | assert.Equal(t, payload, resp.Payload) 155 | assertChildParentSpans(t, tracer) 156 | } 157 | 158 | func TestStreamingOutputCallOpenTracing(t *testing.T) { 159 | tracer := mocktracer.New() 160 | e := env{ 161 | streamClientInt: otgrpc.OpenTracingStreamClientInterceptor(tracer), 162 | streamServerInt: otgrpc.OpenTracingStreamServerInterceptor(tracer), 163 | } 164 | te := newTest(t, e) 165 | defer te.tearDown() 166 | 167 | payload := int32(0) 168 | stream, err := te.c.StreamingOutputCall(context.Background(), &testpb.SimpleRequest{payload}) 169 | if err != nil { 170 | t.Fatalf("Failed StreamingOutputCall: %v", err) 171 | } 172 | for { 173 | resp, err := stream.Recv() 174 | if err == io.EOF { 175 | break 176 | } 177 | if err != nil { 178 | t.Fatalf("Failed StreamingOutputCall: %v", err) 179 | } 180 | assert.Equal(t, payload, resp.Payload) 181 | } 182 | assertChildParentSpans(t, tracer) 183 | } 184 | 185 | func TestStreamingInputCallOpenTracing(t *testing.T) { 186 | tracer := mocktracer.New() 187 | e := env{ 188 | streamClientInt: otgrpc.OpenTracingStreamClientInterceptor(tracer), 189 | streamServerInt: otgrpc.OpenTracingStreamServerInterceptor(tracer), 190 | } 191 | te := newTest(t, e) 192 | defer te.tearDown() 193 | 194 | payload := int32(1) 195 | stream, err := te.c.StreamingInputCall(context.Background()) 196 | for i := 0; i < streamLength; i++ { 197 | if err = stream.Send(&testpb.SimpleRequest{payload}); err != nil { 198 | t.Fatalf("Failed StreamingInputCall: %v", err) 199 | } 200 | } 201 | resp, err := stream.CloseAndRecv() 202 | if err != nil { 203 | t.Fatalf("Failed StreamingInputCall: %v", err) 204 | } 205 | assert.Equal(t, streamLength*payload, resp.Payload) 206 | assertChildParentSpans(t, tracer) 207 | } 208 | 209 | func TestStreamingBidirectionalCallOpenTracing(t *testing.T) { 210 | tracer := mocktracer.New() 211 | e := env{ 212 | streamClientInt: otgrpc.OpenTracingStreamClientInterceptor(tracer), 213 | streamServerInt: otgrpc.OpenTracingStreamServerInterceptor(tracer), 214 | } 215 | te := newTest(t, e) 216 | defer te.tearDown() 217 | 218 | payload := int32(0) 219 | stream, err := te.c.StreamingBidirectionalCall(context.Background()) 220 | if err != nil { 221 | t.Fatalf("Failed StreamingInputCall: %v", err) 222 | } 223 | go func() { 224 | for i := 0; i < streamLength; i++ { 225 | if err := stream.Send(&testpb.SimpleRequest{payload}); err != nil { 226 | t.Fatalf("Failed StreamingInputCall: %v", err) 227 | } 228 | } 229 | stream.CloseSend() 230 | }() 231 | for { 232 | resp, err := stream.Recv() 233 | if err == io.EOF { 234 | break 235 | } 236 | if err != nil { 237 | t.Fatalf("Failed StreamingOutputCall: %v", err) 238 | } 239 | assert.Equal(t, payload, resp.Payload) 240 | } 241 | assertChildParentSpans(t, tracer) 242 | } 243 | 244 | func TestStreamingContextCancellationOpenTracing(t *testing.T) { 245 | tracer := mocktracer.New() 246 | e := env{ 247 | streamClientInt: otgrpc.OpenTracingStreamClientInterceptor(tracer), 248 | streamServerInt: otgrpc.OpenTracingStreamServerInterceptor(tracer), 249 | } 250 | te := newTest(t, e) 251 | defer te.tearDown() 252 | 253 | payload := int32(0) 254 | ctx, cancel := context.WithCancel(context.Background()) 255 | _, err := te.c.StreamingOutputCall(ctx, &testpb.SimpleRequest{payload}) 256 | if err != nil { 257 | t.Fatalf("Failed StreamingOutputCall: %v", err) 258 | } 259 | cancel() 260 | time.Sleep(100 * time.Millisecond) 261 | spans := tracer.FinishedSpans() 262 | assert.Equal(t, 2, len(spans)) 263 | if len(spans) != 2 { 264 | t.Fatalf("Incorrect span length") 265 | } 266 | parent := spans[0] 267 | child := spans[1] 268 | assert.Equal(t, child.ParentID, parent.Context().(mocktracer.MockSpanContext).SpanID) 269 | assert.True(t, parent.Tag("error").(bool)) 270 | } 271 | -------------------------------------------------------------------------------- /go/otgrpc/client.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "github.com/opentracing/opentracing-go" 5 | "github.com/opentracing/opentracing-go/ext" 6 | "github.com/opentracing/opentracing-go/log" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/metadata" 10 | "io" 11 | "runtime" 12 | "sync/atomic" 13 | ) 14 | 15 | // OpenTracingClientInterceptor returns a grpc.UnaryClientInterceptor suitable 16 | // for use in a grpc.Dial call. 17 | // 18 | // For example: 19 | // 20 | // conn, err := grpc.Dial( 21 | // address, 22 | // ..., // (existing DialOptions) 23 | // grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(tracer))) 24 | // 25 | // All gRPC client spans will inject the OpenTracing SpanContext into the gRPC 26 | // metadata; they will also look in the context.Context for an active 27 | // in-process parent Span and establish a ChildOf reference if such a parent 28 | // Span could be found. 29 | func OpenTracingClientInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.UnaryClientInterceptor { 30 | otgrpcOpts := newOptions() 31 | otgrpcOpts.apply(optFuncs...) 32 | return func( 33 | ctx context.Context, 34 | method string, 35 | req, resp interface{}, 36 | cc *grpc.ClientConn, 37 | invoker grpc.UnaryInvoker, 38 | opts ...grpc.CallOption, 39 | ) error { 40 | var err error 41 | var parentCtx opentracing.SpanContext 42 | if parent := opentracing.SpanFromContext(ctx); parent != nil { 43 | parentCtx = parent.Context() 44 | } 45 | if otgrpcOpts.inclusionFunc != nil && 46 | !otgrpcOpts.inclusionFunc(parentCtx, method, req, resp) { 47 | return invoker(ctx, method, req, resp, cc, opts...) 48 | } 49 | clientSpan := tracer.StartSpan( 50 | method, 51 | opentracing.ChildOf(parentCtx), 52 | ext.SpanKindRPCClient, 53 | gRPCComponentTag, 54 | ) 55 | defer clientSpan.Finish() 56 | ctx = injectSpanContext(ctx, tracer, clientSpan) 57 | if otgrpcOpts.logPayloads { 58 | clientSpan.LogFields(log.Object("gRPC request", req)) 59 | } 60 | err = invoker(ctx, method, req, resp, cc, opts...) 61 | if err == nil { 62 | if otgrpcOpts.logPayloads { 63 | clientSpan.LogFields(log.Object("gRPC response", resp)) 64 | } 65 | } else { 66 | SetSpanTags(clientSpan, err, true) 67 | clientSpan.LogFields(log.String("event", "error"), log.String("message", err.Error())) 68 | } 69 | if otgrpcOpts.decorator != nil { 70 | otgrpcOpts.decorator(clientSpan, method, req, resp, err) 71 | } 72 | return err 73 | } 74 | } 75 | 76 | // OpenTracingStreamClientInterceptor returns a grpc.StreamClientInterceptor suitable 77 | // for use in a grpc.Dial call. The interceptor instruments streaming RPCs by creating 78 | // a single span to correspond to the lifetime of the RPC's stream. 79 | // 80 | // For example: 81 | // 82 | // conn, err := grpc.Dial( 83 | // address, 84 | // ..., // (existing DialOptions) 85 | // grpc.WithStreamInterceptor(otgrpc.OpenTracingStreamClientInterceptor(tracer))) 86 | // 87 | // All gRPC client spans will inject the OpenTracing SpanContext into the gRPC 88 | // metadata; they will also look in the context.Context for an active 89 | // in-process parent Span and establish a ChildOf reference if such a parent 90 | // Span could be found. 91 | func OpenTracingStreamClientInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.StreamClientInterceptor { 92 | otgrpcOpts := newOptions() 93 | otgrpcOpts.apply(optFuncs...) 94 | return func( 95 | ctx context.Context, 96 | desc *grpc.StreamDesc, 97 | cc *grpc.ClientConn, 98 | method string, 99 | streamer grpc.Streamer, 100 | opts ...grpc.CallOption, 101 | ) (grpc.ClientStream, error) { 102 | var err error 103 | var parentCtx opentracing.SpanContext 104 | if parent := opentracing.SpanFromContext(ctx); parent != nil { 105 | parentCtx = parent.Context() 106 | } 107 | if otgrpcOpts.inclusionFunc != nil && 108 | !otgrpcOpts.inclusionFunc(parentCtx, method, nil, nil) { 109 | return streamer(ctx, desc, cc, method, opts...) 110 | } 111 | 112 | clientSpan := tracer.StartSpan( 113 | method, 114 | opentracing.ChildOf(parentCtx), 115 | ext.SpanKindRPCClient, 116 | gRPCComponentTag, 117 | ) 118 | ctx = injectSpanContext(ctx, tracer, clientSpan) 119 | cs, err := streamer(ctx, desc, cc, method, opts...) 120 | if err != nil { 121 | clientSpan.LogFields(log.String("event", "error"), log.String("message", err.Error())) 122 | SetSpanTags(clientSpan, err, true) 123 | clientSpan.Finish() 124 | return cs, err 125 | } 126 | return newOpenTracingClientStream(cs, method, desc, clientSpan, otgrpcOpts), nil 127 | } 128 | } 129 | 130 | func newOpenTracingClientStream(cs grpc.ClientStream, method string, desc *grpc.StreamDesc, clientSpan opentracing.Span, otgrpcOpts *options) grpc.ClientStream { 131 | finishChan := make(chan struct{}) 132 | 133 | isFinished := new(int32) 134 | *isFinished = 0 135 | finishFunc := func(err error) { 136 | // The current OpenTracing specification forbids finishing a span more than 137 | // once. Since we have multiple code paths that could concurrently call 138 | // `finishFunc`, we need to add some sort of synchronization to guard against 139 | // multiple finishing. 140 | if !atomic.CompareAndSwapInt32(isFinished, 0, 1) { 141 | return 142 | } 143 | close(finishChan) 144 | defer clientSpan.Finish() 145 | if err != nil { 146 | clientSpan.LogFields(log.String("event", "error"), log.String("message", err.Error())) 147 | SetSpanTags(clientSpan, err, true) 148 | } 149 | if otgrpcOpts.decorator != nil { 150 | otgrpcOpts.decorator(clientSpan, method, nil, nil, err) 151 | } 152 | } 153 | go func() { 154 | select { 155 | case <-finishChan: 156 | // The client span is being finished by another code path; hence, no 157 | // action is necessary. 158 | case <-cs.Context().Done(): 159 | finishFunc(cs.Context().Err()) 160 | } 161 | }() 162 | otcs := &openTracingClientStream{ 163 | ClientStream: cs, 164 | desc: desc, 165 | finishFunc: finishFunc, 166 | } 167 | 168 | // The `ClientStream` interface allows one to omit calling `Recv` if it's 169 | // known that the result will be `io.EOF`. See 170 | // http://stackoverflow.com/q/42915337 171 | // In such cases, there's nothing that triggers the span to finish. We, 172 | // therefore, set a finalizer so that the span and the context goroutine will 173 | // at least be cleaned up when the garbage collector is run. 174 | runtime.SetFinalizer(otcs, func(otcs *openTracingClientStream) { 175 | otcs.finishFunc(nil) 176 | }) 177 | return otcs 178 | } 179 | 180 | type openTracingClientStream struct { 181 | grpc.ClientStream 182 | desc *grpc.StreamDesc 183 | finishFunc func(error) 184 | } 185 | 186 | func (cs *openTracingClientStream) Header() (metadata.MD, error) { 187 | md, err := cs.ClientStream.Header() 188 | if err != nil { 189 | cs.finishFunc(err) 190 | } 191 | return md, err 192 | } 193 | 194 | func (cs *openTracingClientStream) SendMsg(m interface{}) error { 195 | err := cs.ClientStream.SendMsg(m) 196 | if err != nil { 197 | cs.finishFunc(err) 198 | } 199 | return err 200 | } 201 | 202 | func (cs *openTracingClientStream) RecvMsg(m interface{}) error { 203 | err := cs.ClientStream.RecvMsg(m) 204 | if err == io.EOF { 205 | cs.finishFunc(nil) 206 | return err 207 | } else if err != nil { 208 | cs.finishFunc(err) 209 | return err 210 | } 211 | if !cs.desc.ServerStreams { 212 | cs.finishFunc(nil) 213 | } 214 | return err 215 | } 216 | 217 | func (cs *openTracingClientStream) CloseSend() error { 218 | err := cs.ClientStream.CloseSend() 219 | if err != nil { 220 | cs.finishFunc(err) 221 | } 222 | return err 223 | } 224 | 225 | func injectSpanContext(ctx context.Context, tracer opentracing.Tracer, clientSpan opentracing.Span) context.Context { 226 | md, ok := metadata.FromOutgoingContext(ctx) 227 | if !ok { 228 | md = metadata.New(nil) 229 | } else { 230 | md = md.Copy() 231 | } 232 | mdWriter := metadataReaderWriter{md} 233 | err := tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, mdWriter) 234 | // We have no better place to record an error than the Span itself :-/ 235 | if err != nil { 236 | clientSpan.LogFields(log.String("event", "Tracer.Inject() failed"), log.Error(err)) 237 | } 238 | return metadata.NewOutgoingContext(ctx, md) 239 | } 240 | -------------------------------------------------------------------------------- /python/examples/trivial/command_line_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: command_line.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | from google.protobuf import descriptor_pb2 11 | # @@protoc_insertion_point(imports) 12 | 13 | _sym_db = _symbol_database.Default() 14 | 15 | 16 | 17 | 18 | DESCRIPTOR = _descriptor.FileDescriptor( 19 | name='command_line.proto', 20 | package='command_line', 21 | syntax='proto3', 22 | serialized_pb=_b('\n\x12\x63ommand_line.proto\x12\x0c\x63ommand_line\"\x1e\n\x0e\x43ommandRequest\x12\x0c\n\x04text\x18\x01 \x01(\t\"\x1f\n\x0f\x43ommandResponse\x12\x0c\n\x04text\x18\x01 \x01(\t2T\n\x0b\x43ommandLine\x12\x45\n\x04\x45\x63ho\x12\x1c.command_line.CommandRequest\x1a\x1d.command_line.CommandResponse\"\x00\x62\x06proto3') 23 | ) 24 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 25 | 26 | 27 | 28 | 29 | _COMMANDREQUEST = _descriptor.Descriptor( 30 | name='CommandRequest', 31 | full_name='command_line.CommandRequest', 32 | filename=None, 33 | file=DESCRIPTOR, 34 | containing_type=None, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='text', full_name='command_line.CommandRequest.text', index=0, 38 | number=1, type=9, cpp_type=9, label=1, 39 | has_default_value=False, default_value=_b("").decode('utf-8'), 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | options=None), 43 | ], 44 | extensions=[ 45 | ], 46 | nested_types=[], 47 | enum_types=[ 48 | ], 49 | options=None, 50 | is_extendable=False, 51 | syntax='proto3', 52 | extension_ranges=[], 53 | oneofs=[ 54 | ], 55 | serialized_start=36, 56 | serialized_end=66, 57 | ) 58 | 59 | 60 | _COMMANDRESPONSE = _descriptor.Descriptor( 61 | name='CommandResponse', 62 | full_name='command_line.CommandResponse', 63 | filename=None, 64 | file=DESCRIPTOR, 65 | containing_type=None, 66 | fields=[ 67 | _descriptor.FieldDescriptor( 68 | name='text', full_name='command_line.CommandResponse.text', index=0, 69 | number=1, type=9, cpp_type=9, label=1, 70 | has_default_value=False, default_value=_b("").decode('utf-8'), 71 | message_type=None, enum_type=None, containing_type=None, 72 | is_extension=False, extension_scope=None, 73 | options=None), 74 | ], 75 | extensions=[ 76 | ], 77 | nested_types=[], 78 | enum_types=[ 79 | ], 80 | options=None, 81 | is_extendable=False, 82 | syntax='proto3', 83 | extension_ranges=[], 84 | oneofs=[ 85 | ], 86 | serialized_start=68, 87 | serialized_end=99, 88 | ) 89 | 90 | DESCRIPTOR.message_types_by_name['CommandRequest'] = _COMMANDREQUEST 91 | DESCRIPTOR.message_types_by_name['CommandResponse'] = _COMMANDRESPONSE 92 | 93 | CommandRequest = _reflection.GeneratedProtocolMessageType('CommandRequest', (_message.Message,), dict( 94 | DESCRIPTOR = _COMMANDREQUEST, 95 | __module__ = 'command_line_pb2' 96 | # @@protoc_insertion_point(class_scope:command_line.CommandRequest) 97 | )) 98 | _sym_db.RegisterMessage(CommandRequest) 99 | 100 | CommandResponse = _reflection.GeneratedProtocolMessageType('CommandResponse', (_message.Message,), dict( 101 | DESCRIPTOR = _COMMANDRESPONSE, 102 | __module__ = 'command_line_pb2' 103 | # @@protoc_insertion_point(class_scope:command_line.CommandResponse) 104 | )) 105 | _sym_db.RegisterMessage(CommandResponse) 106 | 107 | 108 | try: 109 | # THESE ELEMENTS WILL BE DEPRECATED. 110 | # Please use the generated *_pb2_grpc.py files instead. 111 | import grpc 112 | from grpc.framework.common import cardinality 113 | from grpc.framework.interfaces.face import utilities as face_utilities 114 | from grpc.beta import implementations as beta_implementations 115 | from grpc.beta import interfaces as beta_interfaces 116 | 117 | 118 | class CommandLineStub(object): 119 | 120 | def __init__(self, channel): 121 | """Constructor. 122 | 123 | Args: 124 | channel: A grpc.Channel. 125 | """ 126 | self.Echo = channel.unary_unary( 127 | '/command_line.CommandLine/Echo', 128 | request_serializer=CommandRequest.SerializeToString, 129 | response_deserializer=CommandResponse.FromString, 130 | ) 131 | 132 | 133 | class CommandLineServicer(object): 134 | 135 | def Echo(self, request, context): 136 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 137 | context.set_details('Method not implemented!') 138 | raise NotImplementedError('Method not implemented!') 139 | 140 | 141 | def add_CommandLineServicer_to_server(servicer, server): 142 | rpc_method_handlers = { 143 | 'Echo': grpc.unary_unary_rpc_method_handler( 144 | servicer.Echo, 145 | request_deserializer=CommandRequest.FromString, 146 | response_serializer=CommandResponse.SerializeToString, 147 | ), 148 | } 149 | generic_handler = grpc.method_handlers_generic_handler( 150 | 'command_line.CommandLine', rpc_method_handlers) 151 | server.add_generic_rpc_handlers((generic_handler,)) 152 | 153 | 154 | class BetaCommandLineServicer(object): 155 | """The Beta API is deprecated for 0.15.0 and later. 156 | 157 | It is recommended to use the GA API (classes and functions in this 158 | file not marked beta) for all further purposes. This class was generated 159 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 160 | def Echo(self, request, context): 161 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 162 | 163 | 164 | class BetaCommandLineStub(object): 165 | """The Beta API is deprecated for 0.15.0 and later. 166 | 167 | It is recommended to use the GA API (classes and functions in this 168 | file not marked beta) for all further purposes. This class was generated 169 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 170 | def Echo(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 171 | raise NotImplementedError() 172 | Echo.future = None 173 | 174 | 175 | def beta_create_CommandLine_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): 176 | """The Beta API is deprecated for 0.15.0 and later. 177 | 178 | It is recommended to use the GA API (classes and functions in this 179 | file not marked beta) for all further purposes. This function was 180 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 181 | request_deserializers = { 182 | ('command_line.CommandLine', 'Echo'): CommandRequest.FromString, 183 | } 184 | response_serializers = { 185 | ('command_line.CommandLine', 'Echo'): CommandResponse.SerializeToString, 186 | } 187 | method_implementations = { 188 | ('command_line.CommandLine', 'Echo'): face_utilities.unary_unary_inline(servicer.Echo), 189 | } 190 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) 191 | return beta_implementations.server(method_implementations, options=server_options) 192 | 193 | 194 | def beta_create_CommandLine_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 195 | """The Beta API is deprecated for 0.15.0 and later. 196 | 197 | It is recommended to use the GA API (classes and functions in this 198 | file not marked beta) for all further purposes. This function was 199 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 200 | request_serializers = { 201 | ('command_line.CommandLine', 'Echo'): CommandRequest.SerializeToString, 202 | } 203 | response_deserializers = { 204 | ('command_line.CommandLine', 'Echo'): CommandResponse.FromString, 205 | } 206 | cardinalities = { 207 | 'Echo': cardinality.Cardinality.UNARY_UNARY, 208 | } 209 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) 210 | return beta_implementations.dynamic_stub(channel, 'command_line.CommandLine', cardinalities, options=stub_options) 211 | except ImportError: 212 | pass 213 | # @@protoc_insertion_point(module_scope) 214 | -------------------------------------------------------------------------------- /python/examples/integration/command_line_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: command_line.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | from google.protobuf import descriptor_pb2 11 | # @@protoc_insertion_point(imports) 12 | 13 | _sym_db = _symbol_database.Default() 14 | 15 | 16 | 17 | 18 | DESCRIPTOR = _descriptor.FileDescriptor( 19 | name='command_line.proto', 20 | package='command_line', 21 | syntax='proto3', 22 | serialized_pb=_b('\n\x12\x63ommand_line.proto\x12\x0c\x63ommand_line\"\x1e\n\x0e\x43ommandRequest\x12\x0c\n\x04text\x18\x01 \x01(\t\"\x1f\n\x0f\x43ommandResponse\x12\x0c\n\x04text\x18\x01 \x01(\t2T\n\x0b\x43ommandLine\x12\x45\n\x04\x45\x63ho\x12\x1c.command_line.CommandRequest\x1a\x1d.command_line.CommandResponse\"\x00\x62\x06proto3') 23 | ) 24 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 25 | 26 | 27 | 28 | 29 | _COMMANDREQUEST = _descriptor.Descriptor( 30 | name='CommandRequest', 31 | full_name='command_line.CommandRequest', 32 | filename=None, 33 | file=DESCRIPTOR, 34 | containing_type=None, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='text', full_name='command_line.CommandRequest.text', index=0, 38 | number=1, type=9, cpp_type=9, label=1, 39 | has_default_value=False, default_value=_b("").decode('utf-8'), 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | options=None), 43 | ], 44 | extensions=[ 45 | ], 46 | nested_types=[], 47 | enum_types=[ 48 | ], 49 | options=None, 50 | is_extendable=False, 51 | syntax='proto3', 52 | extension_ranges=[], 53 | oneofs=[ 54 | ], 55 | serialized_start=36, 56 | serialized_end=66, 57 | ) 58 | 59 | 60 | _COMMANDRESPONSE = _descriptor.Descriptor( 61 | name='CommandResponse', 62 | full_name='command_line.CommandResponse', 63 | filename=None, 64 | file=DESCRIPTOR, 65 | containing_type=None, 66 | fields=[ 67 | _descriptor.FieldDescriptor( 68 | name='text', full_name='command_line.CommandResponse.text', index=0, 69 | number=1, type=9, cpp_type=9, label=1, 70 | has_default_value=False, default_value=_b("").decode('utf-8'), 71 | message_type=None, enum_type=None, containing_type=None, 72 | is_extension=False, extension_scope=None, 73 | options=None), 74 | ], 75 | extensions=[ 76 | ], 77 | nested_types=[], 78 | enum_types=[ 79 | ], 80 | options=None, 81 | is_extendable=False, 82 | syntax='proto3', 83 | extension_ranges=[], 84 | oneofs=[ 85 | ], 86 | serialized_start=68, 87 | serialized_end=99, 88 | ) 89 | 90 | DESCRIPTOR.message_types_by_name['CommandRequest'] = _COMMANDREQUEST 91 | DESCRIPTOR.message_types_by_name['CommandResponse'] = _COMMANDRESPONSE 92 | 93 | CommandRequest = _reflection.GeneratedProtocolMessageType('CommandRequest', (_message.Message,), dict( 94 | DESCRIPTOR = _COMMANDREQUEST, 95 | __module__ = 'command_line_pb2' 96 | # @@protoc_insertion_point(class_scope:command_line.CommandRequest) 97 | )) 98 | _sym_db.RegisterMessage(CommandRequest) 99 | 100 | CommandResponse = _reflection.GeneratedProtocolMessageType('CommandResponse', (_message.Message,), dict( 101 | DESCRIPTOR = _COMMANDRESPONSE, 102 | __module__ = 'command_line_pb2' 103 | # @@protoc_insertion_point(class_scope:command_line.CommandResponse) 104 | )) 105 | _sym_db.RegisterMessage(CommandResponse) 106 | 107 | 108 | try: 109 | # THESE ELEMENTS WILL BE DEPRECATED. 110 | # Please use the generated *_pb2_grpc.py files instead. 111 | import grpc 112 | from grpc.framework.common import cardinality 113 | from grpc.framework.interfaces.face import utilities as face_utilities 114 | from grpc.beta import implementations as beta_implementations 115 | from grpc.beta import interfaces as beta_interfaces 116 | 117 | 118 | class CommandLineStub(object): 119 | 120 | def __init__(self, channel): 121 | """Constructor. 122 | 123 | Args: 124 | channel: A grpc.Channel. 125 | """ 126 | self.Echo = channel.unary_unary( 127 | '/command_line.CommandLine/Echo', 128 | request_serializer=CommandRequest.SerializeToString, 129 | response_deserializer=CommandResponse.FromString, 130 | ) 131 | 132 | 133 | class CommandLineServicer(object): 134 | 135 | def Echo(self, request, context): 136 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 137 | context.set_details('Method not implemented!') 138 | raise NotImplementedError('Method not implemented!') 139 | 140 | 141 | def add_CommandLineServicer_to_server(servicer, server): 142 | rpc_method_handlers = { 143 | 'Echo': grpc.unary_unary_rpc_method_handler( 144 | servicer.Echo, 145 | request_deserializer=CommandRequest.FromString, 146 | response_serializer=CommandResponse.SerializeToString, 147 | ), 148 | } 149 | generic_handler = grpc.method_handlers_generic_handler( 150 | 'command_line.CommandLine', rpc_method_handlers) 151 | server.add_generic_rpc_handlers((generic_handler,)) 152 | 153 | 154 | class BetaCommandLineServicer(object): 155 | """The Beta API is deprecated for 0.15.0 and later. 156 | 157 | It is recommended to use the GA API (classes and functions in this 158 | file not marked beta) for all further purposes. This class was generated 159 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 160 | def Echo(self, request, context): 161 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 162 | 163 | 164 | class BetaCommandLineStub(object): 165 | """The Beta API is deprecated for 0.15.0 and later. 166 | 167 | It is recommended to use the GA API (classes and functions in this 168 | file not marked beta) for all further purposes. This class was generated 169 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 170 | def Echo(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 171 | raise NotImplementedError() 172 | Echo.future = None 173 | 174 | 175 | def beta_create_CommandLine_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): 176 | """The Beta API is deprecated for 0.15.0 and later. 177 | 178 | It is recommended to use the GA API (classes and functions in this 179 | file not marked beta) for all further purposes. This function was 180 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 181 | request_deserializers = { 182 | ('command_line.CommandLine', 'Echo'): CommandRequest.FromString, 183 | } 184 | response_serializers = { 185 | ('command_line.CommandLine', 'Echo'): CommandResponse.SerializeToString, 186 | } 187 | method_implementations = { 188 | ('command_line.CommandLine', 'Echo'): face_utilities.unary_unary_inline(servicer.Echo), 189 | } 190 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) 191 | return beta_implementations.server(method_implementations, options=server_options) 192 | 193 | 194 | def beta_create_CommandLine_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 195 | """The Beta API is deprecated for 0.15.0 and later. 196 | 197 | It is recommended to use the GA API (classes and functions in this 198 | file not marked beta) for all further purposes. This function was 199 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 200 | request_serializers = { 201 | ('command_line.CommandLine', 'Echo'): CommandRequest.SerializeToString, 202 | } 203 | response_deserializers = { 204 | ('command_line.CommandLine', 'Echo'): CommandResponse.FromString, 205 | } 206 | cardinalities = { 207 | 'Echo': cardinality.Cardinality.UNARY_UNARY, 208 | } 209 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) 210 | return beta_implementations.dynamic_stub(channel, 'command_line.CommandLine', cardinalities, options=stub_options) 211 | except ImportError: 212 | pass 213 | # @@protoc_insertion_point(module_scope) 214 | -------------------------------------------------------------------------------- /java/src/testgen/io/opentracing/contrib/GreeterGrpc.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import static io.grpc.MethodDescriptor.generateFullMethodName; 4 | import static io.grpc.stub.ClientCalls.asyncUnaryCall; 5 | import static io.grpc.stub.ClientCalls.blockingUnaryCall; 6 | import static io.grpc.stub.ClientCalls.futureUnaryCall; 7 | import static io.grpc.stub.ServerCalls.asyncUnaryCall; 8 | import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; 9 | 10 | /** 11 | *
 12 |  * The greeting service definition.
 13 |  * 
14 | */ 15 | @javax.annotation.Generated( 16 | value = "by gRPC proto compiler (version 0.15.0)", 17 | comments = "Source: helloworld.proto") 18 | public class GreeterGrpc { 19 | 20 | private GreeterGrpc() {} 21 | 22 | public static final String SERVICE_NAME = "helloworld.Greeter"; 23 | 24 | // Static method descriptors that strictly reflect the proto. 25 | @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901") 26 | public static final io.grpc.MethodDescriptor METHOD_SAY_HELLO = 28 | io.grpc.MethodDescriptor.create( 29 | io.grpc.MethodDescriptor.MethodType.UNARY, 30 | generateFullMethodName( 31 | "helloworld.Greeter", "SayHello"), 32 | io.grpc.protobuf.ProtoUtils.marshaller(io.opentracing.contrib.HelloRequest.getDefaultInstance()), 33 | io.grpc.protobuf.ProtoUtils.marshaller(io.opentracing.contrib.HelloReply.getDefaultInstance())); 34 | 35 | /** 36 | * Creates a new async stub that supports all call types for the service 37 | */ 38 | public static GreeterStub newStub(io.grpc.Channel channel) { 39 | return new GreeterStub(channel); 40 | } 41 | 42 | /** 43 | * Creates a new blocking-style stub that supports unary and streaming output calls on the service 44 | */ 45 | public static GreeterBlockingStub newBlockingStub( 46 | io.grpc.Channel channel) { 47 | return new GreeterBlockingStub(channel); 48 | } 49 | 50 | /** 51 | * Creates a new ListenableFuture-style stub that supports unary and streaming output calls on the service 52 | */ 53 | public static GreeterFutureStub newFutureStub( 54 | io.grpc.Channel channel) { 55 | return new GreeterFutureStub(channel); 56 | } 57 | 58 | /** 59 | *
 60 |    * The greeting service definition.
 61 |    * 
62 | */ 63 | @java.lang.Deprecated public static interface Greeter { 64 | 65 | /** 66 | *
 67 |      * Sends a greeting
 68 |      * 
69 | */ 70 | public void sayHello(io.opentracing.contrib.HelloRequest request, 71 | io.grpc.stub.StreamObserver responseObserver); 72 | } 73 | 74 | @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1469") 75 | public static abstract class GreeterImplBase implements Greeter, io.grpc.BindableService { 76 | 77 | @java.lang.Override 78 | public void sayHello(io.opentracing.contrib.HelloRequest request, 79 | io.grpc.stub.StreamObserver responseObserver) { 80 | asyncUnimplementedUnaryCall(METHOD_SAY_HELLO, responseObserver); 81 | } 82 | 83 | @java.lang.Override public io.grpc.ServerServiceDefinition bindService() { 84 | return GreeterGrpc.bindService(this); 85 | } 86 | } 87 | 88 | /** 89 | *
 90 |    * The greeting service definition.
 91 |    * 
92 | */ 93 | @java.lang.Deprecated public static interface GreeterBlockingClient { 94 | 95 | /** 96 | *
 97 |      * Sends a greeting
 98 |      * 
99 | */ 100 | public io.opentracing.contrib.HelloReply sayHello(io.opentracing.contrib.HelloRequest request); 101 | } 102 | 103 | /** 104 | *
105 |    * The greeting service definition.
106 |    * 
107 | */ 108 | @java.lang.Deprecated public static interface GreeterFutureClient { 109 | 110 | /** 111 | *
112 |      * Sends a greeting
113 |      * 
114 | */ 115 | public com.google.common.util.concurrent.ListenableFuture sayHello( 116 | io.opentracing.contrib.HelloRequest request); 117 | } 118 | 119 | public static class GreeterStub extends io.grpc.stub.AbstractStub 120 | implements Greeter { 121 | private GreeterStub(io.grpc.Channel channel) { 122 | super(channel); 123 | } 124 | 125 | private GreeterStub(io.grpc.Channel channel, 126 | io.grpc.CallOptions callOptions) { 127 | super(channel, callOptions); 128 | } 129 | 130 | @java.lang.Override 131 | protected GreeterStub build(io.grpc.Channel channel, 132 | io.grpc.CallOptions callOptions) { 133 | return new GreeterStub(channel, callOptions); 134 | } 135 | 136 | @java.lang.Override 137 | public void sayHello(io.opentracing.contrib.HelloRequest request, 138 | io.grpc.stub.StreamObserver responseObserver) { 139 | asyncUnaryCall( 140 | getChannel().newCall(METHOD_SAY_HELLO, getCallOptions()), request, responseObserver); 141 | } 142 | } 143 | 144 | public static class GreeterBlockingStub extends io.grpc.stub.AbstractStub 145 | implements GreeterBlockingClient { 146 | private GreeterBlockingStub(io.grpc.Channel channel) { 147 | super(channel); 148 | } 149 | 150 | private GreeterBlockingStub(io.grpc.Channel channel, 151 | io.grpc.CallOptions callOptions) { 152 | super(channel, callOptions); 153 | } 154 | 155 | @java.lang.Override 156 | protected GreeterBlockingStub build(io.grpc.Channel channel, 157 | io.grpc.CallOptions callOptions) { 158 | return new GreeterBlockingStub(channel, callOptions); 159 | } 160 | 161 | @java.lang.Override 162 | public io.opentracing.contrib.HelloReply sayHello(io.opentracing.contrib.HelloRequest request) { 163 | return blockingUnaryCall( 164 | getChannel(), METHOD_SAY_HELLO, getCallOptions(), request); 165 | } 166 | } 167 | 168 | public static class GreeterFutureStub extends io.grpc.stub.AbstractStub 169 | implements GreeterFutureClient { 170 | private GreeterFutureStub(io.grpc.Channel channel) { 171 | super(channel); 172 | } 173 | 174 | private GreeterFutureStub(io.grpc.Channel channel, 175 | io.grpc.CallOptions callOptions) { 176 | super(channel, callOptions); 177 | } 178 | 179 | @java.lang.Override 180 | protected GreeterFutureStub build(io.grpc.Channel channel, 181 | io.grpc.CallOptions callOptions) { 182 | return new GreeterFutureStub(channel, callOptions); 183 | } 184 | 185 | @java.lang.Override 186 | public com.google.common.util.concurrent.ListenableFuture sayHello( 187 | io.opentracing.contrib.HelloRequest request) { 188 | return futureUnaryCall( 189 | getChannel().newCall(METHOD_SAY_HELLO, getCallOptions()), request); 190 | } 191 | } 192 | 193 | @java.lang.Deprecated public static abstract class AbstractGreeter extends GreeterImplBase {} 194 | 195 | private static final int METHODID_SAY_HELLO = 0; 196 | 197 | private static class MethodHandlers implements 198 | io.grpc.stub.ServerCalls.UnaryMethod, 199 | io.grpc.stub.ServerCalls.ServerStreamingMethod, 200 | io.grpc.stub.ServerCalls.ClientStreamingMethod, 201 | io.grpc.stub.ServerCalls.BidiStreamingMethod { 202 | private final Greeter serviceImpl; 203 | private final int methodId; 204 | 205 | public MethodHandlers(Greeter serviceImpl, int methodId) { 206 | this.serviceImpl = serviceImpl; 207 | this.methodId = methodId; 208 | } 209 | 210 | @java.lang.Override 211 | @java.lang.SuppressWarnings("unchecked") 212 | public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { 213 | switch (methodId) { 214 | case METHODID_SAY_HELLO: 215 | serviceImpl.sayHello((io.opentracing.contrib.HelloRequest) request, 216 | (io.grpc.stub.StreamObserver) responseObserver); 217 | break; 218 | default: 219 | throw new AssertionError(); 220 | } 221 | } 222 | 223 | @java.lang.Override 224 | @java.lang.SuppressWarnings("unchecked") 225 | public io.grpc.stub.StreamObserver invoke( 226 | io.grpc.stub.StreamObserver responseObserver) { 227 | switch (methodId) { 228 | default: 229 | throw new AssertionError(); 230 | } 231 | } 232 | } 233 | 234 | public static io.grpc.ServiceDescriptor getServiceDescriptor() { 235 | return new io.grpc.ServiceDescriptor(SERVICE_NAME, 236 | METHOD_SAY_HELLO); 237 | } 238 | 239 | @java.lang.Deprecated public static io.grpc.ServerServiceDefinition bindService( 240 | final Greeter serviceImpl) { 241 | return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) 242 | .addMethod( 243 | METHOD_SAY_HELLO, 244 | asyncUnaryCall( 245 | new MethodHandlers< 246 | io.opentracing.contrib.HelloRequest, 247 | io.opentracing.contrib.HelloReply>( 248 | serviceImpl, METHODID_SAY_HELLO))) 249 | .build(); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /python/grpc_opentracing/_client.py: -------------------------------------------------------------------------------- 1 | """Implementation of the invocation-side open-tracing interceptor.""" 2 | 3 | import sys 4 | import logging 5 | import time 6 | 7 | from six import iteritems 8 | 9 | import grpc 10 | from grpc_opentracing import grpcext 11 | from grpc_opentracing._utilities import get_method_type, get_deadline_millis,\ 12 | log_or_wrap_request_or_iterator, RpcInfo 13 | import opentracing 14 | from opentracing.ext import tags as ot_tags 15 | 16 | 17 | class _GuardedSpan(object): 18 | 19 | def __init__(self, span): 20 | self.span = span 21 | self._engaged = True 22 | 23 | def __enter__(self): 24 | self.span.__enter__() 25 | return self 26 | 27 | def __exit__(self, *args, **kwargs): 28 | if self._engaged: 29 | return self.span.__exit__(*args, **kwargs) 30 | else: 31 | return False 32 | 33 | def release(self): 34 | self._engaged = False 35 | return self.span 36 | 37 | 38 | def _inject_span_context(tracer, span, metadata): 39 | headers = {} 40 | try: 41 | tracer.inject(span.context, opentracing.Format.HTTP_HEADERS, headers) 42 | except (opentracing.UnsupportedFormatException, 43 | opentracing.InvalidCarrierException, 44 | opentracing.SpanContextCorruptedException) as e: 45 | logging.exception('tracer.inject() failed') 46 | span.log_kv({'event': 'error', 'error.object': e}) 47 | return metadata 48 | metadata = () if metadata is None else tuple(metadata) 49 | return metadata + tuple(iteritems(headers)) 50 | 51 | 52 | def _make_future_done_callback(span, rpc_info, log_payloads, span_decorator): 53 | 54 | def callback(response_future): 55 | with span: 56 | code = response_future.code() 57 | if code != grpc.StatusCode.OK: 58 | span.set_tag('error', True) 59 | error_log = {'event': 'error', 'error.kind': str(code)} 60 | details = response_future.details() 61 | if details is not None: 62 | error_log['message'] = details 63 | span.log_kv(error_log) 64 | rpc_info.error = code 65 | if span_decorator is not None: 66 | span_decorator(span, rpc_info) 67 | return 68 | response = response_future.result() 69 | rpc_info.response = response 70 | if log_payloads: 71 | span.log_kv({'response': response}) 72 | if span_decorator is not None: 73 | span_decorator(span, rpc_info) 74 | 75 | return callback 76 | 77 | 78 | class OpenTracingClientInterceptor(grpcext.UnaryClientInterceptor, 79 | grpcext.StreamClientInterceptor): 80 | 81 | def __init__(self, tracer, active_span_source, log_payloads, 82 | span_decorator): 83 | self._tracer = tracer 84 | self._active_span_source = active_span_source 85 | self._log_payloads = log_payloads 86 | self._span_decorator = span_decorator 87 | 88 | def _start_span(self, method): 89 | active_span_context = None 90 | if self._active_span_source is not None: 91 | active_span = self._active_span_source.get_active_span() 92 | if active_span is not None: 93 | active_span_context = active_span.context 94 | tags = { 95 | ot_tags.COMPONENT: 'grpc', 96 | ot_tags.SPAN_KIND: ot_tags.SPAN_KIND_RPC_CLIENT 97 | } 98 | return self._tracer.start_span( 99 | operation_name=method, child_of=active_span_context, tags=tags) 100 | 101 | def _trace_result(self, guarded_span, rpc_info, result): 102 | # If the RPC is called asynchronously, release the guard and add a callback 103 | # so that the span can be finished once the future is done. 104 | if isinstance(result, grpc.Future): 105 | result.add_done_callback( 106 | _make_future_done_callback(guarded_span.release( 107 | ), rpc_info, self._log_payloads, self._span_decorator)) 108 | return result 109 | response = result 110 | # Handle the case when the RPC is initiated via the with_call 111 | # method and the result is a tuple with the first element as the 112 | # response. 113 | # http://www.grpc.io/grpc/python/grpc.html#grpc.UnaryUnaryMultiCallable.with_call 114 | if isinstance(result, tuple): 115 | response = result[0] 116 | rpc_info.response = response 117 | if self._log_payloads: 118 | guarded_span.span.log_kv({'response': response}) 119 | if self._span_decorator is not None: 120 | self._span_decorator(guarded_span.span, rpc_info) 121 | return result 122 | 123 | def _start_guarded_span(self, *args, **kwargs): 124 | return _GuardedSpan(self._start_span(*args, **kwargs)) 125 | 126 | def intercept_unary(self, request, metadata, client_info, invoker): 127 | with self._start_guarded_span(client_info.full_method) as guarded_span: 128 | metadata = _inject_span_context(self._tracer, guarded_span.span, 129 | metadata) 130 | rpc_info = RpcInfo( 131 | full_method=client_info.full_method, 132 | metadata=metadata, 133 | timeout=client_info.timeout, 134 | request=request) 135 | if self._log_payloads: 136 | guarded_span.span.log_kv({'request': request}) 137 | try: 138 | result = invoker(request, metadata) 139 | except: 140 | e = sys.exc_info()[0] 141 | guarded_span.span.set_tag('error', True) 142 | guarded_span.span.log_kv({'event': 'error', 'error.object': e}) 143 | rpc_info.error = e 144 | if self._span_decorator is not None: 145 | self._span_decorator(guarded_span.span, rpc_info) 146 | raise 147 | return self._trace_result(guarded_span, rpc_info, result) 148 | 149 | # For RPCs that stream responses, the result can be a generator. To record 150 | # the span across the generated responses and detect any errors, we wrap the 151 | # result in a new generator that yields the response values. 152 | def _intercept_server_stream(self, request_or_iterator, metadata, 153 | client_info, invoker): 154 | with self._start_span(client_info.full_method) as span: 155 | metadata = _inject_span_context(self._tracer, span, metadata) 156 | rpc_info = RpcInfo( 157 | full_method=client_info.full_method, 158 | metadata=metadata, 159 | timeout=client_info.timeout) 160 | if client_info.is_client_stream: 161 | rpc_info.request = request_or_iterator 162 | if self._log_payloads: 163 | request_or_iterator = log_or_wrap_request_or_iterator( 164 | span, client_info.is_client_stream, request_or_iterator) 165 | try: 166 | result = invoker(request_or_iterator, metadata) 167 | for response in result: 168 | if self._log_payloads: 169 | span.log_kv({'response': response}) 170 | yield response 171 | except: 172 | e = sys.exc_info()[0] 173 | span.set_tag('error', True) 174 | span.log_kv({'event': 'error', 'error.object': e}) 175 | rpc_info.error = e 176 | if self._span_decorator is not None: 177 | self._span_decorator(span, rpc_info) 178 | raise 179 | if self._span_decorator is not None: 180 | self._span_decorator(span, rpc_info) 181 | 182 | def intercept_stream(self, request_or_iterator, metadata, client_info, 183 | invoker): 184 | if client_info.is_server_stream: 185 | return self._intercept_server_stream(request_or_iterator, metadata, 186 | client_info, invoker) 187 | with self._start_guarded_span(client_info.full_method) as guarded_span: 188 | metadata = _inject_span_context(self._tracer, guarded_span.span, 189 | metadata) 190 | rpc_info = RpcInfo( 191 | full_method=client_info.full_method, 192 | metadata=metadata, 193 | timeout=client_info.timeout, 194 | request=request_or_iterator) 195 | if self._log_payloads: 196 | request_or_iterator = log_or_wrap_request_or_iterator( 197 | guarded_span.span, client_info.is_client_stream, 198 | request_or_iterator) 199 | try: 200 | result = invoker(request_or_iterator, metadata) 201 | except: 202 | e = sys.exc_info()[0] 203 | guarded_span.span.set_tag('error', True) 204 | guarded_span.span.log_kv({'event': 'error', 'error.object': e}) 205 | rpc_info.error = e 206 | if self._span_decorator is not None: 207 | self._span_decorator(guarded_span.span, rpc_info) 208 | raise 209 | return self._trace_result(guarded_span, rpc_info, result) 210 | -------------------------------------------------------------------------------- /java/src/main/java/io/opentracing/contrib/ServerTracingInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.grpc.BindableService; 5 | import io.grpc.Context; 6 | import io.grpc.Contexts; 7 | import io.grpc.Metadata; 8 | import io.grpc.ServerCall; 9 | import io.grpc.ServerCallHandler; 10 | import io.grpc.ServerInterceptor; 11 | import io.grpc.ServerInterceptors; 12 | import io.grpc.ServerServiceDefinition; 13 | import io.grpc.ForwardingServerCallListener; 14 | 15 | import io.opentracing.propagation.Format; 16 | import io.opentracing.propagation.TextMapExtractAdapter; 17 | import io.opentracing.Span; 18 | import io.opentracing.SpanContext; 19 | import io.opentracing.Tracer; 20 | 21 | import java.util.Arrays; 22 | import java.util.HashMap; 23 | import java.util.HashSet; 24 | import java.util.Map; 25 | import java.util.Set; 26 | 27 | /** 28 | * An intercepter that applies tracing via OpenTracing to all requests 29 | * to the server. 30 | */ 31 | public class ServerTracingInterceptor implements ServerInterceptor { 32 | 33 | private final Tracer tracer; 34 | private final OperationNameConstructor operationNameConstructor; 35 | private final boolean streaming; 36 | private final boolean verbose; 37 | private final Set tracedAttributes; 38 | 39 | /** 40 | * @param tracer used to trace requests 41 | */ 42 | public ServerTracingInterceptor(Tracer tracer) { 43 | this.tracer = tracer; 44 | this.operationNameConstructor = OperationNameConstructor.DEFAULT; 45 | this.streaming = false; 46 | this.verbose = false; 47 | this.tracedAttributes = new HashSet(); 48 | } 49 | 50 | private ServerTracingInterceptor(Tracer tracer, OperationNameConstructor operationNameConstructor, boolean streaming, 51 | boolean verbose, Set tracedAttributes) { 52 | this.tracer = tracer; 53 | this.operationNameConstructor = operationNameConstructor; 54 | this.streaming = streaming; 55 | this.verbose = verbose; 56 | this.tracedAttributes = tracedAttributes; 57 | } 58 | 59 | /** 60 | * Add tracing to all requests made to this service. 61 | * @param serviceDef of the service to intercept 62 | * @return the serviceDef with a tracing interceptor 63 | */ 64 | public ServerServiceDefinition intercept(ServerServiceDefinition serviceDef) { 65 | return ServerInterceptors.intercept(serviceDef, this); 66 | } 67 | 68 | /** 69 | * Add tracing to all requests made to this service. 70 | * @param bindableService to intercept 71 | * @return the serviceDef with a tracing interceptor 72 | */ 73 | public ServerServiceDefinition intercept(BindableService bindableService) { 74 | return ServerInterceptors.intercept(bindableService, this); 75 | } 76 | 77 | @Override 78 | public ServerCall.Listener interceptCall( 79 | ServerCall call, 80 | Metadata headers, 81 | ServerCallHandler next 82 | ) { 83 | Map headerMap = new HashMap(); 84 | for (String key : headers.keys()) { 85 | if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { 86 | String value = headers.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); 87 | headerMap.put(key, value); 88 | } 89 | 90 | } 91 | 92 | final String operationName = operationNameConstructor.constructOperationName(call.getMethodDescriptor()); 93 | final Span span = getSpanFromHeaders(headerMap, operationName); 94 | 95 | for (ServerRequestAttribute attr : this.tracedAttributes) { 96 | switch (attr) { 97 | case METHOD_TYPE: 98 | span.setTag("grpc.method_type", call.getMethodDescriptor().getType().toString()); 99 | break; 100 | case METHOD_NAME: 101 | span.setTag("grpc.method_name", call.getMethodDescriptor().getFullMethodName()); 102 | break; 103 | case CALL_ATTRIBUTES: 104 | span.setTag("grpc.call_attributes", call.getAttributes().toString()); 105 | break; 106 | case HEADERS: 107 | span.setTag("grpc.headers", headers.toString()); 108 | break; 109 | } 110 | } 111 | 112 | Context ctxWithSpan = Context.current().withValue(OpenTracingContextKey.getKey(), span); 113 | ServerCall.Listener listenerWithContext = Contexts 114 | .interceptCall(ctxWithSpan, call, headers, next); 115 | 116 | ServerCall.Listener tracingListenerWithContext = 117 | new ForwardingServerCallListener.SimpleForwardingServerCallListener(listenerWithContext) { 118 | 119 | @Override 120 | public void onMessage(ReqT message) { 121 | if (streaming || verbose) { span.log(ImmutableMap.of("Message received", message)); } 122 | delegate().onMessage(message); 123 | } 124 | 125 | @Override 126 | public void onHalfClose() { 127 | if (streaming) { span.log("Client finished sending messages"); } 128 | delegate().onHalfClose(); 129 | } 130 | 131 | @Override 132 | public void onCancel() { 133 | span.log("Call cancelled"); 134 | span.finish(); 135 | delegate().onCancel(); 136 | } 137 | 138 | @Override 139 | public void onComplete() { 140 | if (verbose) { span.log("Call completed"); } 141 | span.finish(); 142 | delegate().onComplete(); 143 | } 144 | }; 145 | 146 | return tracingListenerWithContext; 147 | } 148 | 149 | private Span getSpanFromHeaders(Map headers, String operationName) { 150 | Span span; 151 | try { 152 | SpanContext parentSpanCtx = tracer.extract(Format.Builtin.HTTP_HEADERS, 153 | new TextMapExtractAdapter(headers)); 154 | if (parentSpanCtx == null) { 155 | span = tracer.buildSpan(operationName).startManual(); 156 | } else { 157 | span = tracer.buildSpan(operationName).asChildOf(parentSpanCtx).startManual(); 158 | } 159 | } catch (IllegalArgumentException iae){ 160 | span = tracer.buildSpan(operationName) 161 | .withTag("Error", "Extract failed and an IllegalArgumentException was thrown") 162 | .startManual(); 163 | } 164 | return span; 165 | } 166 | 167 | /** 168 | * Builds the configuration of a ServerTracingInterceptor. 169 | */ 170 | public static class Builder { 171 | private final Tracer tracer; 172 | private OperationNameConstructor operationNameConstructor; 173 | private boolean streaming; 174 | private boolean verbose; 175 | private Set tracedAttributes; 176 | 177 | /** 178 | * @param tracer to use for this intercepter 179 | * Creates a Builder with default configuration 180 | */ 181 | public Builder(Tracer tracer) { 182 | this.tracer = tracer; 183 | this.operationNameConstructor = OperationNameConstructor.DEFAULT; 184 | this.streaming = false; 185 | this.verbose = false; 186 | this.tracedAttributes = new HashSet(); 187 | } 188 | 189 | /** 190 | * @param operationNameConstructor for all spans created by this intercepter 191 | * @return this Builder with configured operation name 192 | */ 193 | public Builder withOperationName(OperationNameConstructor operationNameConstructor) { 194 | this.operationNameConstructor = operationNameConstructor; 195 | return this; 196 | } 197 | 198 | /** 199 | * @param attributes to set as tags on server spans 200 | * created by this intercepter 201 | * @return this Builder configured to trace request attributes 202 | */ 203 | public Builder withTracedAttributes(ServerRequestAttribute... attributes) { 204 | this.tracedAttributes = new HashSet(Arrays.asList(attributes)); 205 | return this; 206 | } 207 | 208 | /** 209 | * Logs streaming events to server spans. 210 | * @return this Builder configured to log streaming events 211 | */ 212 | public Builder withStreaming() { 213 | this.streaming = true; 214 | return this; 215 | } 216 | 217 | /** 218 | * Logs all request life-cycle events to server spans. 219 | * @return this Builder configured to be verbose 220 | */ 221 | public Builder withVerbosity() { 222 | this.verbose = true; 223 | return this; 224 | } 225 | 226 | /** 227 | * @return a ServerTracingInterceptor with this Builder's configuration 228 | */ 229 | public ServerTracingInterceptor build() { 230 | return new ServerTracingInterceptor(this.tracer, this.operationNameConstructor, 231 | this.streaming, this.verbose, this.tracedAttributes); 232 | } 233 | } 234 | 235 | public enum ServerRequestAttribute { 236 | HEADERS, 237 | METHOD_TYPE, 238 | METHOD_NAME, 239 | CALL_ATTRIBUTES 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /python/grpc_opentracing/_server.py: -------------------------------------------------------------------------------- 1 | """Implementation of the service-side open-tracing interceptor.""" 2 | 3 | import sys 4 | import logging 5 | import re 6 | 7 | import grpc 8 | from grpc_opentracing import grpcext, ActiveSpanSource 9 | from grpc_opentracing._utilities import get_method_type, get_deadline_millis,\ 10 | log_or_wrap_request_or_iterator, RpcInfo 11 | import opentracing 12 | from opentracing.ext import tags as ot_tags 13 | 14 | 15 | class _OpenTracingServicerContext(grpc.ServicerContext, ActiveSpanSource): 16 | 17 | def __init__(self, servicer_context, active_span): 18 | self._servicer_context = servicer_context 19 | self._active_span = active_span 20 | self.code = grpc.StatusCode.OK 21 | self.details = None 22 | 23 | def is_active(self, *args, **kwargs): 24 | return self._servicer_context.is_active(*args, **kwargs) 25 | 26 | def time_remaining(self, *args, **kwargs): 27 | return self._servicer_context.time_remaining(*args, **kwargs) 28 | 29 | def cancel(self, *args, **kwargs): 30 | return self._servicer_context.cancel(*args, **kwargs) 31 | 32 | def add_callback(self, *args, **kwargs): 33 | return self._servicer_context.add_callback(*args, **kwargs) 34 | 35 | def invocation_metadata(self, *args, **kwargs): 36 | return self._servicer_context.invocation_metadata(*args, **kwargs) 37 | 38 | def peer(self, *args, **kwargs): 39 | return self._servicer_context.peer(*args, **kwargs) 40 | 41 | def peer_identities(self, *args, **kwargs): 42 | return self._servicer_context.peer_identities(*args, **kwargs) 43 | 44 | def peer_identity_key(self, *args, **kwargs): 45 | return self._servicer_context.peer_identity_key(*args, **kwargs) 46 | 47 | def auth_context(self, *args, **kwargs): 48 | return self._servicer_context.auth_context(*args, **kwargs) 49 | 50 | def send_initial_metadata(self, *args, **kwargs): 51 | return self._servicer_context.send_initial_metadata(*args, **kwargs) 52 | 53 | def set_trailing_metadata(self, *args, **kwargs): 54 | return self._servicer_context.set_trailing_metadata(*args, **kwargs) 55 | 56 | def set_code(self, code): 57 | self.code = code 58 | return self._servicer_context.set_code(code) 59 | 60 | def set_details(self, details): 61 | self.details = details 62 | return self._servicer_context.set_details(details) 63 | 64 | def get_active_span(self): 65 | return self._active_span 66 | 67 | 68 | def _add_peer_tags(peer_str, tags): 69 | ipv4_re = r"ipv4:(?P
.+):(?P\d+)" 70 | match = re.match(ipv4_re, peer_str) 71 | if match: 72 | tags[ot_tags.PEER_HOST_IPV4] = match.group('address') 73 | tags[ot_tags.PEER_PORT] = match.group('port') 74 | return 75 | ipv6_re = r"ipv6:\[(?P
.+)\]:(?P\d+)" 76 | match = re.match(ipv6_re, peer_str) 77 | if match: 78 | tags[ot_tags.PEER_HOST_IPV6] = match.group('address') 79 | tags[ot_tags.PEER_PORT] = match.group('port') 80 | return 81 | logging.warning('Unrecognized peer: \"%s\"', peer_str) 82 | 83 | 84 | # On the service-side, errors can be signaled either by exceptions or by calling 85 | # `set_code` on the `servicer_context`. This function checks for the latter and 86 | # updates the span accordingly. 87 | def _check_error_code(span, servicer_context, rpc_info): 88 | if servicer_context.code != grpc.StatusCode.OK: 89 | span.set_tag('error', True) 90 | error_log = {'event': 'error', 'error.kind': str(servicer_context.code)} 91 | if servicer_context.details is not None: 92 | error_log['message'] = servicer_context.details 93 | span.log_kv(error_log) 94 | rpc_info.error = servicer_context.code 95 | 96 | 97 | class OpenTracingServerInterceptor(grpcext.UnaryServerInterceptor, 98 | grpcext.StreamServerInterceptor): 99 | 100 | def __init__(self, tracer, log_payloads, span_decorator): 101 | self._tracer = tracer 102 | self._log_payloads = log_payloads 103 | self._span_decorator = span_decorator 104 | 105 | def _start_span(self, servicer_context, method): 106 | span_context = None 107 | error = None 108 | metadata = servicer_context.invocation_metadata() 109 | try: 110 | if metadata: 111 | span_context = self._tracer.extract( 112 | opentracing.Format.HTTP_HEADERS, dict(metadata)) 113 | except (opentracing.UnsupportedFormatException, 114 | opentracing.InvalidCarrierException, 115 | opentracing.SpanContextCorruptedException) as e: 116 | logging.exception('tracer.extract() failed') 117 | error = e 118 | tags = { 119 | ot_tags.COMPONENT: 'grpc', 120 | ot_tags.SPAN_KIND: ot_tags.SPAN_KIND_RPC_SERVER 121 | } 122 | _add_peer_tags(servicer_context.peer(), tags) 123 | span = self._tracer.start_span( 124 | operation_name=method, child_of=span_context, tags=tags) 125 | if error is not None: 126 | span.log_kv({'event': 'error', 'error.object': error}) 127 | return span 128 | 129 | def intercept_unary(self, request, servicer_context, server_info, handler): 130 | with self._start_span(servicer_context, 131 | server_info.full_method) as span: 132 | rpc_info = RpcInfo( 133 | full_method=server_info.full_method, 134 | metadata=servicer_context.invocation_metadata(), 135 | timeout=servicer_context.time_remaining(), 136 | request=request) 137 | if self._log_payloads: 138 | span.log_kv({'request': request}) 139 | servicer_context = _OpenTracingServicerContext( 140 | servicer_context, span) 141 | try: 142 | response = handler(request, servicer_context) 143 | except: 144 | e = sys.exc_info()[0] 145 | span.set_tag('error', True) 146 | span.log_kv({'event': 'error', 'error.object': e}) 147 | rpc_info.error = e 148 | if self._span_decorator is not None: 149 | self._span_decorator(span, rpc_info) 150 | raise 151 | if self._log_payloads: 152 | span.log_kv({'response': response}) 153 | _check_error_code(span, servicer_context, rpc_info) 154 | rpc_info.response = response 155 | if self._span_decorator is not None: 156 | self._span_decorator(span, rpc_info) 157 | return response 158 | 159 | # For RPCs that stream responses, the result can be a generator. To record 160 | # the span across the generated responses and detect any errors, we wrap the 161 | # result in a new generator that yields the response values. 162 | def _intercept_server_stream(self, request_or_iterator, servicer_context, 163 | server_info, handler): 164 | with self._start_span(servicer_context, 165 | server_info.full_method) as span: 166 | rpc_info = RpcInfo( 167 | full_method=server_info.full_method, 168 | metadata=servicer_context.invocation_metadata(), 169 | timeout=servicer_context.time_remaining()) 170 | if not server_info.is_client_stream: 171 | rpc_info.request = request_or_iterator 172 | if self._log_payloads: 173 | request_or_iterator = log_or_wrap_request_or_iterator( 174 | span, server_info.is_client_stream, request_or_iterator) 175 | servicer_context = _OpenTracingServicerContext( 176 | servicer_context, span) 177 | try: 178 | result = handler(request_or_iterator, servicer_context) 179 | for response in result: 180 | if self._log_payloads: 181 | span.log_kv({'response': response}) 182 | yield response 183 | except: 184 | e = sys.exc_info()[0] 185 | span.set_tag('error', True) 186 | span.log_kv({'event': 'error', 'error.object': e}) 187 | rpc_info.error = e 188 | if self._span_decorator is not None: 189 | self._span_decorator(span, rpc_info) 190 | raise 191 | _check_error_code(span, servicer_context, rpc_info) 192 | if self._span_decorator is not None: 193 | self._span_decorator(span, rpc_info) 194 | 195 | def intercept_stream(self, request_or_iterator, servicer_context, 196 | server_info, handler): 197 | if server_info.is_server_stream: 198 | return self._intercept_server_stream( 199 | request_or_iterator, servicer_context, server_info, handler) 200 | with self._start_span(servicer_context, 201 | server_info.full_method) as span: 202 | rpc_info = RpcInfo( 203 | full_method=server_info.full_method, 204 | metadata=servicer_context.invocation_metadata(), 205 | timeout=servicer_context.time_remaining()) 206 | if self._log_payloads: 207 | request_or_iterator = log_or_wrap_request_or_iterator( 208 | span, server_info.is_client_stream, request_or_iterator) 209 | servicer_context = _OpenTracingServicerContext( 210 | servicer_context, span) 211 | try: 212 | response = handler(request_or_iterator, servicer_context) 213 | except: 214 | e = sys.exc_info()[0] 215 | span.set_tag('error', True) 216 | span.log_kv({'event': 'error', 'error.object': e}) 217 | rpc_info.error = e 218 | if self._span_decorator is not None: 219 | self._span_decorator(span, rpc_info) 220 | raise 221 | if self._log_payloads: 222 | span.log_kv({'response': response}) 223 | _check_error_code(span, servicer_context, rpc_info) 224 | rpc_info.response = response 225 | if self._span_decorator is not None: 226 | self._span_decorator(span, rpc_info) 227 | return response 228 | -------------------------------------------------------------------------------- /java/src/main/java/io/opentracing/contrib/ClientTracingInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.grpc.*; 5 | import io.opentracing.Span; 6 | import io.opentracing.Tracer; 7 | import io.opentracing.propagation.Format; 8 | import io.opentracing.propagation.TextMap; 9 | 10 | import javax.annotation.Nullable; 11 | import java.util.Map.Entry; 12 | import java.util.*; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * An intercepter that applies tracing via OpenTracing to all client requests. 17 | */ 18 | public class ClientTracingInterceptor implements ClientInterceptor { 19 | 20 | private final Tracer tracer; 21 | private final OperationNameConstructor operationNameConstructor; 22 | private final boolean streaming; 23 | private final boolean verbose; 24 | private final Set tracedAttributes; 25 | private final ActiveSpanSource activeSpanSource; 26 | 27 | /** 28 | * @param tracer to use to trace requests 29 | */ 30 | public ClientTracingInterceptor(Tracer tracer) { 31 | this.tracer = tracer; 32 | this.operationNameConstructor = OperationNameConstructor.DEFAULT; 33 | this.streaming = false; 34 | this.verbose = false; 35 | this.tracedAttributes = new HashSet(); 36 | this.activeSpanSource = ActiveSpanSource.GRPC_CONTEXT; 37 | } 38 | 39 | private ClientTracingInterceptor(Tracer tracer, OperationNameConstructor operationNameConstructor, boolean streaming, 40 | boolean verbose, Set tracedAttributes, ActiveSpanSource activeSpanSource) { 41 | this.tracer = tracer; 42 | this.operationNameConstructor = operationNameConstructor; 43 | this.streaming = streaming; 44 | this.verbose = verbose; 45 | this.tracedAttributes = tracedAttributes; 46 | this.activeSpanSource = activeSpanSource; 47 | } 48 | 49 | /** 50 | * Use this intercepter to trace all requests made by this client channel. 51 | * @param channel to be traced 52 | * @return intercepted channel 53 | */ 54 | public Channel intercept(Channel channel) { 55 | return ClientInterceptors.intercept(channel, this); 56 | } 57 | 58 | @Override 59 | public ClientCall interceptCall( 60 | MethodDescriptor method, 61 | CallOptions callOptions, 62 | Channel next 63 | ) { 64 | final String operationName = operationNameConstructor.constructOperationName(method); 65 | 66 | Span activeSpan = this.activeSpanSource.getActiveSpan(); 67 | final Span span = createSpanFromParent(activeSpan, operationName); 68 | 69 | for (ClientRequestAttribute attr : this.tracedAttributes) { 70 | switch (attr) { 71 | case ALL_CALL_OPTIONS: 72 | span.setTag("grpc.call_options", callOptions.toString()); 73 | break; 74 | case AUTHORITY: 75 | if (callOptions.getAuthority() == null) { 76 | span.setTag("grpc.authority", "null"); 77 | } else { 78 | span.setTag("grpc.authority", callOptions.getAuthority()); 79 | } 80 | break; 81 | case COMPRESSOR: 82 | if (callOptions.getCompressor() == null) { 83 | span.setTag("grpc.compressor", "null"); 84 | } else { 85 | span.setTag("grpc.compressor", callOptions.getCompressor()); 86 | } 87 | break; 88 | case DEADLINE: 89 | if (callOptions.getDeadline() == null) { 90 | span.setTag("grpc.deadline_millis", "null"); 91 | } else { 92 | span.setTag("grpc.deadline_millis", callOptions.getDeadline().timeRemaining(TimeUnit.MILLISECONDS)); 93 | } 94 | break; 95 | case METHOD_NAME: 96 | span.setTag("grpc.method_name", method.getFullMethodName()); 97 | break; 98 | case METHOD_TYPE: 99 | if (method.getType() == null) { 100 | span.setTag("grpc.method_type", "null"); 101 | } else { 102 | span.setTag("grpc.method_type", method.getType().toString()); 103 | } 104 | break; 105 | case HEADERS: 106 | break; 107 | } 108 | } 109 | 110 | return new ForwardingClientCall.SimpleForwardingClientCall(next.newCall(method, callOptions)) { 111 | 112 | @Override 113 | public void start(Listener responseListener, Metadata headers) { 114 | if (verbose) { 115 | span.log("Started call"); 116 | } 117 | if (tracedAttributes.contains(ClientRequestAttribute.HEADERS)) { 118 | span.setTag("grpc.headers", headers.toString()); 119 | } 120 | 121 | tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMap() { 122 | @Override 123 | public void put(String key, String value) { 124 | Metadata.Key headerKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); 125 | headers.put(headerKey, value); 126 | } 127 | @Override 128 | public Iterator> iterator() { 129 | throw new UnsupportedOperationException( 130 | "TextMapInjectAdapter should only be used with Tracer.inject()"); 131 | } 132 | }); 133 | 134 | Listener tracingResponseListener = new ForwardingClientCallListener 135 | .SimpleForwardingClientCallListener(responseListener) { 136 | 137 | @Override 138 | public void onHeaders(Metadata headers) { 139 | if (verbose) { span.log(ImmutableMap.of("Response headers received", headers.toString())); } 140 | delegate().onHeaders(headers); 141 | } 142 | 143 | @Override 144 | public void onMessage(RespT message) { 145 | if (streaming || verbose) { span.log("Response received"); } 146 | delegate().onMessage(message); 147 | } 148 | 149 | @Override 150 | public void onClose(Status status, Metadata trailers) { 151 | if (verbose) { 152 | if (status.getCode().value() == 0) { span.log("Call closed"); } 153 | else { span.log(ImmutableMap.of("Call failed", status.getDescription())); } 154 | } 155 | span.finish(); 156 | delegate().onClose(status, trailers); 157 | } 158 | }; 159 | delegate().start(tracingResponseListener, headers); 160 | } 161 | 162 | @Override 163 | public void cancel(@Nullable String message, @Nullable Throwable cause) { 164 | String errorMessage; 165 | if (message == null) { 166 | errorMessage = "Error"; 167 | } else { 168 | errorMessage = message; 169 | } 170 | if (cause == null) { 171 | span.log(errorMessage); 172 | } else { 173 | span.log(ImmutableMap.of(errorMessage, cause.getMessage())); 174 | } 175 | delegate().cancel(message, cause); 176 | } 177 | 178 | @Override 179 | public void halfClose() { 180 | if (streaming) { span.log("Finished sending messages"); } 181 | delegate().halfClose(); 182 | } 183 | 184 | @Override 185 | public void sendMessage(ReqT message) { 186 | if (streaming || verbose) { span.log("Message sent"); } 187 | delegate().sendMessage(message); 188 | } 189 | }; 190 | } 191 | 192 | private Span createSpanFromParent(Span parentSpan, String operationName) { 193 | if (parentSpan == null) { 194 | return tracer.buildSpan(operationName).startManual(); 195 | } else { 196 | return tracer.buildSpan(operationName).asChildOf(parentSpan).startManual(); 197 | } 198 | } 199 | 200 | /** 201 | * Builds the configuration of a ClientTracingInterceptor. 202 | */ 203 | public static class Builder { 204 | 205 | private Tracer tracer; 206 | private OperationNameConstructor operationNameConstructor; 207 | private boolean streaming; 208 | private boolean verbose; 209 | private Set tracedAttributes; 210 | private ActiveSpanSource activeSpanSource; 211 | 212 | /** 213 | * @param tracer to use for this intercepter 214 | * Creates a Builder with default configuration 215 | */ 216 | public Builder(Tracer tracer) { 217 | this.tracer = tracer; 218 | this.operationNameConstructor = OperationNameConstructor.DEFAULT; 219 | this.streaming = false; 220 | this.verbose = false; 221 | this.tracedAttributes = new HashSet(); 222 | this.activeSpanSource = ActiveSpanSource.GRPC_CONTEXT; 223 | } 224 | 225 | /** 226 | * @param operationNameConstructor to name all spans created by this intercepter 227 | * @return this Builder with configured operation name 228 | */ 229 | public Builder withOperationName(OperationNameConstructor operationNameConstructor) { 230 | this.operationNameConstructor = operationNameConstructor; 231 | return this; 232 | } 233 | 234 | /** 235 | * Logs streaming events to client spans. 236 | * @return this Builder configured to log streaming events 237 | */ 238 | public Builder withStreaming() { 239 | this.streaming = true; 240 | return this; 241 | } 242 | 243 | /** 244 | * @param tracedAttributes to set as tags on client spans 245 | * created by this intercepter 246 | * @return this Builder configured to trace attributes 247 | */ 248 | public Builder withTracedAttributes(ClientRequestAttribute... tracedAttributes) { 249 | this.tracedAttributes = new HashSet( 250 | Arrays.asList(tracedAttributes)); 251 | return this; 252 | } 253 | 254 | /** 255 | * Logs all request life-cycle events to client spans. 256 | * @return this Builder configured to be verbose 257 | */ 258 | public Builder withVerbosity() { 259 | this.verbose = true; 260 | return this; 261 | } 262 | 263 | /** 264 | * @param activeSpanSource that provides a method of getting the 265 | * active span before the client call 266 | * @return this Builder configured to start client span as children 267 | * of the span returned by activeSpanSource.getActiveSpan() 268 | */ 269 | public Builder withActiveSpanSource(ActiveSpanSource activeSpanSource) { 270 | this.activeSpanSource = activeSpanSource; 271 | return this; 272 | } 273 | 274 | /** 275 | * @return a ClientTracingInterceptor with this Builder's configuration 276 | */ 277 | public ClientTracingInterceptor build() { 278 | return new ClientTracingInterceptor(this.tracer, this.operationNameConstructor, 279 | this.streaming, this.verbose, this.tracedAttributes, this.activeSpanSource); 280 | } 281 | } 282 | 283 | public enum ClientRequestAttribute { 284 | METHOD_TYPE, 285 | METHOD_NAME, 286 | DEADLINE, 287 | COMPRESSOR, 288 | AUTHORITY, 289 | ALL_CALL_OPTIONS, 290 | HEADERS 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /go/otgrpc/test/otgrpc_testing/test.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: test.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package otgrpc_testing is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | test.proto 10 | 11 | It has these top-level messages: 12 | SimpleRequest 13 | SimpleResponse 14 | */ 15 | package otgrpc_testing 16 | 17 | import proto "github.com/golang/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | import ( 22 | context "golang.org/x/net/context" 23 | grpc "google.golang.org/grpc" 24 | ) 25 | 26 | // Reference imports to suppress errors if they are not otherwise used. 27 | var _ = proto.Marshal 28 | var _ = fmt.Errorf 29 | var _ = math.Inf 30 | 31 | // This is a compile-time assertion to ensure that this generated file 32 | // is compatible with the proto package it is being compiled against. 33 | // A compilation error at this line likely means your copy of the 34 | // proto package needs to be updated. 35 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 36 | 37 | type SimpleRequest struct { 38 | Payload int32 `protobuf:"varint,1,opt,name=payload" json:"payload,omitempty"` 39 | } 40 | 41 | func (m *SimpleRequest) Reset() { *m = SimpleRequest{} } 42 | func (m *SimpleRequest) String() string { return proto.CompactTextString(m) } 43 | func (*SimpleRequest) ProtoMessage() {} 44 | func (*SimpleRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 45 | 46 | func (m *SimpleRequest) GetPayload() int32 { 47 | if m != nil { 48 | return m.Payload 49 | } 50 | return 0 51 | } 52 | 53 | type SimpleResponse struct { 54 | Payload int32 `protobuf:"varint,1,opt,name=payload" json:"payload,omitempty"` 55 | } 56 | 57 | func (m *SimpleResponse) Reset() { *m = SimpleResponse{} } 58 | func (m *SimpleResponse) String() string { return proto.CompactTextString(m) } 59 | func (*SimpleResponse) ProtoMessage() {} 60 | func (*SimpleResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 61 | 62 | func (m *SimpleResponse) GetPayload() int32 { 63 | if m != nil { 64 | return m.Payload 65 | } 66 | return 0 67 | } 68 | 69 | func init() { 70 | proto.RegisterType((*SimpleRequest)(nil), "otgrpc.testing.SimpleRequest") 71 | proto.RegisterType((*SimpleResponse)(nil), "otgrpc.testing.SimpleResponse") 72 | } 73 | 74 | // Reference imports to suppress errors if they are not otherwise used. 75 | var _ context.Context 76 | var _ grpc.ClientConn 77 | 78 | // This is a compile-time assertion to ensure that this generated file 79 | // is compatible with the grpc package it is being compiled against. 80 | const _ = grpc.SupportPackageIsVersion4 81 | 82 | // Client API for TestService service 83 | 84 | type TestServiceClient interface { 85 | UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) 86 | StreamingOutputCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (TestService_StreamingOutputCallClient, error) 87 | StreamingInputCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingInputCallClient, error) 88 | StreamingBidirectionalCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingBidirectionalCallClient, error) 89 | } 90 | 91 | type testServiceClient struct { 92 | cc *grpc.ClientConn 93 | } 94 | 95 | func NewTestServiceClient(cc *grpc.ClientConn) TestServiceClient { 96 | return &testServiceClient{cc} 97 | } 98 | 99 | func (c *testServiceClient) UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) { 100 | out := new(SimpleResponse) 101 | err := grpc.Invoke(ctx, "/otgrpc.testing.TestService/UnaryCall", in, out, c.cc, opts...) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return out, nil 106 | } 107 | 108 | func (c *testServiceClient) StreamingOutputCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (TestService_StreamingOutputCallClient, error) { 109 | stream, err := grpc.NewClientStream(ctx, &_TestService_serviceDesc.Streams[0], c.cc, "/otgrpc.testing.TestService/StreamingOutputCall", opts...) 110 | if err != nil { 111 | return nil, err 112 | } 113 | x := &testServiceStreamingOutputCallClient{stream} 114 | if err := x.ClientStream.SendMsg(in); err != nil { 115 | return nil, err 116 | } 117 | if err := x.ClientStream.CloseSend(); err != nil { 118 | return nil, err 119 | } 120 | return x, nil 121 | } 122 | 123 | type TestService_StreamingOutputCallClient interface { 124 | Recv() (*SimpleResponse, error) 125 | grpc.ClientStream 126 | } 127 | 128 | type testServiceStreamingOutputCallClient struct { 129 | grpc.ClientStream 130 | } 131 | 132 | func (x *testServiceStreamingOutputCallClient) Recv() (*SimpleResponse, error) { 133 | m := new(SimpleResponse) 134 | if err := x.ClientStream.RecvMsg(m); err != nil { 135 | return nil, err 136 | } 137 | return m, nil 138 | } 139 | 140 | func (c *testServiceClient) StreamingInputCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingInputCallClient, error) { 141 | stream, err := grpc.NewClientStream(ctx, &_TestService_serviceDesc.Streams[1], c.cc, "/otgrpc.testing.TestService/StreamingInputCall", opts...) 142 | if err != nil { 143 | return nil, err 144 | } 145 | x := &testServiceStreamingInputCallClient{stream} 146 | return x, nil 147 | } 148 | 149 | type TestService_StreamingInputCallClient interface { 150 | Send(*SimpleRequest) error 151 | CloseAndRecv() (*SimpleResponse, error) 152 | grpc.ClientStream 153 | } 154 | 155 | type testServiceStreamingInputCallClient struct { 156 | grpc.ClientStream 157 | } 158 | 159 | func (x *testServiceStreamingInputCallClient) Send(m *SimpleRequest) error { 160 | return x.ClientStream.SendMsg(m) 161 | } 162 | 163 | func (x *testServiceStreamingInputCallClient) CloseAndRecv() (*SimpleResponse, error) { 164 | if err := x.ClientStream.CloseSend(); err != nil { 165 | return nil, err 166 | } 167 | m := new(SimpleResponse) 168 | if err := x.ClientStream.RecvMsg(m); err != nil { 169 | return nil, err 170 | } 171 | return m, nil 172 | } 173 | 174 | func (c *testServiceClient) StreamingBidirectionalCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingBidirectionalCallClient, error) { 175 | stream, err := grpc.NewClientStream(ctx, &_TestService_serviceDesc.Streams[2], c.cc, "/otgrpc.testing.TestService/StreamingBidirectionalCall", opts...) 176 | if err != nil { 177 | return nil, err 178 | } 179 | x := &testServiceStreamingBidirectionalCallClient{stream} 180 | return x, nil 181 | } 182 | 183 | type TestService_StreamingBidirectionalCallClient interface { 184 | Send(*SimpleRequest) error 185 | Recv() (*SimpleResponse, error) 186 | grpc.ClientStream 187 | } 188 | 189 | type testServiceStreamingBidirectionalCallClient struct { 190 | grpc.ClientStream 191 | } 192 | 193 | func (x *testServiceStreamingBidirectionalCallClient) Send(m *SimpleRequest) error { 194 | return x.ClientStream.SendMsg(m) 195 | } 196 | 197 | func (x *testServiceStreamingBidirectionalCallClient) Recv() (*SimpleResponse, error) { 198 | m := new(SimpleResponse) 199 | if err := x.ClientStream.RecvMsg(m); err != nil { 200 | return nil, err 201 | } 202 | return m, nil 203 | } 204 | 205 | // Server API for TestService service 206 | 207 | type TestServiceServer interface { 208 | UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error) 209 | StreamingOutputCall(*SimpleRequest, TestService_StreamingOutputCallServer) error 210 | StreamingInputCall(TestService_StreamingInputCallServer) error 211 | StreamingBidirectionalCall(TestService_StreamingBidirectionalCallServer) error 212 | } 213 | 214 | func RegisterTestServiceServer(s *grpc.Server, srv TestServiceServer) { 215 | s.RegisterService(&_TestService_serviceDesc, srv) 216 | } 217 | 218 | func _TestService_UnaryCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 219 | in := new(SimpleRequest) 220 | if err := dec(in); err != nil { 221 | return nil, err 222 | } 223 | if interceptor == nil { 224 | return srv.(TestServiceServer).UnaryCall(ctx, in) 225 | } 226 | info := &grpc.UnaryServerInfo{ 227 | Server: srv, 228 | FullMethod: "/otgrpc.testing.TestService/UnaryCall", 229 | } 230 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 231 | return srv.(TestServiceServer).UnaryCall(ctx, req.(*SimpleRequest)) 232 | } 233 | return interceptor(ctx, in, info, handler) 234 | } 235 | 236 | func _TestService_StreamingOutputCall_Handler(srv interface{}, stream grpc.ServerStream) error { 237 | m := new(SimpleRequest) 238 | if err := stream.RecvMsg(m); err != nil { 239 | return err 240 | } 241 | return srv.(TestServiceServer).StreamingOutputCall(m, &testServiceStreamingOutputCallServer{stream}) 242 | } 243 | 244 | type TestService_StreamingOutputCallServer interface { 245 | Send(*SimpleResponse) error 246 | grpc.ServerStream 247 | } 248 | 249 | type testServiceStreamingOutputCallServer struct { 250 | grpc.ServerStream 251 | } 252 | 253 | func (x *testServiceStreamingOutputCallServer) Send(m *SimpleResponse) error { 254 | return x.ServerStream.SendMsg(m) 255 | } 256 | 257 | func _TestService_StreamingInputCall_Handler(srv interface{}, stream grpc.ServerStream) error { 258 | return srv.(TestServiceServer).StreamingInputCall(&testServiceStreamingInputCallServer{stream}) 259 | } 260 | 261 | type TestService_StreamingInputCallServer interface { 262 | SendAndClose(*SimpleResponse) error 263 | Recv() (*SimpleRequest, error) 264 | grpc.ServerStream 265 | } 266 | 267 | type testServiceStreamingInputCallServer struct { 268 | grpc.ServerStream 269 | } 270 | 271 | func (x *testServiceStreamingInputCallServer) SendAndClose(m *SimpleResponse) error { 272 | return x.ServerStream.SendMsg(m) 273 | } 274 | 275 | func (x *testServiceStreamingInputCallServer) Recv() (*SimpleRequest, error) { 276 | m := new(SimpleRequest) 277 | if err := x.ServerStream.RecvMsg(m); err != nil { 278 | return nil, err 279 | } 280 | return m, nil 281 | } 282 | 283 | func _TestService_StreamingBidirectionalCall_Handler(srv interface{}, stream grpc.ServerStream) error { 284 | return srv.(TestServiceServer).StreamingBidirectionalCall(&testServiceStreamingBidirectionalCallServer{stream}) 285 | } 286 | 287 | type TestService_StreamingBidirectionalCallServer interface { 288 | Send(*SimpleResponse) error 289 | Recv() (*SimpleRequest, error) 290 | grpc.ServerStream 291 | } 292 | 293 | type testServiceStreamingBidirectionalCallServer struct { 294 | grpc.ServerStream 295 | } 296 | 297 | func (x *testServiceStreamingBidirectionalCallServer) Send(m *SimpleResponse) error { 298 | return x.ServerStream.SendMsg(m) 299 | } 300 | 301 | func (x *testServiceStreamingBidirectionalCallServer) Recv() (*SimpleRequest, error) { 302 | m := new(SimpleRequest) 303 | if err := x.ServerStream.RecvMsg(m); err != nil { 304 | return nil, err 305 | } 306 | return m, nil 307 | } 308 | 309 | var _TestService_serviceDesc = grpc.ServiceDesc{ 310 | ServiceName: "otgrpc.testing.TestService", 311 | HandlerType: (*TestServiceServer)(nil), 312 | Methods: []grpc.MethodDesc{ 313 | { 314 | MethodName: "UnaryCall", 315 | Handler: _TestService_UnaryCall_Handler, 316 | }, 317 | }, 318 | Streams: []grpc.StreamDesc{ 319 | { 320 | StreamName: "StreamingOutputCall", 321 | Handler: _TestService_StreamingOutputCall_Handler, 322 | ServerStreams: true, 323 | }, 324 | { 325 | StreamName: "StreamingInputCall", 326 | Handler: _TestService_StreamingInputCall_Handler, 327 | ClientStreams: true, 328 | }, 329 | { 330 | StreamName: "StreamingBidirectionalCall", 331 | Handler: _TestService_StreamingBidirectionalCall_Handler, 332 | ServerStreams: true, 333 | ClientStreams: true, 334 | }, 335 | }, 336 | Metadata: "test.proto", 337 | } 338 | 339 | func init() { proto.RegisterFile("test.proto", fileDescriptor0) } 340 | 341 | var fileDescriptor0 = []byte{ 342 | // 210 bytes of a gzipped FileDescriptorProto 343 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, 344 | 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0xcb, 0x2f, 0x49, 0x2f, 0x2a, 0x48, 0xd6, 0x03, 345 | 0x09, 0x65, 0xe6, 0xa5, 0x2b, 0x69, 0x72, 0xf1, 0x06, 0x67, 0xe6, 0x16, 0xe4, 0xa4, 0x06, 0xa5, 346 | 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x49, 0x70, 0xb1, 0x17, 0x24, 0x56, 0xe6, 0xe4, 0x27, 0xa6, 347 | 0x48, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x06, 0xc1, 0xb8, 0x4a, 0x5a, 0x5c, 0x7c, 0x30, 0xa5, 0xc5, 348 | 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0xb8, 0xd5, 0x1a, 0xbd, 0x64, 0xe2, 0xe2, 0x0e, 0x49, 0x2d, 0x2e, 349 | 0x09, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x15, 0xf2, 0xe2, 0xe2, 0x0c, 0xcd, 0x4b, 0x2c, 0xaa, 350 | 0x74, 0x4e, 0xcc, 0xc9, 0x11, 0x92, 0xd5, 0x43, 0x75, 0x84, 0x1e, 0x8a, 0x0b, 0xa4, 0xe4, 0x70, 351 | 0x49, 0x43, 0x6d, 0x0d, 0xe3, 0x12, 0x0e, 0x2e, 0x29, 0x4a, 0x4d, 0xcc, 0xcd, 0xcc, 0x4b, 0xf7, 352 | 0x2f, 0x2d, 0x29, 0x28, 0x2d, 0xa1, 0x82, 0xa9, 0x06, 0x8c, 0x42, 0xa1, 0x5c, 0x42, 0x70, 0x73, 353 | 0x3d, 0xf3, 0xa8, 0x63, 0xac, 0x06, 0xa3, 0x50, 0x3c, 0x97, 0x14, 0xdc, 0x58, 0xa7, 0xcc, 0x94, 354 | 0xcc, 0xa2, 0xd4, 0xe4, 0x92, 0xcc, 0xfc, 0xbc, 0xc4, 0x1c, 0xaa, 0x18, 0x6f, 0xc0, 0x98, 0xc4, 355 | 0x06, 0x8e, 0x59, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfd, 0x50, 0x3e, 0xe4, 0xe7, 0x01, 356 | 0x00, 0x00, 357 | } 358 | -------------------------------------------------------------------------------- /java/README.rst: -------------------------------------------------------------------------------- 1 | ##################### 2 | GRPC-Java OpenTracing 3 | ##################### 4 | 5 | ============ 6 | Installation 7 | ============ 8 | 9 | This package is available on Maven Central and can be added to your project as follows: 10 | 11 | **Maven** 12 | 13 | .. code-block:: 14 | 15 | 16 | 17 | io.opentracing.contrib 18 | grpc-opentracing 19 | 0.2.0 20 | 21 | 22 | 23 | **Gradle** 24 | 25 | .. code-block:: 26 | 27 | compile 'io.opentracing.contrib:grpc-opentracing:0.2.0' 28 | 29 | ========== 30 | Quickstart 31 | ========== 32 | 33 | If you want to add basic tracing to your clients and servers, you can do so in a few short and simple steps, as shown below. (These code snippets use the grpc example's ``GreeterGrpc``, generated by protocol buffers.) 34 | 35 | **Servers** 36 | 37 | - Instantiate a tracer 38 | - Create a ``ServerTracingInterceptor`` 39 | - Intercept a service 40 | - (Optional) Access the `current span`_ 41 | 42 | .. _current span: `Current Span Context`_ 43 | 44 | .. code-block:: java 45 | 46 | import io.opentracing.Tracer; 47 | 48 | public class YourServer { 49 | 50 | private int port; 51 | private Server server; 52 | // Any io.opentracing.Tracer implementation will do here. For instance, 53 | // https://github.com/uber/jaeger-client-java/blob/master/jaeger-core/src/main/java/com/uber/jaeger/Tracer.java 54 | // generates Zipkin-compatible data. 55 | private final Tracer tracer; 56 | 57 | private void start() throws IOException { 58 | ServerTracingInterceptor tracingInterceptor = new ServerTracingInterceptor(this.tracer); 59 | 60 | server = ServerBuilder.forPort(port) 61 | .addService(tracingInterceptor.intercept(someServiceDef)) 62 | .build() 63 | .start(); 64 | } 65 | } 66 | 67 | **Clients** 68 | 69 | - Instantiate a tracer 70 | - Create a ``ClientTracingInterceptor`` 71 | - Intercept the client channel 72 | 73 | .. code-block:: java 74 | 75 | import io.opentracing.Tracer; 76 | 77 | public class YourClient { 78 | 79 | private final ManagedChannel channel; 80 | private final GreeterGrpc.GreeterBlockingStub blockingStub; 81 | // Any io.opentracing.Tracer implementation will do here. For instance, 82 | // https://github.com/uber/jaeger-client-java/blob/master/jaeger-core/src/main/java/com/uber/jaeger/Tracer.java 83 | // generates Zipkin-compatible data. 84 | private final Tracer tracer; 85 | 86 | public YourClient(String host, int port) { 87 | 88 | channel = ManagedChannelBuilder.forAddress(host, port) 89 | .usePlaintext(true) 90 | .build(); 91 | 92 | ClientTracingInterceptor tracingInterceptor = new ClientTracingInterceptor(this.tracer) 93 | 94 | blockingStub = GreeterGrpc.newBlockingStub(tracingInterceptor.intercept(channel)); 95 | } 96 | } 97 | 98 | There's an example of a simple traced client (`TracedClient`) and server (`TracedService`) in `src/test`. 99 | 100 | ============== 101 | Server Tracing 102 | ============== 103 | 104 | A ``ServerTracingInterceptor`` uses default settings, which you can override by creating it using a ``ServerTracingInterceptor.Builder``. 105 | 106 | - ``withOperationName(OperationNameConstructor constructor)``: Define how the operation name is constructed for all spans created for the intercepted service. Default sets the operation name as the name of the RPC method. More details in the `Operation Name`_ section. 107 | - ``withStreaming()``: Logs to the server span whenever a message is received. *Note:* This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know. 108 | - ``withVerbosity()``: Logs to the server span additional events, such as message received, half close (client finished sending messages), and call complete. Default only logs if a call is cancelled. 109 | - ``withTracedAttributes(ServerRequestAttribute... attrs)``: Sets tags on the server span in case you want to track information about the RPC call. See ServerRequestAttribute.java for a list of traceable request attributes. 110 | 111 | **Example**: 112 | 113 | .. code-block:: java 114 | 115 | ServerTracingInterceptor tracingInterceptor = new ServerTracingInterceptor 116 | .Builder(tracer) 117 | .withStreaming() 118 | .withVerbosity() 119 | .withOperationName(new OperationNameConstructor() { 120 | @Override 121 | public String constructOperationName(MethodDescriptor method) { 122 | // construct some operation name from the method descriptor 123 | } 124 | }) 125 | .withTracedAttributes(ServerRequestAttribute.HEADERS, 126 | ServerRequestAttribute.METHOD_TYPE) 127 | .build(); 128 | 129 | ============== 130 | Client Tracing 131 | ============== 132 | 133 | A ``ClientTracingInterceptor`` also has default settings, which you can override by creating it using a ``ClientTracingInterceptor.Builder``. 134 | 135 | - ``withOperationName(String operationName)``: Define how the operation name is constructed for all spans created for this intercepted client. Default is the name of the RPC method. More details in the `Operation Name`_ section. 136 | - ``withActiveSpanSource(ActiveSpanSource activeSpanSource)``: Define how to extract the current active span, if any. This is needed if you want your client to continue a trace instead of starting a new one. More details in the `Active Span Source`_ section. 137 | - ``withStreaming()``: Logs to the client span whenever a message is sent or a response is received. *Note:* This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know. 138 | - ``withVerbosity()``: Logs to the client span additional events, such as call started, message sent, half close (client finished sending messages), response received, and call complete. Default only logs if a call is cancelled. 139 | - ``withTracedAttributes(ClientRequestAttribute... attrs)``: Sets tags on the client span in case you want to track information about the RPC call. See ClientRequestAttribute.java for a list of traceable request attributes. 140 | 141 | **Example**: 142 | 143 | .. code-block:: java 144 | 145 | import io.opentracing.Span; 146 | 147 | ClientTracingInterceptor tracingInterceptor = new ClientTracingInterceptor 148 | .Builder(tracer) 149 | .withStreaming() 150 | .withVerbosity() 151 | .withOperationName(new OperationNameConstructor() { 152 | @Override 153 | public String constructOperationName(MethodDescriptor method) { 154 | // construct some operation name from the method descriptor 155 | } 156 | }) 157 | .withActiveSpanSource(new ActiveSpanSource() { 158 | @Override 159 | public Span getActiveSpan() { 160 | // implement how to get the current active span, for example: 161 | return OpenTracingContextKey.activeSpan(); 162 | } 163 | }) 164 | .withTracingAttributes(ClientRequestAttribute.ALL_CALL_OPTIONS, 165 | ClientRequestAttribute.HEADERS) 166 | .build(); 167 | 168 | .. _Operation Name: `Operation Names`_ 169 | .. _Active Span Source: `Active Span Sources`_ 170 | 171 | ==================== 172 | Current Span Context 173 | ==================== 174 | 175 | In your server request handler, you can access the current active span for that request by calling 176 | 177 | .. code-block:: java 178 | 179 | Span span = OpenTracingContextKey.activeSpan(); 180 | 181 | This is useful if you want to manually set tags on the span, log important events, or create a new child span for internal units of work. You can also use this key to wrap these internal units of work with a new context that has a user-defined active span. 182 | 183 | For example: 184 | 185 | .. code-block:: java 186 | 187 | Tracer tracer = ...; 188 | 189 | // some unit of internal work that you want to trace 190 | Runnable internalWork = someInternalWork 191 | 192 | // a wrapper that traces the work of the runnable 193 | class TracedRunnable implements Runnable { 194 | Runnable work; 195 | Tracer tracer; 196 | 197 | TracedRunnable(Runnable work, Tracer tracer) { 198 | this.work = work; 199 | this.tracer = tracer; 200 | } 201 | 202 | public void run() { 203 | 204 | // create a child span for the current active span 205 | Span span = tracer 206 | .buildSpan("internal-work") 207 | .asChildOf(OpenTracingContextKey.activeSpan()) 208 | .start(); 209 | 210 | // create a new context with the child span as the active span 211 | Context contextWithNewSpan = Context.current() 212 | .withValue(OpenTracingContextKey.get(), span); 213 | 214 | // wrap the original work and run it 215 | Runnable tracedWork = contextWithNewSpan.wrap(this.work); 216 | tracedWork.run(); 217 | 218 | // make sure to finish any manually created spans! 219 | span.finish(); 220 | } 221 | } 222 | 223 | Runnable tracedInternalWork = new TracedRunnable(internalWork, tracer); 224 | tracedInternalWork.run(); 225 | 226 | =============== 227 | Operation Names 228 | =============== 229 | 230 | The default operation name for any span is the RPC method name (``io.grpc.MethodDescriptor.getFullMethodName()``). However, you may want to add your own prefixes, alter the name, or define a new name. For examples of good operation names, check out the OpenTracing `semantics`_. 231 | 232 | To alter the operation name, you need to add an implementation of the interface ``OperationNameConstructor`` to the ``ClientTracingInterceptor.Builder`` or ``ServerTracingInterceptor.Builder``. For example, if you want to add a prefix to the default operation name of your ClientInterceptor, your code would look like this: 233 | 234 | .. code-block:: java 235 | 236 | ClientTracingInterceptor interceptor = ClientTracingInterceptor.Builder ... 237 | .withOperationName(new OperationNameConstructor() { 238 | @Override 239 | public String constructOperationName(MethodDescriptor method) { 240 | return "your-prefix" + method.getFullMethodName(); 241 | } 242 | }) 243 | .with.... 244 | .build() 245 | 246 | .. _semantics: http://opentracing.io/spec/#operation-names 247 | 248 | =================== 249 | Active Span Sources 250 | =================== 251 | 252 | If you want your client to continue a trace rather than starting a new one, then you can tell your ``ClientTracingInterceptor`` how to extract the current active span by building it with your own implementation of the interface ``ActiveSpanSource``. This interface has one method, ``getActiveSpan``, in which you will define how to access the current active span. 253 | 254 | For example, if you're creating the client in an environment that has the active span stored in a global dictionary-style context under ``OPENTRACING_SPAN_KEY``, then you could configure your Interceptor as follows: 255 | 256 | .. code-block:: java 257 | 258 | import io.opentracing.Span; 259 | 260 | ClientTracingInterceptor interceptor = new ClientTracingInterceptor 261 | .Builder(tracer) 262 | ... 263 | .withActiveSpanSource(new ActiveSpanSource() { 264 | @Override 265 | public Span getActiveSpan() { 266 | return Context.get(OPENTRACING_SPAN_KEY); 267 | } 268 | }) 269 | ... 270 | .build(); 271 | 272 | We also provide two built-in implementations: 273 | 274 | * ``ActiveSpanSource.GRPC_CONTEXT`` uses the current ``io.grpc.Context`` and returns the active span for ``OpenTracingContextKey``. This is the default active span source. 275 | * ``ActiveSpanSource.NONE`` always returns null as the active span, which means the client will always start a new trace 276 | 277 | =================================== 278 | Integrating with Other Interceptors 279 | =================================== 280 | 281 | Although we provide ``ServerTracingInterceptor.intercept(service)`` and ``ClientTracingInterceptor.intercept(channel)`` methods, you don't want to use these if you're chaining multiple interceptors. Instead, use the following code (preferably putting the tracing interceptor at the top of the interceptor stack so that it traces the entire request lifecycle, including other interceptors): 282 | 283 | **Servers** 284 | 285 | .. code-block:: java 286 | 287 | server = ServerBuilder.forPort(port) 288 | .addService(ServerInterceptors.intercept(service, someInterceptor, 289 | someOtherInterceptor, serverTracingInterceptor)) 290 | .build() 291 | .start(); 292 | 293 | **Clients** 294 | 295 | .. code-block:: java 296 | 297 | blockingStub = GreeterGrpc.newBlockingStub(ClientInterceptors.intercept(channel, 298 | someInterceptor, someOtherInterceptor, clientTracingInterceptor)); 299 | 300 | ====================== 301 | Releasing new versions 302 | ====================== 303 | 304 | Create a gradle.properties in this directory. It should look approximately like this: 305 | 306 | .. code-block:: 307 | 308 | sonatypeUsername=bensigelman 309 | sonatypePassword= 310 | signing.keyId=<`gpg --list-keys` output, minus the prefix like "2048R/"> 311 | signing.password= 312 | signing.secretKeyRingFile=/Your/Homedir/.gnupg/secring.gpg 313 | 314 | Then run: 315 | 316 | .. code-block:: 317 | 318 | $ gradle uploadArchives closeAndPromoteRepository 319 | 320 | --------------------------------------------------------------------------------