11import logging
2- from socket import timeout
2+ import socket
3+ import errno
4+ import struct
5+ from io import BytesIO
36
4- from thriftpy .transport import TTransportException , TFramedTransportFactory
5- from thriftpy .rpc import make_client
7+ from thriftpy .transport import TTransportException , TFramedTransportFactory ,\
8+ TSocket
9+ from thriftpy .protocol import TBinaryProtocolFactory
10+ from thriftpy .protocol .binary import write_message_begin , write_val
11+ from thriftpy .thrift import TClient , TType , TMessageType
612
713from .util import base64_thrift_formatter
814from .scribe import scribe_thrift
1218
1319CONNECTION_RETRIES = [1 , 10 , 20 , 50 , 100 , 200 , 400 , 1000 ]
1420
21+ try :
22+ MSG_NOSIGNAL = socket .MSG_NOSIGNAL
23+ except :
24+ MSG_NOSIGNAL = 16384 # python2
25+
26+
27+ class TNonBlockingSocket (TSocket ):
28+ def _init_sock (self ):
29+ super (TNonBlockingSocket , self )._init_sock ()
30+
31+ # 1M sendq buffer
32+ self .sock .setsockopt (socket .SOL_SOCKET , socket .SO_SNDBUF , 1024 * 1024 )
33+
34+ self .sock .setblocking (0 )
35+
36+ def open (self ):
37+ self ._init_sock ()
38+
39+ addr = self .unix_socket or (self .host , self .port )
40+ status = self .sock .connect_ex (addr )
41+
42+ if status not in [errno .EINPROGRESS , errno .EALREADY ]:
43+ raise IOError ("connection attempt on a non-clean socket" ,
44+ errno .errorcode [status ])
45+
46+ def write (self , buff ):
47+ # First ensure our incoming end is always empty
48+ self .read_all ()
49+
50+ # Then actually try to write to socket
51+ try :
52+ # We are a library, we can't just set sighandlers. But we don't
53+ # want SIGPIPE if peer has gone away either. We better set
54+ # MSG_NOSIGNAL to avoid that.
55+ # If peer has disconnected, then a errno.EPIPE will raise, and
56+ # will be catched on the uppper layer
57+
58+ self .sock .sendall (buff , MSG_NOSIGNAL )
59+ except socket .error as e :
60+ if e .errno not in [errno .EINPROGRESS , # Not connected yet
61+ errno .EWOULDBLOCK ]: # write buffer full
62+ # In all other cases, raise.
63+ raise
64+
65+ # If not yet connected or write buffer is full, silently drop.
66+
67+ def read_all (self ):
68+ """
69+ Flush incoming buffer
70+ """
71+ try :
72+ while True : # socket.error.errno.EAGAIN will exit this
73+ self .sock .recv (1024 )
74+ except socket .error as e :
75+ # if EAGAIN or EWOULDBLOCK, then there is nothing to read
76+ if e .errno not in [errno .EAGAIN , errno .EWOULDBLOCK ]:
77+ # Otherwise that's an error.
78+ raise
79+
80+ return # No more data to read, or connection is not ready
81+
82+ def read (self , _ ):
83+ """
84+ Mock response, we don't care about results. We never actually read
85+ them. But we don't want client to wait for server to reply.
86+ """
87+ buffer = BytesIO ()
88+ seq_id = 0 # Sequence id is never compared to message.
89+ write_message_begin (buffer , 'Log_result' , TMessageType .REPLY , seq_id )
90+
91+ response = scribe_thrift .Scribe .Log_result (
92+ success = scribe_thrift .ResultCode .OK )
93+ write_val (buffer , TType .STRUCT , response )
94+
95+ out = buffer .getvalue ()
96+
97+ # Framed message, starts with length of message.
98+ return struct .pack ('!i' , len (out )) + out
99+
100+
101+ def make_client (service , host , port ,
102+ proto_factory = TBinaryProtocolFactory (),
103+ trans_factory = TFramedTransportFactory ()):
104+
105+ socket = TNonBlockingSocket (host , port )
106+ transport = trans_factory .get_transport (socket )
107+ protocol = proto_factory .get_protocol (transport )
108+ transport .open ()
109+ return TClient (service , protocol )
110+
15111
16112class Client (object ):
17113
18114 host = None
19115 port = 9410
20116 _client = None
21117 _connection_attempts = 0
22- # This is used in TSocket which divides by 1000 before passing it to
23- # python socket. This is ms, do not trust socket.settimeout documentation.
24- timeout = 50
25118
26119 @classmethod
27120 def configure (cls , settings , prefix ):
28121 cls .host = settings .get (prefix + 'collector' )
29122 if prefix + 'collector.port' in settings :
30123 cls .port = int (settings [prefix + 'collector.port' ])
31- if prefix + 'collector.timeout' in settings :
32- cls .timeout = int (settings [prefix + 'collector.timeout' ])
33124
34125 @classmethod
35126 def get_connection (cls ):
@@ -47,8 +138,7 @@ def get_connection(cls):
47138 try :
48139 cls ._client = make_client (
49140 scribe_thrift .Scribe , host = cls .host ,
50- port = cls .port , timeout = cls .timeout ,
51- trans_factory = TFramedTransportFactory ())
141+ port = cls .port )
52142
53143 cls ._connection_attempts = 0
54144 except TTransportException :
@@ -79,10 +169,6 @@ def log(cls, trace):
79169 cls ._client = None
80170 logger .error ('EOFError while logging a trace on zipkin '
81171 'collector %s:%d' % (cls .host , cls .port ))
82- except timeout :
83- cls ._client = None
84- logger .error ('timeout when sending data or connecting to '
85- 'collector %s:%d' % (cls .host , cls .port ))
86172 except Exception :
87173 cls ._client = None
88174 logger .exception ('Unknown Exception while logging a trace on '
@@ -91,6 +177,11 @@ def log(cls, trace):
91177 else :
92178 logger .warn ("Can't log zipkin trace, not connected" )
93179
180+ @classmethod
181+ def disconnect (cls ):
182+ if cls ._client :
183+ cls ._client .close ()
184+
94185
95186def log (trace ):
96187 Client .log (trace )
0 commit comments