severb / graypy Goto Github PK
View Code? Open in Web Editor NEWPython logging handler for Graylog that sends messages in GELF (Graylog Extended Log Format).
Home Page: https://www.graylog.org/
License: BSD 3-Clause "New" or "Revised" License
Python logging handler for Graylog that sends messages in GELF (Graylog Extended Log Format).
Home Page: https://www.graylog.org/
License: BSD 3-Clause "New" or "Revised" License
Hi I'm not sure why it doesn't work, maybe some non-printable character inside? But the latest version doesn't work
File "/usr/local/lib/python2.6/dist-packages/graypy/__init__.py", line 1, in <module>
from graypy.handler import GELFHandler, WAN_CHUNK, LAN_CHUNK
File "/usr/local/lib/python2.6/dist-packages/graypy/handler.py", line 167
return {sanitize(k): sanitize(v) for k, v in obj.items()}
^
SyntaxError: invalid syntax
If I add extra field named 'status' to the extra fields dict when logging, graypy will drop the message without any error (nothing gets logged).
Example:
import logging
import graypy
mylog=logging.getLogger('order.processing')
handler=graypy.GELFHandler('mylogserver',debugging_fields=True)
mylog.addHandler(handler)
#the following line works
mylog.info("Test123",extra={'fld1':1,'fld2':2})
# the following line doesn't output anything to GrayLog:
mylog.info("Test with status", extra={'fld1':1,'fld2':2, 'status':'OK'})
I am using logger with three handlers:
Reportportal handler is able to log images. So I would like to log them and I am expecting that console log and graypy log in this situation remain silent or print some warring.
Graypy, however, post waring which contains the whole big image in binary and make impossible to read other console logs.
/env/lib64/python3.7/site-packages/graypy/handler.py:469: GELFChunkOverflowWarning: chunk overflowing GELF message: b'x\x9c\xec\xbd\xfb[\xd3N\xd07\xfc\xafp\xa8\n\x88\x90C\x93\xb4\x88\nrFD\x0e\x82\x80\x15mJ#\x08\x14\x84""\xea\xdf\xfet\xb3\x9fIf\xb2\x9b\xe2\xf7~\xdf\x1f\x9e\xeb\xb9n/\xaf-m\x92\xcd\xee\xec\x9cgv\xf6~\xe8G\xfb\xea\xfa\xe4\xa2345\xe4N8C\xe3C\xc7\x17\xd7\xdd\xde\x97\xb3\x8b\xcb\x9b\xd6q\xef\xfb\xf5\xf1\xc5U\xf7\xf3y\xfb\xfa\xba\xf9\xb5\xdd\xbb\xf0\xe3\xe4\xa8}1p\xdd\xbaj\xb7;\xbdk\xdd\x81\xee\xf9\xe5d\xfe\xd5\x9d\xb8\xec|\xed=\xd6=\xe9=\xd2m\x9e_\x0eM\xb9A-\xaaF5/\x0c\'|\xc7\xa9\xd6\xfd`|\xe8\xac\xfd\xa3}64\x15\x8e\x0f%\xcd\xd6\xc9\xd9I\xf7\xae\xd7\xf5\xe5Y\xf3\xae}\xf5\xac\xdb{\xf0\xba\xd7Err\xa6^8y|q\xde\x9e\xfcv\xd2=mN~=\xe9N&W\x17\x9d\xee\xb3v\xe7(\xbd\xf1\xa4\xf3u\xf2\xfc\xee\xa6{rv=\xd9\xba8\xbfl^\xb5W\xce{#\x9d\xb8\xbc\xebuqv\xd2\xe9u\xe1\xf9\xe3C\x9f\x93\x9bN\xab\xab\'zv\xf1\xf5\xf3\x89\xba\xa9w\xc7\xe7\xcb\x93\xa3\xde\x1d\xb5zM\xdd\xd4=\xbej7\x8f>w\x9a\xe7\xea\xcdo\x9b\'\x9d\xf7\xe9/\xe9\x8dW\x17\xad\x1e\x14\xf8\xc5\r\xfd\x93\xba\xda\x9bk\xeb\xf4\xf3I\'\xb9\x18\x9a\xea\xdc\x9c\x9d\xf5~jv{\xbf\x1d\x9f\xb7;=x\xde\x0f\x1d5\xbb\xcd\xdec\x8d\x9b$I\x8e6\xd6\x97\x1aW\x8dN\xe3\xc6q\xdc\xa6\xfet\x1c\xd1^\xad,\xcfo\x19\xbfW-w\xaa\xd6\xd3\xdd6\xe2\xf4[h\xdeU\xbb=M_\xe6\xf2\x1f\x07\xf4\xc7\xca\xfc\xec\xfb\x9f\xe8 m\xf7\xf5GE\x7f\xfc\xd1\x1f\x1f\xf5\xc7\x87\x0f\xe9C\x89\xfe6\xb7\xae?\xbb;\xfa3\xf4\xf5\xe7%\xef\xae\xbcu\xc2z\xa4\xffN\xf8\x05\xaf\xa5?\xaf\xad\xcf8n\x1d/\xdfL\xbfU\xcd\xeb\x9ff\xf8 u;8\xa3?\xf1\xf5\x03\xbf6\x9f\xde\xee\xf3\x9f\x9e\xaa\xd1\xb5\x1c\xfd\xe5\x0b\xeb\xbcf\xfc\xd4[\x15\xf1\xa6C\xfd1\xa5?\x86\xd3\xa7\x12Z\xbe\xec\xa1\xe4Ic\xc8\xc3\x7f\xf6\xf0\xaf\xb1\xfd\x9b\x97e\x10\x9b\xca\xd7\xd0\xad\xbfY,N<J\xec\x7f\x13\xc0\x14z\xfc\xcb\xb2\xe4\xed;
I would like to see warring which contains only informartion about binary content or short preview.
Running into an issue with certain objects not being JSON serializable using the GELFRabbitHandler and causing a crash.
Would it be possible to allow for a custom handler or handle the case of unserializable objects?
I have the following patch I use, but it is dependent on django for string references to the serializer from a dictConfig. I see BaseGELFHandler._pack_gelf_dict has the default option set that is not present in GELFRabbitHandler.makePickle. That might at least resolve the crash condition.
from django.utils.module_loading import import_string
class GELFRabbitHandler(BaseGELFHandler, SocketHandler):
def __init__(self, url, exchange='logging.gelf', exchange_type='fanout',
virtual_host='/', routing_key='', serializer_class=None, **kwargs):
...
self.serializer = import_string(serializer_class)
def makePickle(self, record):
message_dict = self._make_gelf_dict(record)
return json.dumps(message_dict, cls=self.serializer)
import logging
import graypy
my_logger = logging.getLogger('test_logger')
my_logger.setLevel(logging.DEBUG)
handler = graypy.GELFHandler('my_graylog_server', 12201)
my_logger.addHandler(handler)
my_adapter = logging.LoggerAdapter(logging.getLogger('test_logger'),
{ 'username': 'John' })
my_adapter.debug('Hello Graylog2 from John.')
is not working
I think the issue is the url that should send to /gelf
because when I curl from the terminal to my graylog server , it works
curl -XPOST http://my_graylog_server:12201/gelf -p0 -d '{"short_message":"Hello there", "host":"example1111.org", "facility":"test", "_foo":"bar"}'
The feature of adding a Formatter to the GELFHandler is a life-saver. Please make a release!
I'd be more than happy to contribute.
I have too little Python knowledge to explain this; but I had to edit graypy/init and change
from handler import ..
to
from graypy.handler import ..
or else the import graypy
statement would throw an error
It would be best if we were to implement automatic PyPI deployment via travis-ci to keep the versioning consistent. Our testing is not very sufficient, but, being able to release more often is a good thing.
A good example of implementing the basic auth for automatically deploying to PyPI is shown below:
https://medium.com/@mikkokotila/deploying-python-packages-to-pypi-with-travis-works-9a6597781556
@severb I assume graypy
is uploaded under your account. Would it be possible for you to implement such a feature. You could essentially go hands free after this for maintaining graypy
.
This is working from the command line:
echo -e '{"version": "1.1","host":"example.org","short_message":"WOOORRRKIIING","full_message":"Backtrace here\n\nmore stuff","level":1,"_user_id":9001,"_some_info":"foo","_some_env_var":"bar"}\0' | nc -w 1 -u 104.197.106.62 12201
However, when I try the examples given in graypy documentation, it is not working. No errors, but I cannot see the logs on Graylog.
import logging
logger = logging.getLogger('projectname')
logger.debug('Please, work! :) ')
This is my setup:
'gelf': {
'class': 'graypy.GELFHandler',
'host': '104.197.106.62',
'port': 12201,
}
and
'projectname': {
'handlers': ['gelf'],
'level': 'DEBUG',
}
Please, note that when I do 'host': 'localhost'
it is working on the local Graylog server. That's why I think that the problem may be in 'host': '104.197.106.62'
. Is DNS name required? or am I doing something wrong?
Thank you!
The Debian package of graypy 1.1.2-1 fails in test_resolve_host:
I: pybuild base:217: cd /<<PKGBUILDDIR>>/.pybuild/cpython2_2.7_graypy/build; python2.7 -m pytest tests
============================= test session starts ==============================
platform linux2 -- Python 2.7.16, pytest-3.10.1, py-1.7.0, pluggy-0.8.0
rootdir: /<<PKGBUILDDIR>>, inifile:
collected 262 items
tests/integration/test_chunked_logging.py s [ 0%]
tests/integration/test_common_logging.py sssssssssssssss [ 6%]
tests/integration/test_debugging_fields.py sssssss [ 8%]
tests/integration/test_extra_fields.py sssssss [ 11%]
tests/integration/test_status_issue.py sssssssssssssssssssssssssssssssss [ 24%]
ssssssssssss [ 28%]
tests/unit/test_ExcludeFilter.py ....... [ 31%]
tests/unit/test_GELFRabbitHandler.py ..x. [ 32%]
tests/unit/test_handler.py ............................................. [ 50%]
...................sssssssssssssss...................................... [ 77%]
......................................F........ss.......... [100%]
=================================== FAILURES ===================================
______________________________ test_resolve_host _______________________________
def test_resolve_host():
"""Test all posible resolutions of :meth:`BaseGELFHandler._resolve_host`"""
assert socket.gethostname() == BaseGELFHandler._resolve_host(False, None)
assert socket.getfqdn() == BaseGELFHandler._resolve_host(True, None)
> assert socket.gethostname() == BaseGELFHandler._resolve_host(True, "localhost")
E AssertionError: assert 'x86-grnet-02' == 'x86-grnet-02.debian.org'
E - x86-grnet-02
E + x86-grnet-02.debian.org
tests/unit/test_handler.py:176: AssertionError
========= 1 failed, 168 passed, 92 skipped, 1 xfailed in 0.86 seconds ==========
BaseGELFHandler._resolve_host
returns the FQDN, but socket.gethostname
returns only the hostname (first part of the FQDN).
Full build log: https://buildd.debian.org/status/fetch.php?pkg=graypy&arch=all&ver=1.1.2-1&stamp=1562588890&raw=0
According to the specification "A message MUST NOT consist of more than 128 chunks.".
https://docs.graylog.org/en/3.0/pages/gelf.html
There's a bug where graypy allows a chunk size between 128 and 256. The error is perceived when another API tries to decode a large gelf message encode by graypy.
On handler.py the validation should be on this line:
self.pieces = \ struct.pack('B', int(math.ceil(len(message) * 1.0 / size))) self.id = struct.pack('Q', random.randint(0, 0xFFFFFFFFFFFFFFFF))
I could send an PR for this but I'd have to choose an exception.
Regards!
I just run pip install graypy today on new machine that i using and them a get this error on older project.
AttributeError: module 'graypy' has no attribute 'GELFHandler'
Release 1.1.1 an hour ago.
from graypy import GELFHandler
Traceback (most recent call last):
File "", line 1, in
ImportError: cannot import name 'GELFHandler'
Is the 1.x release not intended to be backwards compatible with 0.3.x releases?
Would you be open to supporting a timestamp format as specified by RFC 5424 (section 6.2.3) for readability? I know that this is not specified by GELF 1.1 so I would suggest to make it configurable using a timestamp_format='rfc-3339'
parameter.
Related standards: ISO 8601, RFC 3339.
Hi,
I have a python script which creates a graylog server (and adds it's domain name). The script is itself setup to log to graylog (and to a file). When I run the script, it outputs:
Traceback (most recent call last):
File "/usr/lib64/python2.7/logging/handlers.py", line 556, in emit
self.send(s)
File "/home/ec2-user/.virtualenvs/python27/lib/python2.7/site-packages/graypy/handler.py", line 40, in send
DatagramHandler.send(self, s)
File "/usr/lib64/python2.7/logging/handlers.py", line 607, in send
self.sock.sendto(s, (self.host, self.port))
gaierror: [Errno -2] Name or service not known
because the domain name of the log server does not yet exist. Can the code be changed to suppress this output? It does not output anything if the DNS entry exists, but there is no server listening.
It would be nice to have TCP sender also, to work with graylog2 tcp inputs
Hey @severb
Since graypy
is an evolving project, I think it would be great to add automated tests/CI support using Travis. I can help in setting up that. Let me know if you are interested.
Thanks.
Should fix README.rst to include logging.basicConfig() before sending log messages. Patch follows.
diff --git a/README.rst b/README.rst
index 14cc99f..7a4fe98 100644
--- a/README.rst
+++ b/README.rst
@@ -17,6 +17,7 @@ Messages are sent to Graylog2 using a custom handler for the builtin logging lib
import logging
import graypy
@@ -30,6 +31,7 @@ Alternately, use GELFRabbitHandler
to send messages to RabbitMQ and configur
import logging
import graypy
@@ -43,6 +45,7 @@ Tracebacks are added as full messages::
import logging
import graypy
I was having trouble with the GELFRabbitHandler example because the queue and exchange weren't bound. I eventually realized this because rabbitmqadmin
provides the feedback "Message published but NOT routed". GELFRabbitHandler provides no feedback about this situation. I checked and a comparable situation exists if the exchange is missing.
Are there any acceptable options to remedy this situation? I work in a regulated industry (healthcare). I'm using the GELFRabbitHandler because I have to maintain the integrity of the logs (can't drop UDP packets). If GELFRabbitHandler isn't working, I really need to know so I can stop serving content.
I realize errors while logging are a bit of a catch-22 since the log is the standard place to send them. Is there an appropriate (alternative) way of escalating an issue like this? For example, would you be open to a PR that adds a "strict" mode that:
If you prefer, I could make the 3rd bullet a callback to make it easy to produce custom behaviors (e.g. email the admins).
My filter:
class ContextInfoFilter(logging.Filter):
def filter(self, record):
record.friend="yes"
record.user = user.id
return True
logger.addFilter(ContextInfoFilter())
logger.info("hi graylog")
Graylog logs:
{
user: "u'serge'", //the u' and ' should not be logged
friend: "'yes'", //the ' surrounding yes should not be logged
...
}
The Graylog documentation says:
GELF TCP does not support compression due to the use of the null byte (\0) as frame delimiter.
But the GELFTcpHandler
class allows to set the compress
parameter to True, thereby breaking the reception of logs by Graylog.
Please, remove the compress
parameter from GELFTcpHandler
class.
... so when can we can pull all the new stuff? :)
I can still pip install from my repo, so this is not really critical!
As raised in the graylog's issue tracker, the order of the messages are random if the messages come in the same millisecond. Shouldn't we have an internal sequence ID created by graypy for solving this problem?
my_logger.info('test', extra={'str': 10*'aaa '})
- this is working
my_logger.info('test5', extra={'str': 10000*'aaa '})
- this is not :(
I use GELFHandler via GELF UDP. Is it possible to work with graylog with large (~300Kb) messages?
I'm super new to both Graylog2 and graypy, so please let me know if I'm doing something wrong!
I'm working on a simple request timing middleware for Django and I would like to be able to send this data to Graylog2 (so I can chart it). This isn't working for me right now, as It looks like non-string extra_fields values are being converted to strings repr() inside of add_extra_fields():
if isinstance(value, basestring):
message_dict['_%s' % key] = value
else:
message_dict['_%s' % key] = repr(value)
I can send you a pull request that changes this section to allow numbers or strings to be written if needed.
Does anyone know what this is caused by? It seems to happen in Python 2.6 but not 2.7.
>>> handler = graypy.GELFRabbitHandler('amqp://guest:guest@localhost/', 'logging.gelf')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/cesar/virtualenvs/venomenv/lib/python2.6/site-packages/graypy/rabbitmq.py", line 46, in __init__
super(GELFRabbitHandler, self).__init__(host, port)
TypeError: super() argument 1 must be type, not classobj
I was wondering if there were any plans to add support for sending to Graylog GELF TCP inputs that have TLS enabled?
I'd be happy to look into this more if that would be helpful.
I like the stack trace in full_message when it is an exception.
but the fallback to record.message() that is also used for the short_message is just unnecessary data collection into graylog.
What would be nice if we could write a filter that can provide a custom implementation on a record.
e.g. record.get_full_message()
Hi
I am new this technology in first place. I am exploring few option to achieve following sceneario
From source system, I need to pull syslog , encrypt it and then send to gray log servers... so is that mandatory to send syslog via AMQP ? this program automatically encrypts message which is being sent to graylog. sorry if these silly doubts...but I am trying to analyze if this program can help me
Any ideas on how you would hook this up to Django-less Celery? The logging documentation doesn't seem all that helpful.
Thanks!
Line 103 in d07d200
GraylogTcpHandler tries to pass chunk_size to the base class and fails with exception:
Traceback (most recent call last):
File "/srv/cms/cms/afl/wsgi.py", line 22, in
application = get_wsgi_application()
File "/srv/cms/cms-lib/django/core/wsgi.py", line 14, in get_wsgi_application
django.setup()
File "/srv/cms/cms-lib/django/init.py", line 20, in setup
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
File "/srv/cms/cms-lib/django/utils/log.py", line 87, in configure_logging
logging_config_func(logging_settings)
File "/usr/lib/python2.7/logging/config.py", line 794, in dictConfig
dictConfigClass(config).configure()
File "/usr/lib/python2.7/logging/config.py", line 576, in configure
'%r: %s' % (name, e))
ValueError: Unable to configure handler 'graylog-gelf': global name 'chunk_size' is not defined
Is there a recommended way of handling large messages so they are not completely dropped by graylog? I added the following patch, but only having used graylog/GELF for a couple days, I imagine there is a better way to handle this case as it drops all but the base set of fields.
class GELFRabbitHandler(BaseGELFHandler, SocketHandler):
def makePickle(self, record):
message_dict = self._make_gelf_dict(record)
data = json.dumps(message_dict)
if len(data) > 32766:
data = json.dumps({
'version': message_dict['version'],
'host': message_dict['host'],
'short_message': 'OVERSIZED_MESSAGE: ' + message_dict['short_message'][:2048],
'timestamp': message_dict['timestamp'],
'level': message_dict['level'],
'facility': message_dict['facility'],
}, cls=self.serializer)
return data
Hi,
I want secure communication to graylog. So I decided to use GELF TCP with TLS.
Does graypy supports it?
I've got two ideas on how to add this feature: add a flag in the GELFHTTPHandler or create a new GELFHTTPSHandler.
For both of them there is a very low code effort, what do you think about them?
How can I open a PR?
Should be 'userid' instead of 'username'. I'm using the latest amqp lib.
Hi, It's good to have that
Presently the GLEF facility
field is deprecated. As stated in their docs: http://docs.graylog.org/en/2.4/pages/gelf.html#gelf-payload-specification
We could either drop sending this field or send it as additional field _facility
.
This line doesn't do what it should do: https://github.com/severb/graypy/blob/master/graypy/handler.py#L112
traceback.format_exc(...) does not take in an exception tuple. The proper way to do this is:
return traceback.format_exception(*exc_info) if exc_info else ''
(not tested but seems right).
See the arguments here for print_exception and print_exc:
http://docs.python.org/library/traceback.html#traceback.print_exc
I have the following settings for django in settings.py
:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(levelname)s %(message)s'
}
},
'filters': {
'fields': {
'env': 'test'
}
},
'handlers': {
'graypy': {
'level': 'DEBUG',
'class': 'graypy.GELFHandler',
'host': 'graylog2.example.org',
'port': 12201,
'filters': ['fields']
}
},
'loggers': {
'testlogger': {
'handlers': ['graypy'],
'level': 'DEBUG',
'propagate': True
}
}
}
But I didn't see any filters setup for graylog in GUI, so I checked it locally with logging.getLogger('testlogger').handlers[0].filters[0].__dict__
and it returns {'name': '', 'nlen': 0}
. Then I made a little change that I throw the filters
out handlers
and put that in loggers
like:
'loggers': {
'testlogger': {
'handlers': ['graypy'],
'filters': ['fields'],
'level': 'DEBUG',
'propagate': True
}
}
And it didn't work as well. The only way seems working for me is manually add a filter class just like the sample in README. So I don't know if dict config hasn't been supported by graylog or I did something in my config.
Hello,
We have logs being stored in s3 buckets on AWS. I was wondering what you all thought about using your code as a Lambda function to move those logs into Graylog?
Thanks for any feedback!
Chris Edwards
The GELF protocol only allows string and numeric values.
Type Bool is excluded from the log.
Hi guys,
Thanks for the package! Really awesome stuff.
I'd like to ask if there's a way to append extra fields to each log request from the initialization of GELFHandler itself?
I'm running a tornado
app and I'm able to log everything correctly with:
handler = graypy.GELFHandler('ip, 12201, localname="service")
logging.getLogger("tornado.access").addHandler(handler)
logging.getLogger("tornado.application").addHandler(handler)
logging.getLogger("tornado.general").addHandler(handler)
I'd like to be able to append some extra fields onto every log that uses the GELFHandler. Looking at the docs, it seems like the way to do it is via LoggerAdapter but that wouldn't work in tornado's case as it uses the logger("tornado.access") by default etc.
Hello,
Commit d96aabe#graypy/handler.py introduced the logging of loads of additional fields in the record. Some fields were added to a skip list.
This change broke interop with logbook. Logbook adds a member called frame to the record before emitting it. This is a frame and cannot be JSON serialized.
Could 'frame' be added to the skiplist?
Cheers,
Colin
The version 0.2.14 on https://pypi.python.org/pypi/graypy is not identical to the 0.2.14 release file on Github.
The pypi release matches revision 10f97aa of the 11th of July, which was the first merge after 0.2.13.
Can this project start keeping a changelog? There have been a number of new releases without an indication to people using the library what changed from one version to the next. It makes it hard to stay up to date when each update is a mystery box.
stelios reported that
When django throws an error 500 graypy throws TypeError when doing
json.dumps.
Here's (part of) the stacktrace:
Traceback (most recent call last):
File "/usr/lib/python2.6/logging/handlers.py", line 541, in emit
s = self.makePickle(record)
File "/usr/local/lib/python2.6/dist-packages/graypy/handler.py",
line 28, in makePickle
return zlib.compress(json.dumps(message_dict))
File "/usr/lib/python2.6/json/__init__.py", line 230, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python2.6/json/encoder.py", line 367, in encode
chunks = list(self.iterencode(o))
File "/usr/lib/python2.6/json/encoder.py", line 309, in _iterencode
for chunk in self._iterencode_dict(o, markers):
File "/usr/lib/python2.6/json/encoder.py", line 275, in
_iterencode_dict
for chunk in self._iterencode(value, markers):
File "/usr/lib/python2.6/json/encoder.py", line 317, in _iterencode
for chunk in self._iterencode_default(o, markers):
File "/usr/lib/python2.6/json/encoder.py", line 323, in
_iterencode_default
newobj = self.default(o)
File "/usr/lib/python2.6/json/encoder.py", line 344, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <WSGIRequest
This is "normal" since WSGIRequest is not JSON serializable but how
can you then send error 500 errors from
Django to graylog2. Hope I'm not missing something obvious...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.