|
| 1 | +"""Usage of kafka-logging-handler with multiprocessing and multithreading.""" |
| 2 | + |
| 3 | +from concurrent.futures import ThreadPoolExecutor |
| 4 | +import logging |
| 5 | +import multiprocessing |
| 6 | +from multiprocessing import Process |
| 7 | +import os |
| 8 | +import sys |
| 9 | +import threading |
| 10 | + |
| 11 | +from kafka_logger.handlers import KafkaLoggingHandler |
| 12 | + |
| 13 | +REQUIRED_ENV_VARS = ['KAFKA_SERVER', 'KAFKA_CERT', 'KAFKA_TOPIC'] |
| 14 | + |
| 15 | +MAIN_PROCESS_THREADS = 2 |
| 16 | +CHILD_PROCESSES = 2 |
| 17 | +THREAD_POOL_WORKERS = 2 |
| 18 | +THREAD_POOL_SIZE = 3 |
| 19 | + |
| 20 | +LOGGER = None |
| 21 | + |
| 22 | + |
| 23 | +def get_process_thread(): |
| 24 | + """Get string with process and thread names.""" |
| 25 | + # you can get PID and thread ID as well: |
| 26 | + # os.getpid(), threading.current_thread().ident |
| 27 | + return "(process: {}, thread: {})".format( |
| 28 | + multiprocessing.current_process().name, |
| 29 | + threading.current_thread().name |
| 30 | + ) |
| 31 | + |
| 32 | + |
| 33 | +def child_process(index): |
| 34 | + """ |
| 35 | + Log a message. |
| 36 | +
|
| 37 | + Args: |
| 38 | + index (int): index of the child process |
| 39 | + """ |
| 40 | + LOGGER.info("Hi, I'm child process #%d %s", index, get_process_thread()) |
| 41 | + |
| 42 | + |
| 43 | +def grandchild_process(): |
| 44 | + """Log a message.""" |
| 45 | + LOGGER.info("Hi, I'm sub sub process %s", get_process_thread()) |
| 46 | + |
| 47 | + |
| 48 | +def child_process_with_grandchild(): |
| 49 | + """Spawn grandchild process.""" |
| 50 | + LOGGER.info("I'm going to spawn another child %s", get_process_thread()) |
| 51 | + subprocess = Process( |
| 52 | + target=grandchild_process, |
| 53 | + name="Grandchild process") |
| 54 | + subprocess.start() |
| 55 | + subprocess.join() |
| 56 | + |
| 57 | + |
| 58 | +def thread_worker(index): |
| 59 | + """Log a message.""" |
| 60 | + LOGGER.info( |
| 61 | + "Hi, I'm a thread worker #%d in the child process thread pool %s", |
| 62 | + index, get_process_thread()) |
| 63 | + |
| 64 | + |
| 65 | +def child_process_with_threads(): |
| 66 | + """Run thread executor pool.""" |
| 67 | + LOGGER.info("I'm going to spawn multiple threads %s", get_process_thread()) |
| 68 | + with ThreadPoolExecutor(max_workers=THREAD_POOL_WORKERS) as executor: |
| 69 | + for thread_idx in range(THREAD_POOL_SIZE): |
| 70 | + executor.submit(thread_worker, thread_idx) |
| 71 | + |
| 72 | + |
| 73 | +def main_process_thread(index): |
| 74 | + """Log a message.""" |
| 75 | + LOGGER.info("Hi, I'm a thread #%d in the main process %s", |
| 76 | + index, get_process_thread()) |
| 77 | + |
| 78 | + |
| 79 | +def child_process_with_exception(): |
| 80 | + """Raise an exception after start.""" |
| 81 | + LOGGER.info("Hi, I'm child process that is going to raise exception %s", |
| 82 | + get_process_thread()) |
| 83 | + raise Exception('This exception will not occur in Kafka!') |
| 84 | + |
| 85 | + |
| 86 | +def main(): |
| 87 | + """Setup logger and test logging.""" |
| 88 | + global LOGGER |
| 89 | + |
| 90 | + # validate that Kafka configuration is available |
| 91 | + assert all([(key in os.environ) for key in REQUIRED_ENV_VARS]) |
| 92 | + |
| 93 | + LOGGER = logging.getLogger("test.logger") |
| 94 | + LOGGER.propagate = False |
| 95 | + log_level = logging.DEBUG |
| 96 | + |
| 97 | + log_format = logging.Formatter( |
| 98 | + '%(asctime)s %(name)-12s %(levelname)-8s %(message)s', |
| 99 | + '%Y-%m-%dT%H:%M:%S') |
| 100 | + |
| 101 | + # create handler to show logs at stdout |
| 102 | + stdout_handler = logging.StreamHandler(sys.stdout) |
| 103 | + stdout_handler.setLevel(log_level) |
| 104 | + stdout_handler.setFormatter(log_format) |
| 105 | + LOGGER.addHandler(stdout_handler) |
| 106 | + |
| 107 | + # create Kafka logging handler |
| 108 | + kafka_handler = KafkaLoggingHandler( |
| 109 | + os.environ['KAFKA_SERVER'], |
| 110 | + os.environ['KAFKA_TOPIC'], |
| 111 | + security_protocol='SSL', |
| 112 | + ssl_cafile=os.environ['KAFKA_CERT'], |
| 113 | + unhandled_exception_logger=LOGGER, |
| 114 | + additional_fields={ |
| 115 | + "service": "test_service" |
| 116 | + } |
| 117 | + ) |
| 118 | + kafka_handler.setFormatter(log_format) |
| 119 | + LOGGER.addHandler(kafka_handler) |
| 120 | + |
| 121 | + LOGGER.setLevel(log_level) |
| 122 | + |
| 123 | + LOGGER.info("Hi there, I'm the main process! %s", get_process_thread()) |
| 124 | + |
| 125 | + # test child processes |
| 126 | + child_processes = [] |
| 127 | + for idx in range(CHILD_PROCESSES): |
| 128 | + child = Process( |
| 129 | + target=child_process, |
| 130 | + name="Child process #{}".format(idx), |
| 131 | + args=(idx,)) |
| 132 | + child_processes.append(child) |
| 133 | + child.start() |
| 134 | + |
| 135 | + # testing threads in the main process |
| 136 | + threads = [] |
| 137 | + for idx in range(MAIN_PROCESS_THREADS): |
| 138 | + thread = threading.Thread( |
| 139 | + target=main_process_thread, |
| 140 | + name="Thread of the main process #{}".format(idx), |
| 141 | + args=(idx, )) |
| 142 | + threads.append(thread) |
| 143 | + thread.start() |
| 144 | + # wait for threads to finish |
| 145 | + for thread in threads: |
| 146 | + thread.join() |
| 147 | + |
| 148 | + # there is a chance of logs loss |
| 149 | + # if the main process terminates without joining child processes |
| 150 | + for child in child_processes: |
| 151 | + child.join() |
| 152 | + |
| 153 | + # test if a child of a child process logs correctly |
| 154 | + child_with_subprocess = Process( |
| 155 | + target=child_process_with_grandchild, |
| 156 | + name="Child process that spawns another child") |
| 157 | + child_with_subprocess.start() |
| 158 | + child_with_subprocess.join() |
| 159 | + |
| 160 | + # test threads in a child process |
| 161 | + child_with_threads = Process( |
| 162 | + target=child_process_with_threads, |
| 163 | + name="Child process that has a thread pool") |
| 164 | + child_with_threads.start() |
| 165 | + child_with_threads.join() |
| 166 | + |
| 167 | + # test unhandled exception in a child process |
| 168 | + child_exception = Process( |
| 169 | + target=child_process_with_exception, |
| 170 | + name="Child process with an exception") |
| 171 | + child_exception.start() |
| 172 | + child_exception.join() |
| 173 | + |
| 174 | + # top-level exception works only in the main process |
| 175 | + raise Exception('Testing top-level exception!') |
| 176 | + |
| 177 | + |
| 178 | +if __name__ == '__main__': |
| 179 | + main() |
0 commit comments