I did figure this out.  I would say that learning the object oriented techniques needed for pyQt is not so easy.  I wanted to post my code in the hopes that it will help others starting out with pyQt. It is heavily commented and covers:
- Creating a logger that reports to a QT text window defined by QT
 
- Designer Qt signals for communicating with the main loop and worker
thread
 
- transferring variables when starting a worker thread
 
- altering logging levels using the GUI
 
- having the worker thread update a progress bar
 
- Running under Spyder without causing the kernal to die all the time
 
This answer is maybe longer than generally accepted, but having a template like this would have saved me an immense amount of frustration and internet searching, so I will present it anyway.  It is more or less my application template going forward.  There may still be bugs in the code, and I am new at this, so any suggestions or improvements would be welcome.
The code copies and modifies much help from the stackoverflow community.  I am very grateful for all your help.
Template GUI code:
#  Template GUI application using
#  pyqt5 and Qtdesigner
#  by "BikeClubVest"
#  3 June 2022
import sys
import logging
from time import sleep
from PyQt5  import (
    QtCore, # Core routines
    QtGui, # Not sure
    uic, # contains translator for .ui files
    QtWidgets  # contains all widget descriptors
)
from PyQt5.QtCore import (
        Qt, 
        QObject, 
        QThread, 
        pyqtSignal
)
# shortcut declarations to make programming simpler
Signal = QtCore.pyqtSignal
Slot = QtCore.pyqtSlot
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
    QPlainTextEdit,
)
# This points to the creator XML file
#qtCreatorFile = r'C:\(your path here)\logging_display2.ui'
# use ".\(filename)" to point to the same directory as the Python code
qtCreatorFile = r'.\logging_display4.ui'
# This activates a translator that converts the .ui file
# into a Class with name "Ui_My_App_Window" that contains
# all the pyQt5 descriptors for the designed GUI.  You don't
# get to see the code here, but it gets loaded into memory
Ui_My_App_Window, QtBaseClass = uic.loadUiType(qtCreatorFile)
# Calls the basic config routine to initialize default config settings.
# Enter desired values for the default logger here.
# If this is not the first call to basicConfig,
# nothing will be changed.
logging.basicConfig() 
# identifies the current logger
logger = logging.getLogger(__name__)
# Sets the logging level for the default logger
# Can be: DEBUG, INFO, WARNING, ERROR, or CRITICAL, by default
#logger.setLevel(logging.INFO)
logger.setLevel(logging.DEBUG)
#
# Signals need to be contained in a QObject or subclass in order to be correctly
# initialized.
#
class Signaller(QtCore.QObject):
    # creates a signal named "log event" with the type as shown
    log_event = Signal(str, logging.LogRecord)
#
# Output to a Qt GUI is only supposed to happen on the main thread. So, this
# handler is designed to take a slot function which is set up to run in the main
# thread. In this example, the function takes a string argument which is a
# formatted log message, and the log record which generated it. The formatted
# string is just a convenience - you could format a string for output any way
# you like in the slot function itself.
#
# You specify the slot function to do whatever GUI updates you want. The handler
# doesn't know or care about specific UI elements.
#   
    
class QtHandler(QObject, logging.Handler):
    def __init__(self, slotfunc, *args, **kwargs):
        super(QtHandler, self).__init__(*args, **kwargs)
        # Get available signals from "Signaller" class
        self.signaller = Signaller()
        # Connect the specific signal named "log_event"
        # to a function designated by the caller
        self.signaller.log_event.connect(slotfunc)
    # When a new log event happens, send the event
    # out via the named signal, in this case "log_event"
    def emit(self, record):
        s = self.format(record)
        self.signaller.log_event.emit(s, record)
#
# This example uses QThreads, which means that the threads at the Python level
# are named something like "Dummy-1". The function below gets the Qt name of the
# current thread.
#
def ctname():
    return QtCore.QThread.currentThread().objectName()
    # Create a worker class.  In general, this is where your
    # desired custom code will go for execution outside the main
    # event handling loop.
class Worker(QObject):
    
    # The initialization function is where the data from the event 
    # loop (My_Application) gets passed to the worker function.
    # The worker can reference the data as self.data_passed
    def __init__(self, data_passed = [], parent=None):
        QThread.__init__(self, parent)
        self.data_passed = data_passed
    
   
    # with the proper initialization, these signals
    # Could be added to the Signaller class, above
    finished = Signal()
    progress = Signal(int)
    progress_report = Signal(list)
        
    # This is the definition of the task  the worker will 
    # perform.  You can have more than one task for work.
    # Other workers will be defs within this class
    # with different names performing different tasks.
    def worker_task_name(self):
        try:
            extra = {'qThreadName': QtCore.QThread.currentThread().objectName() }
            logger.log(logging.INFO, 'Started work', extra=extra)
            
            '''
            worker code goes here!
            '''
            x=2
            # send a message to the logger
            logger.log(logging.DEBUG, 'test 0: ' + self.data_passed[0], extra=extra)
            # emit data on the "progress" signal
            self.progress.emit(0)
            # emit data on the "progress_report" signals=
            self.progress_report.emit([20, self.data_passed[0]])
            sleep(x)
            logger.log(logging.INFO, 'test 1: ' + self.data_passed[1], extra=extra)
            self.progress.emit(1)
            self.progress_report.emit([40, self.data_passed[1]])
            sleep(x)
            logger.log(logging.WARNING, 'test 2: ' + self.data_passed[2], extra=extra)
            self.progress.emit(2)
            self.progress_report.emit([60, self.data_passed[2]])
            sleep(x)
            logger.log(logging.ERROR, f'test 3: ' + self.data_passed[3], extra=extra)
            self.progress.emit(3)
            self.progress_report.emit([80, self.data_passed[3]])
            sleep(x)
            logger.log(logging.CRITICAL, f'test 4: ' + self.data_passed[4], extra=extra)
            self.progress.emit(4)
            self.progress_report.emit([99, self.data_passed[4]])
            sleep(x)
            
            '''
            end worker code
            '''
        # the "try-except" structure catches run-time errors and performs
        # an action, in this case reporting the error to the log window
        except Exception as e:
            logger.log(logging.ERROR, 'Exception = ' + str(e), extra = extra)
        # signal main thread that the code is done
        self.progress.emit(0)
        self.progress_report.emit([0, 'Ready'])
        self.finished.emit()
# This is the main application loop
# it references the widget "QMainWindow"
# and the GUI code implemented by
# the uic.loadUiType call, above.
class My_Application(QMainWindow, Ui_My_App_Window):
    # Colors are used in the "update status"
    # routine, below.
    COLORS = {
        logging.DEBUG: 'black',
        logging.INFO: 'blue',
        logging.WARNING: 'brown',
        logging.ERROR: 'red',
        logging.CRITICAL: 'purple',
    }
    LOG_LEVELS = {
        'DEBUG': logging.DEBUG,
        'INFO': logging.INFO,
        'WARNING': logging.WARNING,
        'ERROR': logging.ERROR,
        'CRITICAL': logging.CRITICAL,
    }
    
    # some data for passing to the worker function
    passed_data = ['This','is','all','passed','data!']
        
    def __init__(self):
        # Run the QMainWindow initialization routine
        QMainWindow.__init__(self)
        # initialize the Ui
        # the name should be "Ui_(object_name_of_top-level_object_in_ui_file)
        # This makes the setupUi routine, below, callable
        Ui_My_App_Window.__init__(self)
        # this runs the setupUi code inside the Ui class that was loaded with
        # the call to uic.loadUiType, above.  No Ui variables will be
        # accessible until the setupUi routine is run.
        self.setupUi(self)
        
        # Widgets for the GUI were created with "Qt Designer"
        # so there is no need to set up the GUI using code.
        # The widgets do need to be connected to the event loop:
        self.log_button.clicked.connect(self.manual_update)
        self.work_button.clicked.connect(self.start_worker)
        self.exit_button.clicked.connect(self.run_exit)
        self.logging_level_select.currentTextChanged.connect(self.change_logging_level)
        
        #routine to set up the logger
        self.setup_logger()
        self.handler.setLevel('INFO')
        
        # a status indicator for the loop
        self.statuss = 0
        
        
    # This routine is called once by the "My_Application"
    # class  init routine to set up the log handler
    def setup_logger(self):        
        
        # Set up the logging handler, in this case "QtHandler"
        # defined above.  Remember to use qThreadName rather 
        # than threadName in the format string.
        
        # This line connects the handler to "QtHandler" and
        # points the handler to the slot function ("update_status")
        # for the emit signal.  This sends the
        # signal to the routine "update_status", defined below.
        self.handler = QtHandler(self.update_status)
        
        # fs is a string containing format information
        # some options are:
        # %(pathname)s Full pathname of the source file where the logging call was issued(if available).
        # %(filename)s Filename portion of pathname.
        # %(module)s Module (name portion of filename).
        # %(funcName)s Name of function containing the logging call.
        # %(lineno)d Source line number where the logging call was issued (if available).
        # %(asctime)s time of log event
        #Trying different formatters, below        
        #fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s'
        fs = '(line %(lineno)d) %(qThreadName)-12s %(levelname)-8s %(message)s'
        
        # formatter calls the logging formatter with the format
        # string and takes a return reference for the desired 
        # format.
        formatter = logging.Formatter(fs)
        
        # sets the formatter for "QtHandler"
        self.handler.setFormatter(formatter)
        
        # Adds QtHandler to the current logger identified above
        # at the very beginning of the program
        logger.addHandler(self.handler)
    
    # When data is emitted in the "progress" signal, this routine
    # will be called, and the value of the "progress" signal will
    # be passed into "n" and the "worker_label" text will be updated
    def reportProgress(self, n):
        self.statuss = n
        self.worker_label.setText(f"Long-Running Step: {n}")
    
    
    def change_logging_level(self, level):
        extra = {'qThreadName': ctname() }
        #level = self.logging_level_select.currentText
        log_level = self.LOG_LEVELS.get(level, logging.INFO)
        message = f'change_logging_level to {level}, {log_level},  handler = {self.handler}'
        message = message.replace("<", "_").replace(">", "_")
        logger.log(logging.DEBUG, message, extra = extra)
        self.handler.setLevel(level)
        message = f'now log level = ' + str(self.handler)
        message = message.replace("<", "_").replace(">", "_")
        logger.log(log_level, message, extra = extra)
    
    # receives data when a log event happens and posts the data
    # to the log window
    def update_status(self, status, record):
        color = self.COLORS.get(record.levelno, 'black')
        s = '<pre><font color="%s">%s</font></pre>' % (color, status)
        self.log_text.appendHtml(s)
    
    def manual_update(self):
        # This routine happens when the "manual_update" button is clicked
        # This function uses the formatted message passed in, but also uses
        # information from the record to format the message in an appropriate
        # color according to its severity (level).
        extra = {'qThreadName': ctname() }
        
        #if self.thread.isRunning():
        if self.statuss == 0:
            level, message = logging.WARNING, 'Thread Stopped ' + str(self.statuss)
        else:
            try:
                if self.thread.isRunning() == True:
                    level, message = logging.INFO, 'Thread Running ' + str(self.statuss)
                else:
                    level, message = logging.WARNING, 'Thread Stopped ' + str(self.statuss)
            except Exception as e:
                level, message = logging.ERROR, 'Thread Killed Before Completion: ' + str(e)
                self.statuss = 0
        logger.log(level, message, extra=extra)
    
    # this routine is executed when a signal is emitted by the worker thread
    # a change in the declared signal "progress_rreport" wlll trigger this function.
    def report_progress_bar(self, progress_report):
        extra = {'qThreadName': ctname() }
        logger.log(logging.DEBUG, 'Starting report_progress_bar ', extra = extra)
        try:
            logger.log(logging.DEBUG, f'progress_report = {progress_report}', extra = extra)
            self.load_progress_bar.setValue(progress_report[0])
            self.case_currently_loading.setText(progress_report[1])
        except Exception as e:
            logger.log(logging.ERROR, 'Exception = ' + str(e), extra = extra)
        logger.log(logging.DEBUG, 'report_progress_bar finished', extra = extra)
    # this starts up the worker thread.  First the thread is created, then the 
    # specific worker is constructed by calling the "Worker" class, and passing the
    # required data (in this case, "self.data_passed".  then the worker is moved to the
    # thread, then the specific function is started, then signals are connected,
    # then the thread is started, then other stuff is adjusted.
    def start_worker(self):
        try:
            # Create a QThread object
            self.thread = QThread()
            # Give the thread a name
            self.thread.setObjectName('WorkerThread')
            # Create a worker object by calling the "Worker" class defined above
            self.worker = Worker(self.passed_data)
            # Move worker to the thread just created
            self.worker.moveToThread(self.thread)
            
            # Connect signals and slots:
            # This connects the thread start troutine to the
            # name of the routine defined under the "Worker" class, above
            self.thread.started.connect(self.worker.worker_task_name)
                        
            # this connects the "finished' signal to three routines
            # to end the thread when the worker is finished.
            self.worker.finished.connect(self.thread.quit)
            self.worker.finished.connect(self.worker.deleteLater)
            self.thread.finished.connect(self.thread.deleteLater)
            
            # this connects the "progress" signal defined in
            # in the "Worker" class
            self.worker.progress.connect(self.reportProgress)
            
            # this executes the "progress_report" signal to the "" function
            # for updating the progress bar in the display
            self.worker.progress_report.connect(self.report_progress_bar)
            
            #  Start the thread
            self.thread.start()
            
            # Final resets
            
            # Disables the "worker_start" button when the
            # thread is launched.  Leaving the button enabled
            # will mean that the task can be re-launched multiple
            # times which may or may not be a problem.
            self.work_button.setEnabled(False)
            
            # re-enables the "Work Start" button disabled above when
            # when the thread has finished
            self.thread.finished.connect(lambda: self.work_button.setEnabled(True))
            # sets the stepLabel text when the thread has finished
            self.thread.finished.connect(lambda: self.stepLabel.setText("Long-Running Step: 0"))
        except Exception as e:
            logger.log(logging.ERROR, 'Exception = ' + str(e), extra = {'qThreadName': ctname() })        
        
    # Quits the event loop and closes the window when the Exit
    # button is pressed.
    def run_exit(self):
        self.close()
        
    def force_quit(self):
        pass
        # For use when the window is closed
        #if self.worker_thread.isRunning():
            #self.kill_thread()
######################################################################
# The code below  resolves the need to kill the kernal after closing
def main():
    # sets the name of the current thread in QThread
    QtCore.QThread.currentThread().setObjectName('MainThread')
    
    # If the application is not already runnng, launch it,
    # otherwise enter the exitsting instance.
    if not QApplication.instance():
        app = QApplication(sys.argv)
    else:
        print('already running')
        app = QApplication.instance()
        
    # launch ghd main window
    main = My_Application()
    # show he main window
    main.show()
    # put the main window at the top of the desktop
    main.raise_()
    
    # for execution from Spyder, disable the next
    # line or Spyder will throw an error
    #sys.exit(app.exec_())
    # returning main to the global coller will avoid
    # problems with Spyder kernel terminating when
    # the application is launched under Spyder
    return main
if __name__ == '__main__':      
    
    # creating a dummy variable ("m") to accept the 
    # object returned from "main" will prevent problems
    # with the kernal dying while running under Spyder.
    m = main()
Here is the .ui file I created in qtdesigner named "logging_display4.ui".
For those who are new, the .ui files are just xml files that you can open and view in a text editor.  To use this file, just copy it into notepad or your favorite text editor and save it as "logging_display4.ui".  It can then be opened by qt designer.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>My_Main_Window</class>
 <widget class="QMainWindow" name="My_Main_Window">
  <property name="enabled">
   <bool>true</bool>
  </property>
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1037</width>
    <height>844</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Logging Example</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="work_button">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>20</y>
      <width>261</width>
      <height>81</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>14</pointsize>
     </font>
    </property>
    <property name="text">
     <string>Start Worker</string>
    </property>
   </widget>
   <widget class="QPushButton" name="log_button">
    <property name="geometry">
     <rect>
      <x>320</x>
      <y>20</y>
      <width>261</width>
      <height>81</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>14</pointsize>
     </font>
    </property>
    <property name="text">
     <string>Manual Log</string>
    </property>
   </widget>
   <widget class="QPushButton" name="exit_button">
    <property name="geometry">
     <rect>
      <x>740</x>
      <y>20</y>
      <width>261</width>
      <height>81</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>14</pointsize>
     </font>
    </property>
    <property name="text">
     <string>Exit</string>
    </property>
   </widget>
   <widget class="QLabel" name="worker_label">
    <property name="geometry">
     <rect>
      <x>340</x>
      <y>110</y>
      <width>661</width>
      <height>51</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>12</pointsize>
     </font>
    </property>
    <property name="accessibleName">
     <string>Worker Status</string>
    </property>
    <property name="layoutDirection">
     <enum>Qt::LeftToRight</enum>
    </property>
    <property name="autoFillBackground">
     <bool>false</bool>
    </property>
    <property name="styleSheet">
     <string notr="true"/>
    </property>
    <property name="frameShape">
     <enum>QFrame::Panel</enum>
    </property>
    <property name="frameShadow">
     <enum>QFrame::Raised</enum>
    </property>
    <property name="lineWidth">
     <number>3</number>
    </property>
    <property name="text">
     <string>Worker Running -- Step: 0</string>
    </property>
    <property name="alignment">
     <set>Qt::AlignCenter</set>
    </property>
   </widget>
   <widget class="QPlainTextEdit" name="log_text">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>200</y>
      <width>971</width>
      <height>561</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>8514oem</family>
     </font>
    </property>
    <property name="plainText">
     <string/>
    </property>
   </widget>
   <widget class="QLabel" name="worker_label_2">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>110</y>
      <width>301</width>
      <height>51</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>12</pointsize>
     </font>
    </property>
    <property name="accessibleName">
     <string>Worker Status</string>
    </property>
    <property name="layoutDirection">
     <enum>Qt::LeftToRight</enum>
    </property>
    <property name="autoFillBackground">
     <bool>false</bool>
    </property>
    <property name="styleSheet">
     <string notr="true"/>
    </property>
    <property name="frameShape">
     <enum>QFrame::Panel</enum>
    </property>
    <property name="frameShadow">
     <enum>QFrame::Raised</enum>
    </property>
    <property name="lineWidth">
     <number>3</number>
    </property>
    <property name="text">
     <string>Set Log Level:</string>
    </property>
    <property name="alignment">
     <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
    </property>
   </widget>
   <widget class="QComboBox" name="logging_level_select">
    <property name="geometry">
     <rect>
      <x>200</x>
      <y>120</y>
      <width>111</width>
      <height>31</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <pointsize>10</pointsize>
     </font>
    </property>
    <property name="currentIndex">
     <number>3</number>
    </property>
    <item>
     <property name="text">
      <string>CRITICAL</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>ERROR</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>WARNING</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>INFO</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>DEBUG</string>
     </property>
    </item>
   </widget>
   <widget class="QLabel" name="case_currently_loading">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>170</y>
      <width>301</width>
      <height>21</height>
     </rect>
    </property>
    <property name="sizePolicy">
     <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
      <horstretch>0</horstretch>
      <verstretch>0</verstretch>
     </sizepolicy>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>8</pointsize>
     </font>
    </property>
    <property name="accessibleName">
     <string>Worker Status</string>
    </property>
    <property name="layoutDirection">
     <enum>Qt::LeftToRight</enum>
    </property>
    <property name="autoFillBackground">
     <bool>false</bool>
    </property>
    <property name="styleSheet">
     <string notr="true"/>
    </property>
    <property name="frameShape">
     <enum>QFrame::StyledPanel</enum>
    </property>
    <property name="frameShadow">
     <enum>QFrame::Plain</enum>
    </property>
    <property name="lineWidth">
     <number>0</number>
    </property>
    <property name="text">
     <string>Ready</string>
    </property>
    <property name="alignment">
     <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
    </property>
   </widget>
   <widget class="QProgressBar" name="load_progress_bar">
    <property name="geometry">
     <rect>
      <x>340</x>
      <y>170</y>
      <width>661</width>
      <height>23</height>
     </rect>
    </property>
    <property name="value">
     <number>0</number>
    </property>
    <property name="textVisible">
     <bool>false</bool>
    </property>
    <property name="orientation">
     <enum>Qt::Horizontal</enum>
    </property>
    <property name="invertedAppearance">
     <bool>false</bool>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1037</width>
     <height>31</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>