In implementing logging using SocketHandlerL, I proceeded while observing packets with WireShark to understand the sample code of "Sending and receiving logging events over the network" described in the Logging cookbook of the Python document.
See the link below in the Python documentation. Sending and receiving logging events over the network (https://docs.python.org/ja/3.7/howto/logging-cookbook.html#sending-and-receiving-logging-events-across-a-network)
The sample code on the sender side is as it is described in the Python document Logging cookbook.
import logging, logging.handlers
rootLogger = logging.getLogger('')
rootLogger.setLevel(logging.DEBUG)
socketHandler = logging.handlers.SocketHandler('localhost',
logging.handlers.DEFAULT_TCP_LOGGING_PORT)
# don't bother with a formatter, since a socket handler sends the event as
# an unformatted pickle
rootLogger.addHandler(socketHandler)
# Now, we can log to the root logger, or any other logger. First the root...
logging.info('Jackdaws love my big sphinx of quartz.')
# Now, define a couple of other loggers which might represent areas in your
# application:
logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')
logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')
The sample code on the receiving side is the code described in the Python document Logging cookbook with some comments and print statements.
import pickle
import logging
import logging.handlers
import socketserver
import struct
import json
class LogRecordStreamHandler(socketserver.StreamRequestHandler):
"""Handler for a streaming logging request.
This basically logs the record using whatever logging policy is
configured locally.
"""
def handle(self):
"""
Handle multiple requests - each expected to be a 4-byte length,
followed by the LogRecord in pickle format. Logs the record
according to whatever policy is configured locally.
"""
while True:
#Waiting for reception...When received, read 4 bytes and get the length of the data part
chunk = self.connection.recv(4)
#Display the first 4 bytes in hexadecimal
print(chunk.hex())
if len(chunk) < 4:
break
#Convert received length to decimal, paying attention to endianness
slen = struct.unpack('>L', chunk)[0]
print(slen)
#Get the acquired reception length
chunk = self.connection.recv(slen)
#Let's display it in hexadecimal.
#Now compare it with the data captured by WireShark.
print(chunk.hex())
#Here, if the retrieved data is less than the reception length, it loops until it can be received.
while len(chunk) < slen:
chunk = chunk + self.connection.recv(slen - len(chunk))
print(chunk.hex())
#Since it will be pickled, expand it
obj = self.unPickle(chunk)
#Expanded to dict. In the documentation it is referred to as attrdict.
print(type(obj),json.dumps(obj,indent=4))
#Create a new LogRecord instance from attrdict
# https://docs.python.org/ja/3/library/logging.html?highlight=makelogrecord#logging.makeLogRecord
record = logging.makeLogRecord(obj)
self.handleLogRecord(record)
def unPickle(self, data):
return pickle.loads(data)
def handleLogRecord(self, record):
# if a name is specified, we use the named logger rather than the one
# implied by the record.
if self.server.logname is not None:
name = self.server.logname
else:
name = record.name
logger = logging.getLogger(name)
# N.B. EVERY record gets logged. This is because Logger.handle
# is normally called AFTER logger-level filtering. If you want
# to do filtering, do it at the client end to save wasting
# cycles and network bandwidth!
# https://docs.python.org/ja/3/library/logging.html#logging.Handler.handle
#Output log record according to the condition set on the sending side
logger.handle(record)
class LogRecordSocketReceiver(socketserver.ThreadingTCPServer):
"""
Simple TCP socket-based logging receiver suitable for testing.
"""
allow_reuse_address = True
def __init__(self, host='localhost',
port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
handler=LogRecordStreamHandler):
socketserver.ThreadingTCPServer.__init__(self, (host, port), handler)
self.abort = 0
self.timeout = 1
self.logname = None
def serve_until_stopped(self):
import select
abort = 0
while not abort:
rd, wr, ex = select.select([self.socket.fileno()],
[], [],
self.timeout)
if rd:
self.handle_request()
abort = self.abort
def main():
logging.basicConfig(
format='%(relativeCreated)5d %(name)-15s %(levelname)-8s %(message)s')
tcpserver = LogRecordSocketReceiver()
print('About to start TCP server...')
tcpserver.serve_until_stopped()
if __name__ == '__main__':
main()
The sender is focusing on the part that received the first logging.info minutes. The part selected in blue is the data part.
The first thing you see is
About to start TCP server...
0000026a
618
In packet capture, the first 4 bytes of the data part is 0000026a, which is the same as the output. Converting this to a decimal number is 618. There is a notation of TCP Payload (622 bytes) in the packet capture, and it is exactly the same when the read 4 bytes are subtracted.
Next, the acquired data part is displayed. Obviously, it is the same as the captured data of the remaining data part.
7d71002858040000006e616d6571015804000000726f6f74710258030000006d7367710358260000004a61636b64617773206c6f7665206d792062696720737068696e78206f662071756172747a2e710458040000006172677371054e58090000006c6576656c6e616d6571065804000000494e464f710758070000006c6576656c6e6f71084b145808000000706174686e616d6571095870000000633a5c55736572735c46756e6168617368695c446f63756d656e74735c76735f636f64655f707974686f6e5c4e45582d534d4152545f53657474696e675f546f6f6c5f72656e65775c5f6578616d706c655f5c5f746573745f636f64655f5c546573745f536f636b65744c6f672e7079710a580800000066696c656e616d65710b5811000000546573745f536f636b65744c6f672e7079710c58060000006d6f64756c65710d580e000000546573745f536f636b65744c6f67710e58080000006578635f696e666f710f4e58080000006578635f7465787471104e580a000000737461636b5f696e666f71114e58060000006c696e656e6f71124b0c580800000066756e634e616d65711358080000003c6d6f64756c653e711458070000006372656174656471154741d7f81312eb188858050000006d7365637371164740850afa68000000580f00000072656c6174697665437265617465647117473fffe9ec00000000580600000074687265616471184d2c18580a0000007468726561644e616d657119580a0000004d61696e546872656164711a580b00000070726f636573734e616d65711b580b0000004d61696e50726f63657373711c580700000070726f63657373711d4dd424752e
Since this is pickled, it is expressed as dict format attrdict when expanded.
{
"name": "root",
"msg": "Jackdaws love my big sphinx of quartz.",
"args": null,
"levelname": "INFO",
"levelno": 20,
"pathname": "c:\\Users\\Funahashi\\Documents\\vs_code_python\\NEX-SMART_Setting_Tool_renew\\_example_\\_test_code_\\Test_SocketLog.py",
"filename": "Test_SocketLog.py",
"module": "Test_SocketLog",
"exc_info": null,
"exc_text": null,
"stack_info": null,
"lineno": 12,
"funcName": "<module>",
"created": 1608536546.772103,
"msecs": 772.1030712127686,
"relativeCreated": 4.986286163330078,
"thread": 12160,
"threadName": "MainThread",
"processName": "MainProcess",
"process": 14752
}
When I instantiated this dict with logging.makeLogRecord and passed it to logger.handle, I got the result of logging.
1 root INFO Jackdaws love my big sphinx of quartz.
Recommended Posts