class_docstr: Collector is the thread that reads and parses lines from stdout or stderr.
  
  It will pass structured data to the logq, and will send a message on doneq when it finishes.
code: def _run(self) -> None:
      for line in read_newlines_or_carriage_returns(self.fd):
          # Capture the timestamp as soon as the line is collected.
          now = datetime.datetime.now(datetime.timezone.utc).isoformat()
  
          if self.dup_io:
              print(line, file=self.dup_io, flush=True, end="")
  
          if self.shipper_died:
              # Keep draining logs so process doesn't block on stdout or stderr, but don't bother
              # queuing the logs we capture.
              continue
  
          log: Dict[str, Any] = {"timestamp": now, **self.metadata}
  
          m = rank.match(line)
          if m:
              try:
                  log["rank_id"] = int(m.group("rank_id"))
                  line = m.group("log")
              except ValueError:
                  pass
  
          m = level.match(line)
          if m:
              found = m.group("level")
              log["level"] = f"LOG_LEVEL_{found}"
              line = m.group("log")
  
          log["log"] = line
  
          self.logq.put(log)
  
imports: import datetime
  import io
  import json
  import logging
  import os
  import queue
  import re
  import signal
  import socket
  import ssl
  import subprocess
  import sys
  import threading
  import time
  import traceback
  import urllib.error
  import urllib.request
  from typing import Any, Dict, Iterator, List, NamedTuple, Optional, cast
label_desc: Use image node_id 3 for calling the Collector obj's underlying member method code with example usage: obj._run() without return types