Giter Club home page Giter Club logo

jaeger-client-python's Introduction

Build Status Coverage Status PyPI Version Python Version FOSSA Status

πŸ›‘ This library is DEPRECATED!

There will be no new releases of this library.

We urge all users to migrate to OpenTelemetry. Please refer to the notice in the documentation for details.

Jaeger Bindings for Python OpenTracing API

This is a client-side library that can be used to instrument Python apps for distributed trace collection, and to send those traces to Jaeger. See the OpenTracing Python API for additional detail.

Contributing and Developing

Please see CONTRIBUTING.md.

Installation

pip install jaeger-client

Getting Started

import logging
import time
from jaeger_client import Config

if __name__ == "__main__":
    log_level = logging.DEBUG
    logging.getLogger('').handlers = []
    logging.basicConfig(format='%(asctime)s %(message)s', level=log_level)

    config = Config(
        config={ # usually read from some yaml config
            'sampler': {
                'type': 'const',
                'param': 1,
            },
            'logging': True,
        },
        service_name='your-app-name',
        validate=True,
    )
    # this call also sets opentracing.tracer
    tracer = config.initialize_tracer()

    with tracer.start_span('TestSpan') as span:
        span.log_kv({'event': 'test message', 'life': 42})

        with tracer.start_span('ChildSpan', child_of=span) as child_span:
            child_span.log_kv({'event': 'down below'})

    time.sleep(2)   # yield to IOLoop to flush the spans - https://github.com/jaegertracing/jaeger-client-python/issues/50
    tracer.close()  # flush any buffered spans

NOTE: If you're using the Jaeger all-in-one Docker image (or similar) and want to run Jaeger in a separate container from your app, use the code below to define the host and port that the Jaeger agent is running on. Note that this is not recommended, as Jaeger sends spans over UDP and UDP does not guarantee delivery. (See this thread for more details.)

    config = Config(
        config={ # usually read from some yaml config
            'sampler': {
                'type': 'const',
                'param': 1,
            },
            'local_agent': {
                'reporting_host': 'your-reporting-host',
                'reporting_port': 'your-reporting-port',
            },
            'logging': True,
        },
        service_name='your-app-name',
        validate=True,
    )

Other Instrumentation

The OpenTracing Registry has many modules that provide explicit instrumentation support for popular frameworks like Django and Flask.

At Uber we are mostly using the opentracing_instrumentation module that provides:

  • explicit instrumentation for HTTP servers, and
  • implicit (monkey-patched) instrumentation for several popular libraries like urllib2, redis, requests, some SQL clients, etc.

Initialization & Configuration

Note: do not initialize the tracer during import, it may cause a deadlock (see issues #31, #60). Instead define a function that returns a tracer (see example below) and call that function explicitly after all the imports are done.

Also note that using gevent.monkey in asyncio-based applications (python 3+) may need to pass current event loop explicitly (see issue #256):

from tornado import ioloop
from jaeger_client import Config

config = Config(config={}, service_name='your-app-name', validate=True)
config.initialize_tracer(io_loop=ioloop.IOLoop.current())

Production

The recommended way to initialize the tracer for production use:

from jaeger_client import Config

def init_jaeger_tracer(service_name='your-app-name'):
    config = Config(config={}, service_name=service_name, validate=True)
    return config.initialize_tracer()

Note that the call initialize_tracer() also sets the opentracing.tracer global variable. If you need to create additional tracers (e.g., to create spans on the client side for remote services that are not instrumented), use the new_tracer() method.

Prometheus metrics

This module brings a Prometheus integration to the internal Jaeger metrics. The way to initialize the tracer with Prometheus metrics:

from jaeger_client.metrics.prometheus import PrometheusMetricsFactory

config = Config(
        config={},
        service_name='your-app-name',
        validate=True,
        metrics_factory=PrometheusMetricsFactory(service_name_label='your-app-name')
)
tracer = config.initialize_tracer()

Note that the optional argument service_name_label to the factory constructor will force it to tag all Jaeger client metrics with a label service: your-app-name. This way you can distinguish Jaeger client metrics produced by different services.

Development

For development, some parameters can be passed via config dictionary, as in the Getting Started example above. For more details please see the Config class.

WSGI, multi-processing, fork(2)

When using this library in applications that fork child processes to handle individual requests, such as with WSGI / PEP 3333, care must be taken when initializing the tracer. When Jaeger tracer is initialized, it may start a new background thread. If the process later forks, it might cause issues or hang the application (due to exclusive lock on the interpreter). Therefore, it is recommended that the tracer is not initialized until after the child processes are forked. Depending on the WSGI framework you might be able to use @postfork decorator to delay tracer initialization (see also issues #31, #60).

Debug Traces (Forced Sampling)

Programmatically

The OpenTracing API defines a sampling.priority standard tag that can be used to affect the sampling of a span and its children:

from opentracing.ext import tags as ext_tags

span.set_tag(ext_tags.SAMPLING_PRIORITY, 1)

Via HTTP Headers

Jaeger Tracer also understands a special HTTP Header jaeger-debug-id, which can be set in the incoming request, e.g.

curl -H "jaeger-debug-id: some-correlation-id" http://myhost.com

When Jaeger sees this header in the request that otherwise has no tracing context, it ensures that the new trace started for this request will be sampled in the "debug" mode (meaning it should survive all downsampling that might happen in the collection pipeline), and the root span will have a tag as if this statement was executed:

span.set_tag('jaeger-debug-id', 'some-correlation-id')

This allows using Jaeger UI to find the trace by this tag.

Zipkin Compatibility

To use this library directly with other Zipkin libraries & backend, you can provide the configuration property propagation: 'b3' and the X-B3-* HTTP headers will be supported.

The B3 codec assumes it will receive lowercase HTTP headers, as this seems to be the standard in the popular frameworks like Flask and Django. Please make sure your framework does the same.

License

Apache 2.0 License.

jaeger-client-python's People

Contributors

ashmita152 avatar bharat-p avatar bhavin192 avatar black-adder avatar cedy avatar condorcet avatar dependabot[bot] avatar ethframe avatar etienne-carriere avatar eundoosong avatar fantoccini avatar gravelg avatar isaachier avatar jan25 avatar kasium avatar objectiser avatar pavolloffay avatar pgadige avatar philipgian avatar provok avatar rbtcollins avatar rmfitzpatrick avatar seanknight avatar thepaulm avatar timdumol avatar timstoop avatar trondhindenes avatar tsaylor avatar victorywekwa avatar yurishkuro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jaeger-client-python's Issues

no doc for how to set config?

I build it and run it fine locally. While mostly the jaeger server was in another machine. But I can't find doc to do this.

Through I can infer from this code.

    @property
    def local_agent_reporting_port(self):
        # noinspection PyBroadException
        try:
            return int(self.local_agent_group()['reporting_port'])
        except:
            return DEFAULT_REPORTING_PORT

    @property
    def local_agent_reporting_host(self):
        # noinspection PyBroadException
        try:
            return self.local_agent_group()['reporting_host']
        except:
            return DEFAULT_REPORTING_HOST

But if there is a doc it will be good for beginning.

Make dependency on tornado/threadloop optional

I would like to use jaeger-client as an opentracing tracer with zipkin.
I've already completed a proof-of-concept integration, and I fully intend to release the Zipkin codec and Zipkin reporter as soon as they are ready.

My goal is to use the client on a Django/Gunicorn/Greenlet environment, so I would like to know if there is any possibility to make the tornado/threadloop dependencies optional.

Ideally, I think that since the client's architecture is so modular, the Reporter could be renamed to TornadoReporter and properly presented on the Python package (along with the local_agent) only if the tornado/threadloop modules are on the Python PATH.

We dropped dependency on `future` but still have references to `past`

@sc68cal said:

You dropped the future package from the requirements but are still referencing it.

I get a stacktrace when trying to run this in a Python 3 environment.

File "/usr/src/app/src/jaeger-client/jaeger_client/tracer.py", line 30, in <module>
    from .codecs import TextCodec, ZipkinCodec, ZipkinSpanFormat, BinaryCodec
  File "/usr/src/app/src/jaeger-client/jaeger_client/codecs.py", line 18, in <module>
    from past.builtins import basestring
ModuleNotFoundError: No module named 'past'

Migrate to jaegertracing org

Per jaegertracing/jaeger#409

  • Move the repo
  • Re-register the repo with Travis
  • Re-register the repo with Coveralls/CodeCov
  • Update the links to badges in the README
  • Re-encrypt credentials
  • Add DCO file
  • Add sign-off checker bot
  • Update CONTRIBUTING instructions to require sign-off
  • Update to Apache license
    • Replace LICENSE file
    • Update CONTRIBUTING instructions and references to license in the README
    • Update the existing license headers (keep Uber copyright)
    • Update the check-license script (new files get The Jaeger Authors copyright)

Understanding one_span_per_rpc configuration

I just spent a while looking into problems with traces being improperly nested when dealing with calls between Go and Python services. It turned out that the needed fix was to set what appears to be an undocumented one_span_per_rpc option to false on my tracer, allowing a new span identifier to be created so that is is a proper child of the request calling my HTTP endpoint. This is the relevant code:

if rpc_server and self.one_span_per_rpc:
# Zipkin-style one-span-per-RPC
span_id = parent.span_id
parent_id = parent.parent_id
else:
span_id = self.random_id()
parent_id = parent.span_id

I see the documentation about supporting zipkin-style behavior, but am unclear on why that is the default. I'm using the jaeger, not zipkin, which I would expect is the primary use case for this library?

Is this something that it would make sense to better document and expose through the Config object?

Stuck / hangs at `Initializing Jaeger Tracer with UDP reporter`

I'm running the latest version of the client. When initialize_tracer() is called, it gets stuck / hangs at Initializing Jaeger Tracer with UDP reporter.

I'm running the agent in another Docker container that is bound to localhost ports, so it should be able to connect (I even changed the reporting host to my internal IP, rather than localhost).

EDIT: So, this is for my web app, it works when running in gunicorn.

Should span_id be a parameter of Tracer.start_span()?

HI,

I see that both jeaeger [1] and basictracer [2] generate ID for new span inside Tracer.start_span().
Which mean that we cannot control the ID from outside.
However, with SpanContext and Span we can put an ID to that.
Therefore, what is the reason for us to make new span ID not controllable?

Currently, my app already had an uuid4 ID for a span. There are 02 options to make uuid4 ID (128 bit) compatibale with OpenTracing ID (usually in 64 bit).

  1. Possible easy - Convert uuid 4 to 64 bit ID
  2. Impossible - Generate OpenTracing ID from Tracer.start_span() and convert it to uuid4

And, I decide not to use Tracer.start_span, create SpanContext and Span manually 😒

[1] https://github.com/uber/jaeger-client-python/blob/3315f85e2bcd719b7a5577a73addbf430aea20bb/jaeger_client/tracer.py#L153
[2] https://github.com/opentracing/basictracer-python/blob/master/basictracer/tracer.py#L68

root span does not reach collector when running under uwsgi

If I run my app using werkzeug.serving.run_simple all spans are propagated just fine and my traces/spans are error free. If I run the app under uwsgi and initialize the tracer in the uwsgi.post_fork_hook, i see the client claiming that it has reported the root span, but the collector never appears to receive it (I added logging when the collector receives spans). Once the root span is missing all the other spans that were one level below the root are marked as having an invalid parent, and the trace is screwed up.

Is there some consideration I'm missing when running jaeger in a tornado.wsgi.WSGIApplication under uwsgi? I suspect it may be an issue with my hacked together middleware that maintains a stack of the ongoing trace in thread-local storage ala py_zipkin. I understand how putting the trace context in TLS could be a problem in a multithreaded environment with concurrent requests, but this issue appears even when I'm only sending in a single request at a time as a proof of concept.

Or am I missing some subtlety related to how tracer flushes as discussed in #50 ?

Support sending spans over HTTP

Similar to the HttpSender in jaeger-java. It seems like it could be as simple as using THttpClient in place of TUDPTransport and connecting to the collector directly. I'm happy to help out with the implementation if needed.

Background: I have some applications that are generating huge spans (> 65k) because they are including full SQL and ES queries as logs in the trace. Right now we are manually truncating the queries, but my clients would like to be able to view them for debugging purposes.

Trying to use same span through inject/extract across scripts to update additional details using jaeger-client

Having two separate scripts...
First one to create span before execute/process something and
Second one to update some more details on the same span at end

In first script, Created root span and child span. Update some portion details in child span and injected
root and child spans were flushed with currently updated details in my execution

I have tried zipkin, as well as OPEN_TEXT format to inject and extract the same to use and treat single span.

import opentracing
from lib.tracing import init_tracer
from opentracing_instrumentation.request_context import get_current_span, span_in_context
from opentracing.ext import tags
from opentracing.propagation import Format
import json
import time
from datetime import datetime

global tracer

def get_current_time():
get_current_time = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
return get_current_time;

def create_root_span(process_name):
global tracer
tracer = init_tracer(process_name)
tracer.one_span_per_rpc = 1
tracer.one_span_per_rpc = 1
span_name = process_name
with tracer.start_span(span_name) as root_span:
with span_in_context(root_span):
root_span.set_tag('start_time', get_current_time() );
root_span.set_tag('test', 'data' );
time.sleep(2)
root_span.set_tag('span', span_name)
root_span.info('Root span created...');
with tracer.start_span( 'rpc_span', child_of=root_span.context) as child_span:
with span_in_context(child_span):
child_span.set_tag('span', span_name)
child_span.set_tag('start-time', get_current_time());
time.sleep(2)
child_span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
text_carrier = {}
tracer.inject(child_span.context, 'zipkin-span-format', text_carrier)
update_header_as_json( text_carrier );
#child_span.finish()

def update_header_as_json( text_carrier ):
filename='./rpc_tracing.txt'
with open(filename, 'w') as f:
json.dump(text_carrier, f)
f.close();

process_name = 'master-app'
create_root_span(process_name)

yield to IOLoop to flush the spans

time.sleep(5)
#tracer.close()

In second independent script, extracted the span, trying to update some portion in the span.
When it flush, it has created as child for same span-id and has details updated only in the script

from lib.tracing import init_tracer
from opentracing.ext import tags
from opentracing_instrumentation.request_context import get_current_span, span_in_context
from opentracing.propagation import Format
import time
from datetime import datetime
import json
import collections

def get_current_time():
get_current_time = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
return get_current_time;

filename='./rpc_tracing.txt'
with open(filename, 'r') as f:
text_carrier = json.load(f)
f.close()

def get_reference_object(referenced_context):

Reference(ctx=spanCtxForSpanA, refType="decorates")

Reference= collections.namedtuple('Reference', ['type', 'referenced_context'])
#return Reference('decorates',referenced_context);
return Reference('',referenced_context);

tracer = init_tracer('master-app')
tracer.one_span_per_rpc = 1

extracted_context = tracer.extract(
#Format.TEXT_MAP,
'zipkin-span-format',
text_carrier
)

span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
span_name = 'rpc-span'
with tracer.start_span(span_name,tags=span_tags, references=get_reference_object(extracted_context)) as span:
with span_in_context(span):
span.set_tag('span', span_name)
time.sleep(5)
span.set_tag('end-time', get_current_time());
time.sleep(2)
tracer.close()

Getting started code in README not working

On running the starter code (in README) in python I get the following error:
TypeError: __init__() got an unexpected keyword argument 'validate'

It seems to be working fine if I comment out validate=True

Any idea as to why this would be happening?

How to set Jaeger endpoint

Hi, i try to use the python version of the jaeger client.
In go i do that to create the tracer :

cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
		LogSpans:           true,
		LocalAgentHostPort: "192.168.1.10:5775",
	},
}

jLogger := jaegerlog.StdLogger
jMetricsFactory := metrics.NullFactory
tracer, _, err := cfg.New(
		"myservice",
		jaegercfg.Logger(jLogger),
		jaegercfg.Metrics(jMetricsFactory),
)

How do that in Python ?
Thanks

Stacktrace is not set properly on errors

The default opentracing impl logs the stacktrace in the event of an exception:
https://github.com/opentracing/opentracing-python/blob/master/opentracing/span.py#L199

However the stacktrace object gets converted to a string via str(tb) and it results in the following display in the UI: python.exception.tb | "<traceback object at 0x7f7a2aad9638>"

I think it might actually be easiest to fix this in opentracing, by logging a string representation of the stacktrace rather than the object

tracer init fail

i was trying to combine pyspider webui (https://github.com/binux/pyspider) and jaeger-client-python together. but when i try to init a tracer in it. it just can't start pyspider webui and i traceback the reason , find out it's the ThreadLoop start() to start the thread ioloop fail and get stuck. may u help to findout why? i have searched the SO , but can't find any answer on it

Config 'enabled' option should be deprecated, since it does nothing

The tracer config has an enabled key, but setting the value to False does not do anything. The tracer will still start a reporter thread and attempt to send traces over UDP.

I guess this is not really distinguishable from it being disabled in most cases, but I would expect the tracer to default to a no-op implementation if I disable it. e.g., just leave opentracing.tracer as-is, since it's already a np-op impl

Instructions for running django_opentracing with jaeger

Instructions for running in a uWSGI, gunicorn and django runserver would also be nice. I should also note that the sample app does not work with the currently released jaeger-client library.

As it stands, simply modifying the django opentracing example to use the jaeger client simply does not work.

Tracer.close() is not fully synchronous

It was meant to be fully synchronous to guarantee that all spans are flushed, however it only blocks until the spans are handed off to the sender, which itself is async, so if close() is used from a command line tool or a hello-world app that exist immediately, the spans may not have time to flush to UDP port.

Should span.tags be a dict?

Does it make sense for {span.tags} to be a dict object instead of a list? Similar to the implementation in the java client {source}

Admittedly, the behavior has been left somewhat ambiguous in opentracing, but shouldn't the Jaeger client libs maintain this degree of parity?

Crossdock build pulls too many dependencies

From the build log, the following dependencies are all pulled into the container

Installing collected packages: futures, thrift, backports.ssl-match-hostname, singledispatch, certifi, backports-abc, tornado, opentracing, contextlib2, ply, thriftrw, crcmod, threadloop, tchannel, wrapt, opentracing-instrumentation, mock, py, pytest, coverage, pytest-cov, pytest-timeout, pytest-tornado, docutils, statistics, pygaljs, pygal, pytest-benchmark, pyflakes, pep8, mccabe, flake8, docopt, coveralls, jaeger-client

That seems like an overkill, especially all the testing deps which are probably not used during the crossdock run.

@thepaulm

Allow to change service name when creating new Span

At present "service_name" is set during tracer initialization and service name set at tracer level is used as service_name when reporting a span.
It would be better if user can be allowed to change (override) "service_name" when creating a new span.

Why?
It will allow developers to change service_name for spans created to track a remove server call.
E.g. While handling an API call from a service ( "api-service") if we are making calls to external services like ElasticSearch or Mongo, new spans which are used to track these remote call can specify "service_name"="elasticsearch" or service_name="mongo" instead of using default service_name="api-service".

Config has no input verification

if you provide wrong field in a wrong place, there's no feedback.
I've wasted couple hours trying to figure out connection issues because of providing 'reporting_host':'1.2.3.4' instead of local_agent':{'reporting_host':'1.2.3.4'}.
I'll whip up a PR to address this.

Jaeger agent dropping spans bigger than 1500 MTU

Hi, thank you for opensourcing this tool along with the client libraries it is really useful. I wanted to see if there are any suggestions for the issue we are facing.

We have multiple applications running in a docker swarm cluster. I started a Jaeger all in one docker image as well as a service reachable by its name on the port 5775 to send the spans over UDP.

From our applications we are able to send and receive the spans which are smaller in size than the set MTU of 1500. If any of the traces are bigger than that limit we do not see the spans in Jaeger and tcp dump shows a message stating bad length > 1472

Is there any workaround this setting as accessing the agent through localhost in the docker container running the application is not possible without baking in the jaeger agent with a collector address in the Dockerfile of each application.

_start_receiving(self):

i'v read the source code, in agent.py , there is a method ,
@gen.engine
def _start_receiving(self):
i can't understand its use , can you explain for me briefly
thanks so much

Remote sampler always fails to refresh

Requirement - what kind of business use case are you trying to solve?

The remote sampler fails to refresh and always uses the default sampler

Problem - what in Jaeger blocks you from solving the requirement?

It looks like there are two sampling endpoints on jaeger-agent:
/sampling and /.
The former returns the strategy as a string (e.g. {"strategyType":"PROBABILISTIC"}), whereas the latter returns it as an integer (e.g. {"strategyType":0})

The python client appears to be expecting the integer version, but it is querying the /sampling endpoint. This causes the following error when refreshing the sampling strategy:

  File ".../jaeger_client/sampler.py", line 438, in _update_sampler
    self._update_rate_limiting_or_probabilistic_sampler(response)
  File ".../jaeger_client/sampler.py", line 470, in _update_rate_limiting_or_probabilistic_sampler
    raise ValueError('Unsupported sampling strategy type: %s' % s_type)
ValueError: Unsupported sampling strategy type: PROBABILISTIC

Proposal - what do you suggest to solve the problem or improve the existing situation?

We should use string constants for comparison here: https://github.com/jaegertracing/jaeger-client-python/blob/master/jaeger_client/sampler.py#L452

Any open questions to address

jaeger does not work with pyinstaller

after build with pyinstaller, jaeger not work

[132] Failed to execute script app_runner
Traceback (most recent call last):
  File "app/app_runner.py", line 39, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "app/storage/database.py", line 39, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "app/storage/db.py", line 6, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "app/metrics/monitor.py", line 11, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "app/metrics/tracing.py", line 13, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "site-packages/jaeger_client/__init__.py", line 27, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "site-packages/jaeger_client/config.py", line 25, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "site-packages/jaeger_client/reporter.py", line 32, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "site-packages/jaeger_client/thrift_gen/agent/Agent.py", line 12, in <module>
  File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
  File "site-packages/jaeger_client/thrift_gen/agent/ttypes.py", line 11, in <module>
ModuleNotFoundError: No module named 'jaeger'
``

Zipkin HTTP headers support

Hello,

I'm currently working on adding Zipkin HTTP headers support to this library and would like to discuss what the preferred method for enabling support for those headers should be.

My current solution is to have a new config parameter, something like use_zipkin_http_headers: True, which would send the Zipkin codec as extra_codecs to the Tracer and take over handling of HTTP headers over the default TextCodec.

Is this the recommended way of adding codecs to the tracer? If not, how should I be enabling the Zipkin support?

SyntaxError in Python 3

The function codecs.span_context_to_string has the following line in it:

parent_id = parent_id or 0L

This is invalid syntax in Python 3 as per PEP 237, as can be seen here:
SyntaxError: invalid syntax Traceback (most recent call last): File "/usr/local/lib/python3.6/site-packages/jaeger_client/__init__.py", line 32, in <module> from .tracer import Tracer # noqa File "/usr/local/lib/python3.6/site-packages/jaeger_client/tracer.py", line 34, in <module> from .codecs import TextCodec, ZipkinCodec, ZipkinSpanFormat, BinaryCodec File "/usr/local/lib/python3.6/site-packages/jaeger_client/codecs.py", line 140 parent_id = parent_id or 0L ^ SyntaxError: invalid syntax

Instead of 0L in should just be 0. Not sure how to make in compatible with both 2 and 3 though.

jaeger-client cannot be installed on older versions of setuptools

Essentially, this is the issue: https://stackoverflow.com/questions/48048745/setup-py-require-a-recent-version-of-setuptools-before-trying-to-install

We use a python_version specifier in the requirements here: https://github.com/jaegertracing/jaeger-client-python/blob/3.8.0/setup.py#L38, and only recent versions of setuptools support this. If you try to install and your setuptools is older, it will fail.

Not sure what the best solution is here

Better support for multiple tracers/services

In some cases it is useful to simulate the existence of different services from within the same process, e.g. when making queries to services that are not instrumented (e.g. databases). In this case we'd want the service on the span to be the remote service.

However, the only way to do this is to create multiple tracers. I came across this proposal opentracing/specification#77 for adding a service tag, but it has not been fleshed out.

The go client makes it pretty straightforward to create multiple tracers, by having methods on the config object to create a Sampler and a Reporter. However the python client does all the heavy lifting inside the initialize_tracer method, which can only be called once.

Should we break out factory methods here like we do in the go client? (see https://github.com/jaegertracing/jaeger-client-go/blob/master/config/config.go#L269).

Illegal header key with b3 headers and GRPC

I'm running into some errors trying to use jaeger on grpc requests with a zipkin compatible backend and was hoping for some pointers. I believe I just need to set the b3 propagation flag and that should set the correct headers, but when I attempt to I get the following errors:

E0130 00:48:37.591239271     112 call.cc:1001]               validate_metadata: {"created":"@1517273317.591206974","description":"Illegal header key","file":"src/core/lib/surface/validate_metadata.cc","file_line":42,"offset":0,"raw_bytes":"58 2d 42 33 2d 54 72 61 63 65 49 64 'X-B3-TraceId'"}
INFO:jaeger_tracing:Reporting span 4b16842cb7f8fff0:4b16842cb7f8fff0:0:1 poiservice-client./grpc.health.v1.Health/Check

  File "poiservice/scripts/poi_example_client.py", line 68, in check_health
    response = stub.Check(request)
  File "/usr/local/lib/python3.6/site-packages/grpc_opentracing/grpcext/_interceptor.py", line 35, in __call__
    invoker)
  File "/usr/local/lib/python3.6/site-packages/grpc_opentracing/_client.py", line 138, in intercept_unary
    result = invoker(request, metadata)
  File "/usr/local/lib/python3.6/site-packages/grpc_opentracing/grpcext/_interceptor.py", line 31, in invoker
    return self._base_callable(request, timeout, metadata, credentials)
  File "/usr/local/lib/python3.6/site-packages/grpc/_channel.py", line 483, in __call__
    credentials)
  File "/usr/local/lib/python3.6/site-packages/grpc/_channel.py", line 476, in _blocking
    _check_call_error(call_error, metadata)
  File "/usr/local/lib/python3.6/site-packages/grpc/_channel.py", line 86, in _check_call_error
    raise ValueError('metadata was invalid: %s' % metadata)
TypeError: not all arguments converted during string formatting

The raw_byte string looks suspicious and was wondering if this could be related to a python2 -> python3 change with the handling of strings in python3 as unicode and not raw byte arrays.

Client code snippet:

import logging
import sys
import grpc
import time

from jaeger_client import Config

from grpc_opentracing import open_tracing_client_interceptor
from grpc_opentracing.grpcext import intercept_channel

from grpc_health.v1 import health_pb2, health_pb2_grpc

def check_health(stub):
    request = health_pb2.HealthCheckRequest(
        service=poi_server.HEALTH_SERVICER_NAME)
    response = stub.Check(request)
    if response.status == health_pb2.HealthCheckResponse.SERVING:
        print("Service is healthy")
    else:
        print("Service is NOT healthy")

def main():
    config = Config(
        config={
            'sampler': {
                'type': 'const',
                'param': 1,
            },
            'propagation': 'b3',
            'logging': True,
            'local_agent': {
                'reporting_host': "<ip_for_docker_image>",
            },
        },
        service_name='poiservice-client')
    tracer = config.initialize_tracer()

    tracer_interceptor = open_tracing_client_interceptor(
        tracer, log_payloads=True)
    channel_args = (
        ('grpc.initial_reconnect_backoff_ms', 5000),
        ('grpc.max_reconnect_backoff_ms', 120000),
    )
    channel = grpc.insecure_channel(
        '{}:{}'.format(<host>, <port>),
        options=channel_args)
    channel = intercept_channel(channel, tracer_interceptor)

    health_stub = health_pb2_grpc.HealthStub(channel)
    print('-------------- Checking Health --------------')
    check_health(health_stub)
    stub = map_service_pb2_grpc.VenueServiceStub(channel)

Relevant pip dependencies:

grpcio (1.8.4)
grpcio-health-checking (1.8.4)
grpcio-opentracing (1.1.1)
grpcio-tools (1.8.4)
jaeger-client (3.7.2.dev0, /src/jaeger-client) [current master]

Python version

python --version
Python 3.6.3

I'm using this as a test zipkin backend

gcr.io/stackdriver-trace-docker/zipkin-collector

Thanks for your help.

should exceptions raised by span_context_from_string be suppressed

span_context_from_string will raise exceptions when carrier given is not valid.

In http request case, carrier is very likely the request's headers,

thus, upstream user can trigger exceptions by pass an invalid header (like 'uber-trace-id': 'xxx') on purpose.

Ability to specify custom `jaeger.hostname` useful for running in kubernetes or containers

This code:

hostname = socket.gethostname()
self.tags[constants.JAEGER_HOSTNAME_TAG_KEY] = hostname

Is not ideal when running jaeger_client in containerized applications. It seems most useful to allow setting a custom hostname in the config or via an environment variable. We're currently using an init_tracer() like this for flask applications:

def initialize_tracer():
    config = Config(
        config={
            'sampler': {
                'type': 'const',
                'param': 1,
            },
            'local_agent': {
                'reporting_host': os.environ.get('NODE_NAME', '127.0.0.1'),
            },
            'logging': True,
        },
        service_name=os.environ.get('APP_DEPLOYMENT_NAME', __name__),
    )
    return config.initialize_tracer()

The NODE_NAME env var is set via the kubernetes downward api to the actual kubelet running the pod (since it runs a jaeger-agent pod on every node).

Changing the code for this seems pretty trivial, but what do you suggest we name the config and env variable for this?

Thrift encoding breaks in python3

In python3, spans are unable to be serialized by thrift due to this line: https://github.com/jaegertracing/jaeger-client-python/blob/master/jaeger_client/thrift.py#L55

In python3, the encode() function returns a bytes literal, which then causes the following error in thrift, which is expecting a str:

...
  File "/Users/nziebart/.virtualenvs/myapp/lib/python3.6/site-packages/jaeger_client/thrift_gen/jaeger/ttypes.py", line 146, in write
    oprot.writeString(self.key)
  File "/Users/nziebart/.virtualenvs/myapp/lib/python3.6/site-packages/thrift/protocol/TProtocol.py", line 121, in writeString
    self.writeBinary(str_to_binary(str_val))
  File "/Users/nziebart/.virtualenvs/myapp/lib/python3.6/site-packages/thrift/compat.py", line 41, in str_to_binary
    return bytes(str_val, 'utf8')
TypeError: encoding without a string argument

It can be fixed by replacing the _to_string function linked above with:

        if six.PY2 and isinstance(s, unicode):
            return s.encode('utf8')
        else:
            return str(s)

I can open a PR for this tomorrow

3.8.0 doesn't report spans in Python 3

I have a set of apps using the Go and Python jaeger client libs and the jaeger all-in-one docker image. They're all running in a docker-compose stack - so the apps can see the jaeger container & reach it via its hostname 'jaeger'. I also have the jaeger ports exposed on localhost so I can run sanity checks on the python code locally (e.g. jaeger all-in-one's docker ps looks like 0.0.0.0:5775->5775/udp, 5778/tcp, 0.0.0.0:6831-6832->6831-6832/udp, 14268/tcp, 0.0.0.0:16686->16686/tcp)

With 3.7.1 I was able to report spans correctly - running the python code locally while relying on the default reporter config (localhost:5775 in 3.7.1) and in docker (by setting local_agent/reporting_host to 'jaeger').

However, I had to upgrade to 3.8.0 to propagate spans from the Go app to Python app (because we're on Python 3 & 'extract' was relying on a Python 2 collection check. In 3.8.0 the default reporter config is localhost:6831.

On 3.8.0 I'm unable to get any spans reported correctly from Python app, whether locally (relying on default config) or in docker (setting reporting_host to 'jaeger'). I'm also unable to get it to work changing the port back to 5775 (both locally or in docker).

The logger still prints that it's reporting spans e.g.

2018-03-08 12:23:23,226 Reporting span 346237e4f6ff27ab:346237e4f6ff27ab:0:1.....

but nothing makes it to the collector.

I see elsewhere that (#59) that Python3 is perhaps not supported - is that the root cause here? It seems like 3.8.0 was trying to address that somewhat. Is there anything else I need to change when upgrading from 3.7.1 to 3.8.0?

Python version support ?

Hi, setup.py claims Python 2.7 under classifiers, and I was sure that I had the client working on a Python2 codebase. But currently (and this seems to have changed in the last 30 days by my estimate):

Collecting jaeger_client
  Downloading https://files.pythonhosted.org/packages/ee/1d/66cfa8ef438980e523c7ca3ed98c1b865b4e18df4167f32133ae3053c86c/jaeger-client-3.10.0.tar.gz (70kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 71kB 139kB/s
Collecting requests
  Downloading https://files.pythonhosted.org/packages/49/df/50aa1999ab9bde74656c2919d9c0c085fd2b3775fd3eca826012bef76d8c/requests-2.18.4-py2.py3-none-any.whl (88kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 92kB 376kB/s
Collecting threadloop<2,>=1 (from jaeger_client)
  Downloading https://files.pythonhosted.org/packages/dc/1a/ecc8f9060b8b00f8a7edac9eb020357575d332a5ad9e32c3372c66a35f79/threadloop-1.0.2-py2-none-any.whl
Collecting thrift (from jaeger_client)
  Downloading https://files.pythonhosted.org/packages/c6/b4/510617906f8e0c5660e7d96fbc5585113f83ad547a3989b80297ac72a74c/thrift-0.11.0.tar.gz (52kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 61kB 1.6MB/s
Collecting tornado<5,>=4.3 (from jaeger_client)
  Downloading https://files.pythonhosted.org/packages/e3/7b/e29ab3d51c8df66922fea216e2bddfcb6430fb29620e5165b16a216e0d3c/tornado-4.5.3.tar.gz (484kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 491kB 2.5MB/s
Collecting opentracing<2,>=1.2.2 (from jaeger_client)
  Downloading https://files.pythonhosted.org/packages/06/c2/90b35a1abdc639a5c6000d8202c70217d60e80f5b328870efb73fda71115/opentracing-1.3.0.tar.gz
Collecting idna<2.7,>=2.5 (from requests)
  Downloading https://files.pythonhosted.org/packages/27/cc/6dd9a3869f15c2edfab863b992838277279ce92663d334df9ecf5106f5c6/idna-2.6-py2.py3-none-any.whl (56kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 61kB 5.5MB/s
Collecting urllib3<1.23,>=1.21.1 (from requests)
  Downloading https://files.pythonhosted.org/packages/63/cb/6965947c13a94236f6d4b8223e21beb4d576dc72e8130bd7880f600839b8/urllib3-1.22-py2.py3-none-any.whl (132kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 133kB 4.4MB/s
Collecting certifi>=2017.4.17 (from requests)
  Downloading https://files.pythonhosted.org/packages/7c/e6/92ad559b7192d846975fc916b65f667c7b8c3a32bea7372340bfe9a15fa5/certifi-2018.4.16-py2.py3-none-any.whl (150kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 153kB 3.2MB/s
Collecting chardet<3.1.0,>=3.0.2 (from requests)
  Downloading https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl (133kB)
    100% |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 143kB 1.4MB/s
Collecting six>=1.7.2 (from thrift->jaeger_client)
  Downloading https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl
Collecting singledispatch (from tornado<5,>=4.3->jaeger_client)
  Downloading https://files.pythonhosted.org/packages/c5/10/369f50bcd4621b263927b0a1519987a04383d4a98fb10438042ad410cf88/singledispatch-3.4.0.3-py2.py3-none-any.whl
Collecting backports_abc>=0.4 (from tornado<5,>=4.3->jaeger_client)
  Downloading https://files.pythonhosted.org/packages/7d/56/6f3ac1b816d0cd8994e83d0c4e55bc64567532f7dc543378bd87f81cebc7/backports_abc-0.5-py2.py3-none-any.whl
Installing collected packages: six, singledispatch, certifi, backports-abc, tornado, threadloop, thrift, opentracing, jaeger-client, idna, urllib3, chardet, requests
  Running setup.py install for tornado ... done
  Running setup.py install for thrift ... done
  Running setup.py install for opentracing ... done
  Running setup.py install for jaeger-client ... done
Successfully installed backports-abc-0.5 certifi-2018.4.16 chardet-3.0.4 idna-2.6 jaeger-client-3.10.0 opentracing-1.3.0 requests-2.18.4 singledispatch-3.4.0.3 six-1.11.0 threadloop-1.0.2 thrift-0.11.0 tornado-4.5.3 urllib3-1.22
(jaeger-client-test) robertc@linux-dev:~/work/vmware/symphony-repos/symphony-infra/tracing/test/image$ python
Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from jaeger_client import config as jaeger_config
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/robertc/.virtualenvs/jaeger-client-test/local/lib/python2.7/site-packages/jaeger_client/__init__.py", line 27, in <module>
    from .config import Config  # noqa
  File "/home/robertc/.virtualenvs/jaeger-client-test/local/lib/python2.7/site-packages/jaeger_client/config.py", line 24, in <module>
    from .local_agent_net import LocalAgentSender
  File "/home/robertc/.virtualenvs/jaeger-client-test/local/lib/python2.7/site-packages/jaeger_client/local_agent_net.py", line 17, in <module>
    from threadloop import ThreadLoop
  File "/home/robertc/.virtualenvs/jaeger-client-test/local/lib/python2.7/site-packages/threadloop/__init__.py", line 3, in <module>
    from threadloop.threadloop import ThreadLoop  # noqa
  File "/home/robertc/.virtualenvs/jaeger-client-test/local/lib/python2.7/site-packages/threadloop/threadloop.py", line 4, in <module>
    from concurrent.futures import Future
ImportError: No module named concurrent.futures

I've done a little looking at this, and jaeger-client-python has depended on threadloop since forever, and that has had a hard dependency on concurrent.futures since 2016 which leaves me a little puzzled as to why I thought it was working.

Anyhow, either we should fix this, and test it in CI, or we should remove Python::2.7 from the setup.py classifiers, as this is clearly not python2.7 compatible as it stands.

how can i find the thrift file in project

i am reading your code,but i just get confused the process between agent and client , so where can i find the thrift file to define what is actually transfer between them.

Python client silently ignores data larger than one UDP datagram

A. The documentation does not specify that there is any limit on data sizes and/or what happens in such a case (not sure that all users of this package are versed in UDP Transport and it's limitations).
B. The code does not handle python socket send exceptions (UDP throws an exception on a send larger than one datagram size, at least on linux).
C. The metrics counters do not count spans which were discarded because of UDP size limits anywhere.

Trace a AMQP Workflow

Hi All,

i have a distributed processing (called Workflow1 in this case) with amqp support:

Start -- amqp--> General Processing --amqp-->Fork into two queues Task A and B

So i can trace this with jaeger :) My first attempt using inject and extract:

#Start
span = tracer.start_span('Workflow')
carrier = {}
tracer.inject(span, Format.TEXT_MAP ,carrier)
with tracer.start_span('ProcessInstance_Start', child_of=span) as cspan:
    time.sleep(2) # placeholder: Do some initial processing
#Then send it to the General Processing via amqp
#Sending this with the amqp msg: carrier["uber-trace-id"]
span.finish() # I think the problem lies here!
#General Processing
#recieving msg via amqp
carrier = {}
carrier['uber-trace-id'] = json_data[0]['variables']['Trace']['value']
parentspan = tracer.extract(Format.TEXT_MAP, carrier)
with tracer.start_span('GeneralProcessing', child_of=parentspan) as span:
    time.sleep(1) # placeholder: do some general processing

#send to the next topic queue: carrier['uber-trace-id'] etc.

So 4 issues / questions:

  1. I get sometimes in the UI the result for a trace (not always):
  1. There are gaps between the spans (queue waiting time) can i fill thes gaps somehow?

  2. Points to 2: I think the thing is that a span needs to be finished where it was started. But here is the Problem in general how can i handle the overall span 'Workflow' i have to finish it in the first service which is actually wrong

  3. Dependency analysis via spark: it should display a dependency like the workflow instead it creates a 'star'like dependency!

I hope you can help me in anyway, pseudo code is also ok

Thanks in advance

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.