| 145 | # when a request context is available. |
| 146 | class VCLogHandler(logging.Handler): |
| 147 | def emit(self, record: logging.LogRecord) -> None: |
| 148 | try: |
| 149 | message = record.getMessage() |
| 150 | except Exception: |
| 151 | message = repr(getattr(record, "msg", "")) |
| 152 | |
| 153 | with contextlib.suppress(Exception): |
| 154 | if record.exc_info: |
| 155 | # logging allows exc_info=True or a (type, value, tb) tuple |
| 156 | exc_info = record.exc_info |
| 157 | if exc_info is True: # type: ignore[comparison-overlap] |
| 158 | exc_info = sys.exc_info() |
| 159 | if isinstance(exc_info, tuple): # pyright: ignore[reportUnnecessaryIsInstance] |
| 160 | tb = "".join(traceback.format_exception(*exc_info)) |
| 161 | if tb: |
| 162 | message = f"{message}\n{tb}" if message else tb |
| 163 | |
| 164 | if record.levelno >= logging.CRITICAL: |
| 165 | level = "fatal" |
| 166 | elif record.levelno >= logging.ERROR: |
| 167 | level = "error" |
| 168 | elif record.levelno >= logging.WARNING: |
| 169 | level = "warn" |
| 170 | elif record.levelno >= logging.INFO: |
| 171 | level = "info" |
| 172 | else: |
| 173 | level = "debug" |
| 174 | |
| 175 | context = storage.get() |
| 176 | if context is not None: |
| 177 | send_message( |
| 178 | { |
| 179 | "type": "log", |
| 180 | "payload": { |
| 181 | "context": { |
| 182 | "invocationId": context["invocationId"], |
| 183 | "requestId": context["requestId"], |
| 184 | }, |
| 185 | "message": base64.b64encode( |
| 186 | message.encode() |
| 187 | ).decode(), |
| 188 | "level": level, |
| 189 | }, |
| 190 | } |
| 191 | ) |
| 192 | else: |
| 193 | # If IPC is not ready, enqueue the message to be sent later. |
| 194 | enqueue_or_send_message( |
| 195 | { |
| 196 | "type": "log", |
| 197 | "payload": { |
| 198 | "context": {"invocationId": "0", "requestId": 0}, |
| 199 | "message": base64.b64encode( |
| 200 | message.encode() |
| 201 | ).decode(), |
| 202 | "level": level, |
| 203 | }, |
| 204 | } |