

### 目的

Pythonでマルチプロセス処理を行い、各プロセスのログをログサーバー（といってもアプリケーションの内部プロセスの一部）にて記録します。  
アプリケーションの内部プロセスの一部なので、アプリケーションを閉じる時にこのログサーバープロセスも正常に終了したいです。

### 問題点

参考コードの中の、以下の箇所においてソケット受信待ちで応答できないため、ログサーバープロセスを正常に終了できません。

[code]

    chunk = self.connection.recv(slen)
    
[/code]

Queueのemptyメソッドのような受信バッファチェックでループを回せばよいと思うのですが、そのような仕組みはあるのでしょうか？

### 参考

Python Document のLogging クックブック  
[ネットワーク越しの logging イベントの送受信](https://docs.python.org/ja/3/howto/logging-
cookbook.html#sending-and-receiving-logging-events-across-a-network)

### 参考コード

Logging クックブックの受信端ソースコードをそのまま記載しています。

[code]

    import pickle
    import logging
    import logging.handlers
    import socketserver
    import struct
    
    
    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:
                chunk = self.connection.recv(4)
                if len(chunk) < 4:
                    break
                slen = struct.unpack('>L', chunk)[0]
                chunk = self.connection.recv(slen)
                while len(chunk) < slen:
                    chunk = chunk + self.connection.recv(slen - len(chunk))
                obj = self.unPickle(chunk)
                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!
            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()
    
[/code]


CC BY-SA 4.0
2021-01-08T06:06:03.980
73155
2023-10-18T04:07:00.790
2023-09-01T04:58:28.483
3060
32891
question
python python3 logging
Python SocketHandler の受信待ち状態を解除したい。


コメントに書きましたが、ソケット受信待ちは`chunk = self.connection.recv(slen)`でしょうか？  
実は`rd, wr, ex = select.select([self.socket.fileno()],...`の部分で待っているのでは？

そちらならば例えばシグナルハンドラを登録して、メインプロセスからシグナルを送ることで終了させる処理を行えるのでは？

参考コードに示された`class
LogRecordSocketReceiver(socketserver.ThreadingTCPServer):`の、メッセージ処理ループ的になっている`def
serve_until_stopped(self):`の`while not abort:`ループの継続条件および最後に`abort =
self.abort`とあって、`self.abort`は`def __init__(...`時に`self.abort =
0`しているだけで`1`(0以外)にする処理は存在していません。

以下のように`def
__init__(...`の最後にシグナルハンドラを登録する処理を追加し、シグナルハンドラの中で`self.abort`フラグ設定するようにして、メインプロセスからシグナルを送れば、ログサーバープロセスは正常に終了すると思われます。

[code]

    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
        signal.signal(signal.SIGINT, self.signal_handler) #### シグナルハンドラ登録
    
    def signal_handler(self, signum, stack):              #### シグナルハンドラ
        self.abort = 1                                    #### abortフラグ設定
    
    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
    
[/code]

ただしWindows環境だと色々動作条件や制約があるようですが。  
[signal.signal(signalnum,
handler)](https://docs.python.org/ja/3.8/library/signal.html#signal.signal)  
こちらはOSに関わらない？

> スレッドが有効な場合、この関数はメインスレッドからしか実行できません。それ以外のスレッドからこの関数を実行しようとすると ValueError
> 例外が発生します。

こちらはWindows向け注意

> On Windows, signal() can only be called with SIGABRT, SIGFPE, SIGILL,
> SIGINT, SIGSEGV, SIGTERM, or SIGBREAK. A ValueError will be raised in any
> other case. Note that not all systems define the same set of signal names;
> an AttributeError will be raised if a signal name is not defined as SIG*
> module level constant.
>
>
> Windowsでは、signal()は、SIGABRT、SIGFPE、SIGILL、SIGINT、SIGSEGV、SIGTERM、またはSIGBREAKでのみ呼び出すことができます。
> それ以外の場合、ValueErrorが発生します。 すべてのシステムが同じ信号名のセットを定義しているわけではないことに注意してください。
> シグナル名がSIG *モジュールレベル定数として定義されていない場合、AttributeErrorが発生します。

こちらの記事も。  
[Windows環境でPythonのシグナルを受け取れない](https://ja.stackoverflow.com/q/62459/26370)  
[@sayuriさんの回答](https://ja.stackoverflow.com/a/62460/26370)を参照


CC BY-SA 4.0
2021-01-09T01:07:15.303
73163
2021-01-09T01:49:36.657
2021-01-09T01:49:36.657
26370
26370
73155
answer